Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f44ff7d31 | ||
|
|
e85ec8ffb7 |
24
Cargo.lock
generated
24
Cargo.lock
generated
@@ -1910,6 +1910,24 @@ name = "bindgen"
|
||||
version = "0.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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 = [
|
||||
"bitflags 2.9.0",
|
||||
"cexpr",
|
||||
@@ -1920,7 +1938,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash 1.1.0",
|
||||
"rustc-hash 2.1.1",
|
||||
"shlex",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
@@ -5848,7 +5866,7 @@ dependencies = [
|
||||
"ashpd",
|
||||
"async-task",
|
||||
"backtrace",
|
||||
"bindgen 0.70.1",
|
||||
"bindgen 0.71.1",
|
||||
"blade-graphics",
|
||||
"blade-macros",
|
||||
"blade-util",
|
||||
@@ -8220,7 +8238,7 @@ name = "media"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bindgen 0.70.1",
|
||||
"bindgen 0.71.1",
|
||||
"core-foundation 0.10.0",
|
||||
"core-video",
|
||||
"ctor",
|
||||
|
||||
@@ -190,7 +190,7 @@ impl ActivityIndicator {
|
||||
fn pending_language_server_work<'a>(
|
||||
&self,
|
||||
cx: &'a App,
|
||||
) -> impl Iterator<Item = PendingWork<'a>> {
|
||||
) -> impl Iterator<Item = PendingWork<'a>> + use<'a> {
|
||||
self.project
|
||||
.read(cx)
|
||||
.language_server_statuses(cx)
|
||||
|
||||
@@ -89,22 +89,25 @@ impl AskPassSession {
|
||||
buffer.clear();
|
||||
}
|
||||
let prompt = String::from_utf8_lossy(&buffer);
|
||||
if let Some(password) = delegate
|
||||
match delegate
|
||||
.ask_password(prompt.to_string())
|
||||
.await
|
||||
.context("failed to get askpass password")
|
||||
.log_err()
|
||||
{
|
||||
stream.write_all(password.as_bytes()).await.log_err();
|
||||
} else {
|
||||
if let Some(kill_tx) = kill_tx.take() {
|
||||
kill_tx.send(()).log_err();
|
||||
Some(password) => {
|
||||
stream.write_all(password.as_bytes()).await.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)
|
||||
|
||||
@@ -741,20 +741,29 @@ impl AssistantPanel {
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(context_editor) = context_editor {
|
||||
Some(InlineAssistTarget::Editor(context_editor, false))
|
||||
} else if let Some(workspace_editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
{
|
||||
Some(InlineAssistTarget::Editor(workspace_editor, true))
|
||||
} else if let Some(terminal_view) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<TerminalView>(cx))
|
||||
{
|
||||
Some(InlineAssistTarget::Terminal(terminal_view))
|
||||
} else {
|
||||
None
|
||||
match context_editor {
|
||||
Some(context_editor) => Some(InlineAssistTarget::Editor(context_editor, false)),
|
||||
_ => {
|
||||
match workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
{
|
||||
Some(workspace_editor) => {
|
||||
Some(InlineAssistTarget::Editor(workspace_editor, true))
|
||||
}
|
||||
_ => {
|
||||
match workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<TerminalView>(cx))
|
||||
{
|
||||
Some(terminal_view) => {
|
||||
Some(InlineAssistTarget::Terminal(terminal_view))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -250,33 +250,35 @@ impl InlineAssistant {
|
||||
selection.end.column = snapshot
|
||||
.buffer_snapshot
|
||||
.line_len(MultiBufferRow(selection.end.row));
|
||||
} else if let Some(fold) =
|
||||
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||
{
|
||||
selection.start = fold.range().start;
|
||||
selection.end = fold.range().end;
|
||||
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
|
||||
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
|
||||
} else {
|
||||
match snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row)) {
|
||||
Some(fold) => {
|
||||
selection.start = fold.range().start;
|
||||
selection.end = fold.range().end;
|
||||
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
|
||||
let chars = 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_bottom;
|
||||
if let Some(decorations) = assist.decorations.as_ref() {
|
||||
scroll_target_top = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
scroll_target_bottom = editor
|
||||
.row_for_block(decorations.end_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
} else {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start_row = assist
|
||||
.range
|
||||
.start
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row();
|
||||
scroll_target_top = start_row.0 as f32;
|
||||
scroll_target_bottom = scroll_target_top + 1.;
|
||||
match assist.decorations.as_ref() {
|
||||
Some(decorations) => {
|
||||
scroll_target_top = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
scroll_target_bottom = editor
|
||||
.row_for_block(decorations.end_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
}
|
||||
_ => {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start_row = assist
|
||||
.range
|
||||
.start
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.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_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) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match self.assists.get_mut(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match self.assists.get_mut(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
|
||||
@@ -2184,7 +2191,7 @@ impl PromptEditor {
|
||||
.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 token_counts = self.token_counts?;
|
||||
let max_token_count = model.max_token_count();
|
||||
@@ -2212,40 +2219,43 @@ impl PromptEditor {
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
);
|
||||
if let Some(workspace) = self.workspace.clone() {
|
||||
token_count = token_count
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Tokens Used ({} from the Assistant Panel)",
|
||||
humanize_token_count(token_counts.assistant_panel)
|
||||
),
|
||||
None,
|
||||
"Click to open the Assistant Panel",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.cursor_pointer()
|
||||
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(move |_, window, cx| {
|
||||
cx.stop_propagation();
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx)
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
} else {
|
||||
token_count = token_count
|
||||
.cursor_default()
|
||||
.tooltip(Tooltip::text("Tokens used"));
|
||||
match self.workspace.clone() {
|
||||
Some(workspace) => {
|
||||
token_count = token_count
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Tokens Used ({} from the Assistant Panel)",
|
||||
humanize_token_count(token_counts.assistant_panel)
|
||||
),
|
||||
None,
|
||||
"Click to open the Assistant Panel",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.cursor_pointer()
|
||||
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(move |_, window, cx| {
|
||||
cx.stop_propagation();
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx)
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
token_count = token_count
|
||||
.cursor_default()
|
||||
.tooltip(Tooltip::text("Tokens used"));
|
||||
}
|
||||
}
|
||||
|
||||
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 text_style = TextStyle {
|
||||
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(
|
||||
v_flex()
|
||||
.occlude()
|
||||
@@ -2430,10 +2440,11 @@ impl InlineAssist {
|
||||
InlineAssistant::update_global(cx, |this, cx| match event {
|
||||
CodegenEvent::Undone => this.finish_assist(assist_id, false, window, cx),
|
||||
CodegenEvent::Finished => {
|
||||
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match this.assists.get(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) {
|
||||
@@ -2865,27 +2876,28 @@ impl CodegenAlternative {
|
||||
assistant_panel_context: Option<LanguageModelRequest>,
|
||||
cx: &App,
|
||||
) -> BoxFuture<'static, Result<TokenCounts>> {
|
||||
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
|
||||
let request = self.build_request(user_prompt, assistant_panel_context.clone(), cx);
|
||||
match request {
|
||||
Ok(request) => {
|
||||
let total_count = model.count_tokens(request.clone(), cx);
|
||||
let assistant_panel_count = assistant_panel_context
|
||||
.map(|context| model.count_tokens(context, cx))
|
||||
.unwrap_or_else(|| future::ready(Ok(0)).boxed());
|
||||
match LanguageModelRegistry::read_global(cx).active_model() {
|
||||
Some(model) => {
|
||||
let request = self.build_request(user_prompt, assistant_panel_context.clone(), cx);
|
||||
match request {
|
||||
Ok(request) => {
|
||||
let total_count = model.count_tokens(request.clone(), cx);
|
||||
let assistant_panel_count = assistant_panel_context
|
||||
.map(|context| model.count_tokens(context, cx))
|
||||
.unwrap_or_else(|| future::ready(Ok(0)).boxed());
|
||||
|
||||
async move {
|
||||
Ok(TokenCounts {
|
||||
total: total_count.await?,
|
||||
assistant_panel: assistant_panel_count.await?,
|
||||
})
|
||||
async move {
|
||||
Ok(TokenCounts {
|
||||
total: total_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| {
|
||||
this.message_id = message_id;
|
||||
this.last_equal_ranges.clear();
|
||||
if let Err(error) = result {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
} else {
|
||||
this.status = CodegenStatus::Done;
|
||||
match result {
|
||||
Err(error) => {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
}
|
||||
_ => {
|
||||
this.status = CodegenStatus::Done;
|
||||
}
|
||||
}
|
||||
this.elapsed_time = Some(elapsed_time);
|
||||
this.completion = Some(completion.lock().clone());
|
||||
|
||||
@@ -184,10 +184,11 @@ impl TerminalInlineAssistant {
|
||||
}
|
||||
|
||||
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match self.assists.get_mut(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(user_prompt) = assist
|
||||
@@ -222,10 +223,11 @@ impl TerminalInlineAssistant {
|
||||
}
|
||||
|
||||
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match self.assists.get_mut(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
|
||||
@@ -436,10 +438,11 @@ impl TerminalInlineAssist {
|
||||
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
|
||||
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
|
||||
CodegenEvent::Finished => {
|
||||
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match this.assists.get(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let CodegenStatus::Error(error) = &codegen.read(cx).status {
|
||||
@@ -664,8 +667,8 @@ impl Render for PromptEditor {
|
||||
},
|
||||
gpui::Corner::TopRight,
|
||||
))
|
||||
.children(
|
||||
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
|
||||
.children(match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Error(error) => {
|
||||
let error_message = SharedString::from(error.to_string());
|
||||
Some(
|
||||
div()
|
||||
@@ -677,10 +680,9 @@ impl Render for PromptEditor {
|
||||
.color(Color::Error),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
}
|
||||
_ => None,
|
||||
}),
|
||||
)
|
||||
.child(div().flex_1().child(self.render_prompt_editor(cx)))
|
||||
.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 token_count = self.token_count?;
|
||||
let max_token_count = model.max_token_count();
|
||||
@@ -1007,37 +1009,40 @@ impl PromptEditor {
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
);
|
||||
if let Some(workspace) = self.workspace.clone() {
|
||||
token_count = token_count
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Tokens Used by Inline Assistant",
|
||||
None,
|
||||
"Click to Open Assistant Panel",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.cursor_pointer()
|
||||
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(move |_, window, cx| {
|
||||
cx.stop_propagation();
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx)
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
} else {
|
||||
token_count = token_count
|
||||
.cursor_default()
|
||||
.tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
|
||||
match self.workspace.clone() {
|
||||
Some(workspace) => {
|
||||
token_count = token_count
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::with_meta(
|
||||
"Tokens Used by Inline Assistant",
|
||||
None,
|
||||
"Click to Open Assistant Panel",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.cursor_pointer()
|
||||
.on_mouse_down(gpui::MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(move |_, window, cx| {
|
||||
cx.stop_propagation();
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx)
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
token_count = token_count
|
||||
.cursor_default()
|
||||
.tooltip(Tooltip::text("Tokens Used by Inline Assistant"));
|
||||
}
|
||||
}
|
||||
|
||||
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 text_style = TextStyle {
|
||||
color: if self.editor.read(cx).read_only(cx) {
|
||||
@@ -1217,10 +1222,13 @@ impl Codegen {
|
||||
let result = generate.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
if let Err(error) = result {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
} else {
|
||||
this.status = CodegenStatus::Done;
|
||||
match result {
|
||||
Err(error) => {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
}
|
||||
_ => {
|
||||
this.status = CodegenStatus::Done;
|
||||
}
|
||||
}
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
|
||||
@@ -77,34 +77,44 @@ impl RenderedMessage {
|
||||
}
|
||||
|
||||
fn append_thinking(&mut self, text: &String, window: &Window, cx: &mut App) {
|
||||
if let Some(RenderedMessageSegment::Thinking {
|
||||
content,
|
||||
scroll_handle,
|
||||
}) = self.segments.last_mut()
|
||||
{
|
||||
content.update(cx, |markdown, cx| {
|
||||
markdown.append(text, cx);
|
||||
});
|
||||
scroll_handle.scroll_to_bottom();
|
||||
} else {
|
||||
self.segments.push(RenderedMessageSegment::Thinking {
|
||||
content: render_markdown(text.into(), self.language_registry.clone(), window, cx),
|
||||
scroll_handle: ScrollHandle::default(),
|
||||
});
|
||||
match self.segments.last_mut() {
|
||||
Some(RenderedMessageSegment::Thinking {
|
||||
content,
|
||||
scroll_handle,
|
||||
}) => {
|
||||
content.update(cx, |markdown, cx| {
|
||||
markdown.append(text, cx);
|
||||
});
|
||||
scroll_handle.scroll_to_bottom();
|
||||
}
|
||||
_ => {
|
||||
self.segments.push(RenderedMessageSegment::Thinking {
|
||||
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) {
|
||||
if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() {
|
||||
markdown.update(cx, |markdown, cx| markdown.append(text, cx));
|
||||
} else {
|
||||
self.segments
|
||||
.push(RenderedMessageSegment::Text(render_markdown(
|
||||
SharedString::from(text),
|
||||
self.language_registry.clone(),
|
||||
window,
|
||||
cx,
|
||||
)));
|
||||
match self.segments.last_mut() {
|
||||
Some(RenderedMessageSegment::Text(markdown)) => {
|
||||
markdown.update(cx, |markdown, cx| markdown.append(text, cx));
|
||||
}
|
||||
_ => {
|
||||
self.segments
|
||||
.push(RenderedMessageSegment::Text(render_markdown(
|
||||
SharedString::from(text),
|
||||
self.language_registry.clone(),
|
||||
window,
|
||||
cx,
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -929,21 +939,18 @@ impl ActiveThread {
|
||||
let message_content =
|
||||
v_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
if let Some(edit_message_editor) = edit_message_editor.clone() {
|
||||
div()
|
||||
.key_context("EditMessageEditor")
|
||||
.on_action(cx.listener(Self::cancel_editing_message))
|
||||
.on_action(cx.listener(Self::confirm_editing_message))
|
||||
.min_h_6()
|
||||
.child(edit_message_editor)
|
||||
} else {
|
||||
div()
|
||||
.min_h_6()
|
||||
.text_ui(cx)
|
||||
.child(self.render_message_content(message_id, rendered_message, cx))
|
||||
},
|
||||
)
|
||||
.child(match edit_message_editor.clone() {
|
||||
Some(edit_message_editor) => div()
|
||||
.key_context("EditMessageEditor")
|
||||
.on_action(cx.listener(Self::cancel_editing_message))
|
||||
.on_action(cx.listener(Self::confirm_editing_message))
|
||||
.min_h_6()
|
||||
.child(edit_message_editor),
|
||||
_ => div()
|
||||
.min_h_6()
|
||||
.text_ui(cx)
|
||||
.child(self.render_message_content(message_id, rendered_message, cx)),
|
||||
})
|
||||
.when_some(context, |parent, context| {
|
||||
if !context.is_empty() {
|
||||
parent.child(h_flex().flex_wrap().gap_1().children(
|
||||
@@ -1204,7 +1211,7 @@ impl ActiveThread {
|
||||
message_id: MessageId,
|
||||
rendered_message: &RenderedMessage,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let pending_thinking_segment_index = rendered_message
|
||||
.segments
|
||||
.iter()
|
||||
@@ -1259,7 +1266,7 @@ impl ActiveThread {
|
||||
scroll_handle: &ScrollHandle,
|
||||
pending: bool,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let is_open = self
|
||||
.expanded_thinking_segments
|
||||
.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
|
||||
.expanded_tool_uses
|
||||
.get(&tool_use.id)
|
||||
@@ -1822,7 +1833,7 @@ impl ActiveThread {
|
||||
fn render_confirmations<'a>(
|
||||
&'a mut self,
|
||||
cx: &'a mut Context<Self>,
|
||||
) -> impl Iterator<Item = AnyElement> + 'a {
|
||||
) -> impl Iterator<Item = AnyElement> + 'a + use<'a> {
|
||||
let thread = self.thread.read(cx);
|
||||
|
||||
thread
|
||||
|
||||
@@ -105,7 +105,7 @@ impl AssistantConfiguration {
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let provider_id = provider.id().0.clone();
|
||||
let provider_name = provider.name().0.clone();
|
||||
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 tools_by_source = self.tools.tools_by_source(cx);
|
||||
let empty = Vec::new();
|
||||
|
||||
@@ -318,7 +318,7 @@ impl ManageProfilesModal {
|
||||
mode: ChooseProfileMode,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
Navigable::new(
|
||||
div()
|
||||
.track_focus(&self.focus_handle(cx))
|
||||
@@ -418,7 +418,7 @@ impl ManageProfilesModal {
|
||||
mode: NewProfileMode,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
|
||||
let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
|
||||
@@ -448,7 +448,7 @@ impl ManageProfilesModal {
|
||||
mode: ViewProfileMode,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
|
||||
let profile_name = settings
|
||||
|
||||
@@ -48,16 +48,17 @@ impl AssistantDiff {
|
||||
.items_of_type::<AssistantDiff>(cx)
|
||||
.find(|diff| diff.read(cx).thread == thread)
|
||||
})?;
|
||||
if let Some(existing_diff) = existing_diff {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
match existing_diff {
|
||||
Some(existing_diff) => workspace.update(cx, |workspace, cx| {
|
||||
workspace.activate_item(&existing_diff, true, true, window, cx);
|
||||
})
|
||||
} else {
|
||||
let assistant_diff =
|
||||
cx.new(|cx| AssistantDiff::new(thread.clone(), workspace.clone(), window, cx));
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_center(Box::new(assistant_diff), window, cx);
|
||||
})
|
||||
}),
|
||||
_ => {
|
||||
let assistant_diff =
|
||||
cx.new(|cx| AssistantDiff::new(thread.clone(), workspace.clone(), window, cx));
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_center(Box::new(assistant_diff), window, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -584,20 +584,14 @@ impl Focusable for AssistantPanel {
|
||||
match self.active_view {
|
||||
ActiveView::Thread => self.message_editor.focus_handle(cx),
|
||||
ActiveView::History => self.history.focus_handle(cx),
|
||||
ActiveView::PromptEditor => {
|
||||
if let Some(context_editor) = self.context_editor.as_ref() {
|
||||
context_editor.focus_handle(cx)
|
||||
} else {
|
||||
cx.focus_handle()
|
||||
}
|
||||
}
|
||||
ActiveView::Configuration => {
|
||||
if let Some(configuration) = self.configuration.as_ref() {
|
||||
configuration.focus_handle(cx)
|
||||
} else {
|
||||
cx.focus_handle()
|
||||
}
|
||||
}
|
||||
ActiveView::PromptEditor => match self.context_editor.as_ref() {
|
||||
Some(context_editor) => context_editor.focus_handle(cx),
|
||||
_ => cx.focus_handle(),
|
||||
},
|
||||
ActiveView::Configuration => match self.configuration.as_ref() {
|
||||
Some(configuration) => configuration.focus_handle(cx),
|
||||
_ => cx.focus_handle(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -683,7 +677,11 @@ impl Panel for 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 focus_handle = self.focus_handle(cx);
|
||||
|
||||
@@ -825,7 +823,7 @@ impl AssistantPanel {
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let recent_history = self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.recent_entries(6, cx));
|
||||
|
||||
@@ -676,10 +676,13 @@ impl CodegenAlternative {
|
||||
.update(cx, |this, cx| {
|
||||
this.message_id = message_id;
|
||||
this.last_equal_ranges.clear();
|
||||
if let Err(error) = result {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
} else {
|
||||
this.status = CodegenStatus::Done;
|
||||
match result {
|
||||
Err(error) => {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
}
|
||||
_ => {
|
||||
this.status = CodegenStatus::Done;
|
||||
}
|
||||
}
|
||||
this.elapsed_time = Some(elapsed_time);
|
||||
this.completion = Some(completion.lock().clone());
|
||||
|
||||
@@ -581,15 +581,14 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
let line_start = Point::new(position.row, 0);
|
||||
let offset_to_line = buffer.point_to_offset(line_start);
|
||||
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||
if let Some(line) = lines.next() {
|
||||
MentionCompletion::try_parse(line, offset_to_line)
|
||||
match lines.next() {
|
||||
Some(line) => MentionCompletion::try_parse(line, offset_to_line)
|
||||
.map(|completion| {
|
||||
completion.source_range.start <= offset_to_line + position.column as usize
|
||||
&& completion.source_range.end >= offset_to_line + position.column as usize
|
||||
})
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
.unwrap_or(false),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -360,12 +360,15 @@ impl ContextStore {
|
||||
remove_if_exists: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(context_id) = self.includes_thread(&thread.read(cx).id()) {
|
||||
if remove_if_exists {
|
||||
self.remove_context(context_id);
|
||||
match self.includes_thread(&thread.read(cx).id()) {
|
||||
Some(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>,
|
||||
changed_buffers: &HashSet<Entity<Buffer>>,
|
||||
cx: &App,
|
||||
) -> impl Future<Output = Vec<ContextId>> {
|
||||
) -> impl Future<Output = Vec<ContextId>> + use<> {
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
for context in &context_store.read(cx).context {
|
||||
@@ -756,8 +759,8 @@ fn refresh_file_text(
|
||||
) -> Option<Task<()>> {
|
||||
let id = file_context.id;
|
||||
let task = refresh_context_buffer(&file_context.context_buffer, cx);
|
||||
if let Some(task) = task {
|
||||
Some(cx.spawn(async move |cx| {
|
||||
match task {
|
||||
Some(task) => Some(cx.spawn(async move |cx| {
|
||||
let context_buffer = task.await;
|
||||
context_store
|
||||
.update(cx, |context_store, _| {
|
||||
@@ -765,9 +768,8 @@ fn refresh_file_text(
|
||||
context_store.replace_context(AssistantContext::File(new_file_context));
|
||||
})
|
||||
.ok();
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
})),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,14 +782,15 @@ fn refresh_directory_text(
|
||||
let futures = directory_context
|
||||
.context_buffers
|
||||
.iter()
|
||||
.map(|context_buffer| {
|
||||
if let Some(refresh_task) = refresh_context_buffer(context_buffer, cx) {
|
||||
stale = true;
|
||||
future::Either::Left(refresh_task)
|
||||
} else {
|
||||
future::Either::Right(future::ready((*context_buffer).clone()))
|
||||
}
|
||||
})
|
||||
.map(
|
||||
|context_buffer| match refresh_context_buffer(context_buffer, cx) {
|
||||
Some(refresh_task) => {
|
||||
stale = true;
|
||||
future::Either::Left(refresh_task)
|
||||
}
|
||||
_ => future::Either::Right(future::ready((*context_buffer).clone())),
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !stale {
|
||||
@@ -816,8 +819,8 @@ fn refresh_symbol_text(
|
||||
) -> Option<Task<()>> {
|
||||
let id = symbol_context.id;
|
||||
let task = refresh_context_symbol(&symbol_context.context_symbol, cx);
|
||||
if let Some(task) = task {
|
||||
Some(cx.spawn(async move |cx| {
|
||||
match task {
|
||||
Some(task) => Some(cx.spawn(async move |cx| {
|
||||
let context_symbol = task.await;
|
||||
context_store
|
||||
.update(cx, |context_store, _| {
|
||||
@@ -825,9 +828,8 @@ fn refresh_symbol_text(
|
||||
context_store.replace_context(AssistantContext::Symbol(new_symbol_context));
|
||||
})
|
||||
.ok();
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
})),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,7 +857,7 @@ fn refresh_thread_text(
|
||||
fn refresh_context_buffer(
|
||||
context_buffer: &ContextBuffer,
|
||||
cx: &App,
|
||||
) -> Option<impl Future<Output = ContextBuffer>> {
|
||||
) -> Option<impl Future<Output = ContextBuffer> + use<>> {
|
||||
let buffer = context_buffer.buffer.read(cx);
|
||||
let path = buffer_path_log_err(buffer)?;
|
||||
if buffer.version.changed_since(&context_buffer.version) {
|
||||
@@ -875,7 +877,7 @@ fn refresh_context_buffer(
|
||||
fn refresh_context_symbol(
|
||||
context_symbol: &ContextSymbol,
|
||||
cx: &App,
|
||||
) -> Option<impl Future<Output = ContextSymbol>> {
|
||||
) -> Option<impl Future<Output = ContextSymbol> + use<>> {
|
||||
let buffer = context_symbol.buffer.read(cx);
|
||||
let path = buffer_path_log_err(buffer)?;
|
||||
let project_path = buffer.project_path(cx)?;
|
||||
|
||||
@@ -341,33 +341,35 @@ impl InlineAssistant {
|
||||
selection.end.column = snapshot
|
||||
.buffer_snapshot
|
||||
.line_len(MultiBufferRow(selection.end.row));
|
||||
} else if let Some(fold) =
|
||||
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||
{
|
||||
selection.start = fold.range().start;
|
||||
selection.end = fold.range().end;
|
||||
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
|
||||
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
|
||||
} else {
|
||||
match snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row)) {
|
||||
Some(fold) => {
|
||||
selection.start = fold.range().start;
|
||||
selection.end = fold.range().end;
|
||||
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
|
||||
let chars = 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_bottom;
|
||||
if let Some(decorations) = assist.decorations.as_ref() {
|
||||
scroll_target_top = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
scroll_target_bottom = editor
|
||||
.row_for_block(decorations.end_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
} else {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start_row = assist
|
||||
.range
|
||||
.start
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.row();
|
||||
scroll_target_top = start_row.0 as f32;
|
||||
scroll_target_bottom = scroll_target_top + 1.;
|
||||
match assist.decorations.as_ref() {
|
||||
Some(decorations) => {
|
||||
scroll_target_top = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
scroll_target_bottom = editor
|
||||
.row_for_block(decorations.end_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
}
|
||||
_ => {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start_row = assist
|
||||
.range
|
||||
.start
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
.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_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) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match self.assists.get_mut(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match self.assists.get_mut(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
|
||||
@@ -1449,20 +1456,27 @@ impl InlineAssistant {
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(context_editor) = context_editor {
|
||||
Some(InlineAssistTarget::Editor(context_editor))
|
||||
} else if let Some(workspace_editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
{
|
||||
Some(InlineAssistTarget::Editor(workspace_editor))
|
||||
} else if let Some(terminal_view) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<TerminalView>(cx))
|
||||
{
|
||||
Some(InlineAssistTarget::Terminal(terminal_view))
|
||||
} else {
|
||||
None
|
||||
match context_editor {
|
||||
Some(context_editor) => Some(InlineAssistTarget::Editor(context_editor)),
|
||||
_ => {
|
||||
match workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
{
|
||||
Some(workspace_editor) => Some(InlineAssistTarget::Editor(workspace_editor)),
|
||||
_ => {
|
||||
match workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<TerminalView>(cx))
|
||||
{
|
||||
Some(terminal_view) => {
|
||||
Some(InlineAssistTarget::Terminal(terminal_view))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1654,10 +1668,11 @@ impl InlineAssist {
|
||||
InlineAssistant::update_global(cx, |this, cx| match event {
|
||||
CodegenEvent::Undone => this.finish_assist(assist_id, false, window, cx),
|
||||
CodegenEvent::Finished => {
|
||||
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match this.assists.get(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let CodegenStatus::Error(error) = codegen.read(cx).status(cx) {
|
||||
|
||||
@@ -677,7 +677,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||
.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(
|
||||
v_flex()
|
||||
.occlude()
|
||||
|
||||
@@ -117,10 +117,13 @@ impl TerminalCodegen {
|
||||
let result = generate.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
if let Err(error) = result {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
} else {
|
||||
this.status = CodegenStatus::Done;
|
||||
match result {
|
||||
Err(error) => {
|
||||
this.status = CodegenStatus::Error(error);
|
||||
}
|
||||
_ => {
|
||||
this.status = CodegenStatus::Done;
|
||||
}
|
||||
}
|
||||
cx.emit(CodegenEvent::Finished);
|
||||
cx.notify();
|
||||
|
||||
@@ -164,10 +164,11 @@ impl TerminalInlineAssistant {
|
||||
}
|
||||
|
||||
fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match self.assists.get_mut(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(user_prompt) = assist
|
||||
@@ -202,10 +203,11 @@ impl TerminalInlineAssistant {
|
||||
}
|
||||
|
||||
fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut App) {
|
||||
let assist = if let Some(assist) = self.assists.get_mut(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match self.assists.get_mut(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
assist.codegen.update(cx, |codegen, cx| codegen.stop(cx));
|
||||
@@ -402,10 +404,11 @@ impl TerminalInlineAssist {
|
||||
window.subscribe(&codegen, cx, move |codegen, event, window, cx| {
|
||||
TerminalInlineAssistant::update_global(cx, |this, cx| match event {
|
||||
CodegenEvent::Finished => {
|
||||
let assist = if let Some(assist) = this.assists.get(&assist_id) {
|
||||
assist
|
||||
} else {
|
||||
return;
|
||||
let assist = match this.assists.get(&assist_id) {
|
||||
Some(assist) => assist,
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let CodegenStatus::Error(error) = &codegen.read(cx).status {
|
||||
|
||||
@@ -379,14 +379,17 @@ impl Thread {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let result = restore.await;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Err(err) = result.as_ref() {
|
||||
this.last_restore_checkpoint = Some(LastRestoreCheckpoint::Error {
|
||||
message_id: checkpoint.message_id,
|
||||
error: err.to_string(),
|
||||
});
|
||||
} else {
|
||||
this.truncate(checkpoint.message_id, cx);
|
||||
this.last_restore_checkpoint = None;
|
||||
match result.as_ref() {
|
||||
Err(err) => {
|
||||
this.last_restore_checkpoint = Some(LastRestoreCheckpoint::Error {
|
||||
message_id: checkpoint.message_id,
|
||||
error: err.to_string(),
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
this.truncate(checkpoint.message_id, cx);
|
||||
this.last_restore_checkpoint = None;
|
||||
}
|
||||
}
|
||||
this.pending_checkpoint = None;
|
||||
cx.emit(ThreadEvent::CheckpointChanged);
|
||||
@@ -721,8 +724,8 @@ impl Thread {
|
||||
})
|
||||
.next();
|
||||
|
||||
if let Some((rel_rules_path, abs_rules_path)) = selected_rules_file {
|
||||
cx.spawn(async move |_| {
|
||||
match selected_rules_file {
|
||||
Some((rel_rules_path, abs_rules_path)) => cx.spawn(async move |_| {
|
||||
let rules_file_result = maybe!(async move {
|
||||
let abs_rules_path = abs_rules_path?;
|
||||
let text = fs.load(&abs_rules_path).await.with_context(|| {
|
||||
@@ -751,16 +754,15 @@ impl Thread {
|
||||
rules_file,
|
||||
};
|
||||
(worktree_info, rules_file_error)
|
||||
})
|
||||
} else {
|
||||
Task::ready((
|
||||
}),
|
||||
_ => Task::ready((
|
||||
WorktreeInfoForSystemPrompt {
|
||||
root_name,
|
||||
abs_path,
|
||||
rules_file: None,
|
||||
},
|
||||
None,
|
||||
))
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1186,7 +1188,7 @@ impl Thread {
|
||||
pub fn use_pending_tools(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoIterator<Item = PendingToolUse> {
|
||||
) -> impl IntoIterator<Item = PendingToolUse> + use<> {
|
||||
let request = self.to_completion_request(RequestKind::Chat, cx);
|
||||
let messages = Arc::new(request.messages);
|
||||
let pending_tool_uses = self
|
||||
@@ -1198,37 +1200,43 @@ impl Thread {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for tool_use in pending_tool_uses.iter() {
|
||||
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
||||
if tool.needs_confirmation()
|
||||
&& !AssistantSettings::get_global(cx).always_allow_tool_actions
|
||||
{
|
||||
self.tool_use.confirm_tool_use(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
tool_use.input.clone(),
|
||||
messages.clone(),
|
||||
tool,
|
||||
);
|
||||
cx.emit(ThreadEvent::ToolConfirmationNeeded);
|
||||
} else {
|
||||
self.run_tool(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
tool_use.input.clone(),
|
||||
&messages,
|
||||
tool,
|
||||
cx,
|
||||
);
|
||||
match self.tools.tool(&tool_use.name, cx) {
|
||||
Some(tool) => {
|
||||
if tool.needs_confirmation()
|
||||
&& !AssistantSettings::get_global(cx).always_allow_tool_actions
|
||||
{
|
||||
self.tool_use.confirm_tool_use(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
tool_use.input.clone(),
|
||||
messages.clone(),
|
||||
tool,
|
||||
);
|
||||
cx.emit(ThreadEvent::ToolConfirmationNeeded);
|
||||
} else {
|
||||
self.run_tool(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
tool_use.input.clone(),
|
||||
&messages,
|
||||
tool,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
||||
self.run_tool(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
tool_use.input.clone(),
|
||||
&messages,
|
||||
tool,
|
||||
cx,
|
||||
);
|
||||
_ => match self.tools.tool(&tool_use.name, cx) {
|
||||
Some(tool) => {
|
||||
self.run_tool(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
tool_use.input.clone(),
|
||||
&messages,
|
||||
tool,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,8 +166,8 @@ impl ToolUseState {
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(pending_tool_use) = self.pending_tool_uses_by_id.get(&tool_use.id) {
|
||||
match pending_tool_use.status {
|
||||
match self.pending_tool_uses_by_id.get(&tool_use.id) {
|
||||
Some(pending_tool_use) => match pending_tool_use.status {
|
||||
PendingToolUseStatus::Idle => ToolUseStatus::Pending,
|
||||
PendingToolUseStatus::NeedsConfirmation { .. } => {
|
||||
ToolUseStatus::NeedsConfirmation
|
||||
@@ -176,17 +176,14 @@ impl ToolUseState {
|
||||
PendingToolUseStatus::Error(ref err) => {
|
||||
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)
|
||||
{
|
||||
(tool.icon(), tool.needs_confirmation())
|
||||
} else {
|
||||
(IconName::Cog, false)
|
||||
let (icon, needs_confirmation) = match self.tools.tool(&tool_use.name, cx) {
|
||||
Some(tool) => (tool.icon(), tool.needs_confirmation()),
|
||||
_ => (IconName::Cog, false),
|
||||
};
|
||||
|
||||
tool_uses.push(ToolUse {
|
||||
@@ -209,10 +206,9 @@ impl ToolUseState {
|
||||
input: &serde_json::Value,
|
||||
cx: &App,
|
||||
) -> SharedString {
|
||||
if let Some(tool) = self.tools.tool(tool_name, cx) {
|
||||
tool.ui_text(input).into()
|
||||
} else {
|
||||
format!("Unknown tool {tool_name:?}").into()
|
||||
match self.tools.tool(tool_name, cx) {
|
||||
Some(tool) => tool.ui_text(input).into(),
|
||||
_ => format!("Unknown tool {tool_name:?}").into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1877,15 +1877,18 @@ impl AssistantContext {
|
||||
|
||||
if let Some(mut pending_patch) = pending_patch {
|
||||
let patch_start = pending_patch.range.start.to_offset(buffer);
|
||||
if let Some(message) = self.message_for_offset(patch_start, cx) {
|
||||
if message.anchor_range.end == text::Anchor::MAX {
|
||||
pending_patch.range.end = text::Anchor::MAX;
|
||||
} else {
|
||||
let message_end = buffer.anchor_after(message.offset_range.end - 1);
|
||||
pending_patch.range.end = message_end;
|
||||
match self.message_for_offset(patch_start, cx) {
|
||||
Some(message) => {
|
||||
if message.anchor_range.end == text::Anchor::MAX {
|
||||
pending_patch.range.end = text::Anchor::MAX;
|
||||
} else {
|
||||
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);
|
||||
@@ -2453,7 +2456,7 @@ impl AssistantContext {
|
||||
let result = stream_completion.await;
|
||||
|
||||
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>() {
|
||||
cx.emit(ContextEvent::ShowPaymentRequiredError);
|
||||
this.update_metadata(assistant_message_id, cx, |metadata| {
|
||||
@@ -2481,12 +2484,12 @@ impl AssistantContext {
|
||||
});
|
||||
Some(error_message)
|
||||
}
|
||||
} else {
|
||||
} _ => {
|
||||
this.update_metadata(assistant_message_id, cx, |metadata| {
|
||||
metadata.status = MessageStatus::Done;
|
||||
});
|
||||
None
|
||||
};
|
||||
}};
|
||||
|
||||
let language_name = this
|
||||
.buffer
|
||||
@@ -2631,15 +2634,16 @@ impl AssistantContext {
|
||||
}
|
||||
|
||||
pub fn cancel_last_assist(&mut self, cx: &mut Context<Self>) -> bool {
|
||||
if let Some(pending_completion) = self.pending_completions.pop() {
|
||||
self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| {
|
||||
if metadata.status == MessageStatus::Pending {
|
||||
metadata.status = MessageStatus::Canceled;
|
||||
}
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
match self.pending_completions.pop() {
|
||||
Some(pending_completion) => {
|
||||
self.update_metadata(pending_completion.assistant_message_id, cx, |metadata| {
|
||||
if metadata.status == MessageStatus::Pending {
|
||||
metadata.status = MessageStatus::Canceled;
|
||||
}
|
||||
});
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2799,121 +2803,123 @@ impl AssistantContext {
|
||||
) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
|
||||
let start_message = self.message_for_offset(range.start, cx);
|
||||
let end_message = self.message_for_offset(range.end, cx);
|
||||
if let Some((start_message, end_message)) = start_message.zip(end_message) {
|
||||
// Prevent splitting when range spans multiple messages.
|
||||
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);
|
||||
match start_message.zip(end_message) {
|
||||
Some((start_message, end_message)) => {
|
||||
// Prevent splitting when range spans multiple messages.
|
||||
if start_message.id != end_message.id {
|
||||
return (None, None);
|
||||
}
|
||||
}
|
||||
|
||||
let version = self.version.clone();
|
||||
let suffix = if let Some(suffix_start) = suffix_start {
|
||||
MessageAnchor {
|
||||
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 message = start_message;
|
||||
let role = message.role;
|
||||
let mut edited_buffer = false;
|
||||
|
||||
let suffix_metadata = MessageMetadata {
|
||||
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 mut suffix_start = None;
|
||||
|
||||
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
|
||||
// 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')
|
||||
{
|
||||
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);
|
||||
}
|
||||
suffix_start = Some(range.end);
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
let version = self.version.clone();
|
||||
let suffix = if let Some(suffix_start) = suffix_start {
|
||||
MessageAnchor {
|
||||
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),
|
||||
}
|
||||
};
|
||||
|
||||
if !edited_buffer {
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
let suffix_metadata = MessageMetadata {
|
||||
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
|
||||
} else {
|
||||
(None, None)
|
||||
_ => (None, None),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -403,23 +403,31 @@ impl ContextEditor {
|
||||
if request_type == RequestType::SuggestEdits && !self.context.read(cx).contains_files(cx) {
|
||||
self.last_error = Some(AssistError::FileRequired);
|
||||
cx.notify();
|
||||
} else if let Some(user_message) = self
|
||||
.context
|
||||
.update(cx, |context, cx| context.assist(request_type, cx))
|
||||
{
|
||||
let new_selection = {
|
||||
let cursor = user_message
|
||||
.start
|
||||
.to_offset(self.context.read(cx).buffer().read(cx));
|
||||
cursor..cursor
|
||||
};
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::fit()), window, 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);
|
||||
} else {
|
||||
match self
|
||||
.context
|
||||
.update(cx, |context, cx| context.assist(request_type, cx))
|
||||
{
|
||||
Some(user_message) => {
|
||||
let new_selection = {
|
||||
let cursor = user_message
|
||||
.start
|
||||
.to_offset(self.context.read(cx).buffer().read(cx));
|
||||
cursor..cursor
|
||||
};
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(
|
||||
Some(Autoscroll::fit()),
|
||||
window,
|
||||
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();
|
||||
@@ -817,62 +825,68 @@ impl ContextEditor {
|
||||
}
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if let Some(invoked_slash_command) =
|
||||
self.context.read(cx).invoked_slash_command(&command_id)
|
||||
{
|
||||
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
|
||||
buffer.as_singleton().unwrap();
|
||||
match self.context.read(cx).invoked_slash_command(&command_id) {
|
||||
Some(invoked_slash_command) => match invoked_slash_command.status {
|
||||
InvokedSlashCommandStatus::Finished => {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
|
||||
buffer.as_singleton().unwrap();
|
||||
|
||||
let start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
|
||||
.unwrap();
|
||||
let end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
|
||||
.unwrap();
|
||||
editor.remove_folds_with_type(
|
||||
&[start..end],
|
||||
TypeId::of::<PendingSlashCommand>(),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
let start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
|
||||
.unwrap();
|
||||
let end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
|
||||
.unwrap();
|
||||
editor.remove_folds_with_type(
|
||||
&[start..end],
|
||||
TypeId::of::<PendingSlashCommand>(),
|
||||
false,
|
||||
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(
|
||||
HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
|
||||
cx,
|
||||
);
|
||||
} else 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()
|
||||
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;
|
||||
if let Some(state) = self.patches.get_mut(&range) {
|
||||
if let Some(editor_state) = &state.editor {
|
||||
if editor_state.opened_patch != patch {
|
||||
state.update_task = Some({
|
||||
let this = this.clone();
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
Self::update_patch_editor(this.clone(), patch, cx)
|
||||
.await
|
||||
.log_err();
|
||||
})
|
||||
});
|
||||
match self.patches.get_mut(&range) {
|
||||
Some(state) => {
|
||||
if let Some(editor_state) = &state.editor {
|
||||
if editor_state.opened_patch != patch {
|
||||
state.update_task = Some({
|
||||
let this = this.clone();
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
Self::update_patch_editor(this.clone(), patch, cx)
|
||||
.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 =
|
||||
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;
|
||||
should_refold = true;
|
||||
}
|
||||
}
|
||||
|
||||
if should_refold {
|
||||
@@ -1175,16 +1192,20 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(editor) = editor {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.activate_item(&editor, true, false, window, cx);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
patch_state.update_task = Some(cx.spawn_in(window, async move |this, cx| {
|
||||
Self::open_patch_editor(this, new_patch, cx).await.log_err();
|
||||
}));
|
||||
match editor {
|
||||
Some(editor) => {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.activate_item(&editor, true, false, window, cx);
|
||||
})
|
||||
.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();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1282,16 +1303,19 @@ impl ContextEditor {
|
||||
.collect();
|
||||
|
||||
if let Some(state) = &mut patch_state.editor {
|
||||
if let Some(editor) = state.editor.upgrade() {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_title(patch.title.clone(), cx);
|
||||
editor.reset_locations(locations, window, cx);
|
||||
resolved_patch.apply(editor, cx);
|
||||
});
|
||||
match state.editor.upgrade() {
|
||||
Some(editor) => {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_title(patch.title.clone(), cx);
|
||||
editor.reset_locations(locations, window, cx);
|
||||
resolved_patch.apply(editor, cx);
|
||||
});
|
||||
|
||||
state.opened_patch = patch;
|
||||
} else {
|
||||
patch_state.editor.take();
|
||||
state.opened_patch = patch;
|
||||
}
|
||||
_ => {
|
||||
patch_state.editor.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
patch_state.update_task.take();
|
||||
@@ -1573,25 +1597,28 @@ impl ContextEditor {
|
||||
let mut new_blocks = vec![];
|
||||
let mut block_index_to_message = vec![];
|
||||
for message in self.context.read(cx).messages(cx) {
|
||||
if let Some(_) = blocks_to_remove.remove(&message.id) {
|
||||
// This is an old message that we might modify.
|
||||
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
|
||||
debug_assert!(
|
||||
false,
|
||||
"old_blocks should contain a message_id we've just removed."
|
||||
);
|
||||
continue;
|
||||
};
|
||||
// Should we modify it?
|
||||
let message_meta = MessageMetadata::from(&message);
|
||||
if meta != &message_meta {
|
||||
blocks_to_replace.insert(*block_id, render_block(message_meta.clone()));
|
||||
*meta = message_meta;
|
||||
match blocks_to_remove.remove(&message.id) {
|
||||
Some(_) => {
|
||||
// This is an old message that we might modify.
|
||||
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
|
||||
debug_assert!(
|
||||
false,
|
||||
"old_blocks should contain a message_id we've just removed."
|
||||
);
|
||||
continue;
|
||||
};
|
||||
// Should we modify it?
|
||||
let message_meta = MessageMetadata::from(&message);
|
||||
if 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);
|
||||
@@ -2321,54 +2348,67 @@ impl ContextEditor {
|
||||
)
|
||||
.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 {
|
||||
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 (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 (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(
|
||||
self.slash_commands.clone(),
|
||||
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 focus_handle = self.editor().focus_handle(cx).clone();
|
||||
let model_name = match active_model {
|
||||
@@ -3283,12 +3327,9 @@ impl FollowableItem for ContextEditor {
|
||||
Some(proto::view::Variant::ContextEditor(
|
||||
proto::view::ContextEditor {
|
||||
context_id: context.id().to_proto(),
|
||||
editor: if let Some(proto::view::Variant::Editor(proto)) =
|
||||
self.editor.read(cx).to_state_proto(window, cx)
|
||||
{
|
||||
Some(proto)
|
||||
} else {
|
||||
None
|
||||
editor: match self.editor.read(cx).to_state_proto(window, cx) {
|
||||
Some(proto::view::Variant::Editor(proto)) => Some(proto),
|
||||
_ => None,
|
||||
},
|
||||
},
|
||||
))
|
||||
@@ -3413,7 +3454,7 @@ impl ContextEditorToolbarItem {
|
||||
pub fn render_remaining_tokens(
|
||||
context_editor: &Entity<ContextEditor>,
|
||||
cx: &App,
|
||||
) -> Option<impl IntoElement> {
|
||||
) -> Option<impl IntoElement + use<>> {
|
||||
let context = &context_editor.read(cx).context;
|
||||
|
||||
let (token_count_color, token_count, max_token_count, tooltip) = match token_state(context, cx)?
|
||||
|
||||
@@ -299,13 +299,12 @@ impl ContextStore {
|
||||
}
|
||||
|
||||
if is_shared {
|
||||
self.contexts.retain_mut(|context| {
|
||||
if let Some(strong_context) = context.upgrade() {
|
||||
self.contexts.retain_mut(|context| match context.upgrade() {
|
||||
Some(strong_context) => {
|
||||
*context = ContextHandle::Strong(strong_context);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
let remote_id = self.project.read(cx).remote_id().unwrap();
|
||||
self.client_subscription = self
|
||||
@@ -336,8 +335,8 @@ impl ContextStore {
|
||||
self.synchronize_contexts(cx);
|
||||
}
|
||||
project::Event::DisconnectedFromHost => {
|
||||
self.contexts.retain_mut(|context| {
|
||||
if let Some(strong_context) = context.upgrade() {
|
||||
self.contexts.retain_mut(|context| match context.upgrade() {
|
||||
Some(strong_context) => {
|
||||
*context = ContextHandle::Weak(context.downgrade());
|
||||
strong_context.update(cx, |context, cx| {
|
||||
if context.replica_id() != ReplicaId::default() {
|
||||
@@ -345,9 +344,8 @@ impl ContextStore {
|
||||
}
|
||||
});
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
self.host_contexts.clear();
|
||||
cx.notify();
|
||||
@@ -422,12 +420,13 @@ impl ContextStore {
|
||||
.await?;
|
||||
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
match this.loaded_context_for_id(&context_id, cx) {
|
||||
Some(existing_context) => existing_context,
|
||||
_ => {
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -471,11 +470,12 @@ impl ContextStore {
|
||||
)
|
||||
})?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, cx);
|
||||
context
|
||||
match this.loaded_context_for_path(&path, cx) {
|
||||
Some(existing_context) => existing_context,
|
||||
_ => {
|
||||
this.register_context(&context, cx);
|
||||
context
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -591,12 +591,13 @@ impl ContextStore {
|
||||
.await?;
|
||||
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||
existing_context
|
||||
} else {
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
match this.loaded_context_for_id(&context_id, cx) {
|
||||
Some(existing_context) => existing_context,
|
||||
_ => {
|
||||
this.register_context(&context, cx);
|
||||
this.synchronize_contexts(cx);
|
||||
context
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -151,29 +151,27 @@ impl SlashCommandCompletionProvider {
|
||||
let mut flag = self.cancel_flag.lock();
|
||||
flag.store(true, SeqCst);
|
||||
*flag = new_cancel_flag.clone();
|
||||
if let Some(command) = self.slash_commands.command(command_name, cx) {
|
||||
let completions = command.complete_argument(
|
||||
arguments,
|
||||
new_cancel_flag.clone(),
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let command_name: Arc<str> = command_name.into();
|
||||
let editor = self.editor.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let arguments = arguments.to_vec();
|
||||
cx.background_spawn(async move {
|
||||
Ok(Some(
|
||||
completions
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|new_argument| {
|
||||
let confirm =
|
||||
editor
|
||||
.clone()
|
||||
.zip(workspace.clone())
|
||||
.map(|(editor, workspace)| {
|
||||
match self.slash_commands.command(command_name, cx) {
|
||||
Some(command) => {
|
||||
let completions = command.complete_argument(
|
||||
arguments,
|
||||
new_cancel_flag.clone(),
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let command_name: Arc<str> = command_name.into();
|
||||
let editor = self.editor.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let arguments = arguments.to_vec();
|
||||
cx.background_spawn(async move {
|
||||
Ok(Some(
|
||||
completions
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|new_argument| {
|
||||
let confirm = editor.clone().zip(workspace.clone()).map(
|
||||
|(editor, workspace)| {
|
||||
Arc::new({
|
||||
let mut completed_arguments = arguments.clone();
|
||||
if new_argument.replace_previous_arguments {
|
||||
@@ -210,32 +208,33 @@ impl SlashCommandCompletionProvider {
|
||||
}
|
||||
}
|
||||
}) as Arc<_>
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
let mut new_text = new_argument.new_text.clone();
|
||||
if new_argument.after_completion == AfterCompletion::Continue {
|
||||
new_text.push(' ');
|
||||
}
|
||||
let mut new_text = new_argument.new_text.clone();
|
||||
if new_argument.after_completion == AfterCompletion::Continue {
|
||||
new_text.push(' ');
|
||||
}
|
||||
|
||||
project::Completion {
|
||||
old_range: if new_argument.replace_previous_arguments {
|
||||
argument_range.clone()
|
||||
} else {
|
||||
last_argument_range.clone()
|
||||
},
|
||||
label: new_argument.label,
|
||||
icon_path: None,
|
||||
new_text,
|
||||
documentation: None,
|
||||
confirm,
|
||||
source: CompletionSource::Custom,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
})
|
||||
} else {
|
||||
Task::ready(Ok(Some(Vec::new())))
|
||||
project::Completion {
|
||||
old_range: if new_argument.replace_previous_arguments {
|
||||
argument_range.clone()
|
||||
} else {
|
||||
last_argument_range.clone()
|
||||
},
|
||||
label: new_argument.label,
|
||||
icon_path: None,
|
||||
new_text,
|
||||
documentation: None,
|
||||
confirm,
|
||||
source: CompletionSource::Custom,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
})
|
||||
}
|
||||
_ => Task::ready(Ok(Some(Vec::new()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,10 +332,9 @@ impl CompletionProvider for SlashCommandCompletionProvider {
|
||||
let position = position.to_point(buffer);
|
||||
let line_start = Point::new(position.row, 0);
|
||||
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||
if let Some(line) = lines.next() {
|
||||
SlashCommandLine::parse(line).is_some()
|
||||
} else {
|
||||
false
|
||||
match lines.next() {
|
||||
Some(line) => SlashCommandLine::parse(line).is_some(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,8 +105,8 @@ impl JsonSchema for AssistantSettingsContent {
|
||||
VersionedAssistantSettingsContent::schema_name()
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> Schema {
|
||||
VersionedAssistantSettingsContent::json_schema(gen)
|
||||
fn json_schema(r#gen: &mut schemars::r#gen::SchemaGenerator) -> Schema {
|
||||
VersionedAssistantSettingsContent::json_schema(r#gen)
|
||||
}
|
||||
|
||||
fn is_referenceable() -> bool {
|
||||
@@ -416,7 +416,7 @@ pub struct LanguageModelSelection {
|
||||
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 {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
|
||||
@@ -88,8 +88,8 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
let server_id = self.server_id.clone();
|
||||
let prompt_name = self.prompt.name.clone();
|
||||
|
||||
if let Some(server) = self.server_manager.read(cx).get_server(&server_id) {
|
||||
cx.foreground_executor().spawn(async move {
|
||||
match self.server_manager.read(cx).get_server(&server_id) {
|
||||
Some(server) => cx.foreground_executor().spawn(async move {
|
||||
let Some(protocol) = server.client() else {
|
||||
return Err(anyhow!("Context server not initialized"));
|
||||
};
|
||||
@@ -118,9 +118,8 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
})
|
||||
.collect();
|
||||
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);
|
||||
if let Some(server) = manager.get_server(&server_id) {
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let Some(protocol) = server.client() else {
|
||||
return Err(anyhow!("Context server not initialized"));
|
||||
};
|
||||
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
|
||||
match manager.get_server(&server_id) {
|
||||
Some(server) => {
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let Some(protocol) = server.client() else {
|
||||
return Err(anyhow!("Context server not initialized"));
|
||||
};
|
||||
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
|
||||
|
||||
// Check that there are only user roles
|
||||
if result
|
||||
.messages
|
||||
.iter()
|
||||
.any(|msg| !matches!(msg.role, context_server::types::Role::User))
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"Prompt contains non-user roles, which is not supported"
|
||||
));
|
||||
}
|
||||
// Check that there are only user roles
|
||||
if result
|
||||
.messages
|
||||
.iter()
|
||||
.any(|msg| !matches!(msg.role, context_server::types::Role::User))
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"Prompt contains non-user roles, which is not supported"
|
||||
));
|
||||
}
|
||||
|
||||
// Extract text from user messages into a single prompt string
|
||||
let mut prompt = result
|
||||
.messages
|
||||
.into_iter()
|
||||
.filter_map(|msg| match msg.content {
|
||||
context_server::types::MessageContent::Text { text, .. } => Some(text),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n\n");
|
||||
// Extract text from user messages into a single prompt string
|
||||
let mut prompt = result
|
||||
.messages
|
||||
.into_iter()
|
||||
.filter_map(|msg| match msg.content {
|
||||
context_server::types::MessageContent::Text { text, .. } => Some(text),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n\n");
|
||||
|
||||
// We must normalize the line endings here, since servers might return CR characters.
|
||||
LineEnding::normalize(&mut prompt);
|
||||
// We must normalize the line endings here, since servers might return CR characters.
|
||||
LineEnding::normalize(&mut prompt);
|
||||
|
||||
Ok(SlashCommandOutput {
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range: 0..(prompt.len()),
|
||||
icon: IconName::ZedAssistant,
|
||||
label: SharedString::from(
|
||||
result
|
||||
.description
|
||||
.unwrap_or(format!("Result from {}", prompt_name)),
|
||||
),
|
||||
metadata: None,
|
||||
}],
|
||||
text: prompt,
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.to_event_stream())
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("Context server not found")))
|
||||
Ok(SlashCommandOutput {
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range: 0..(prompt.len()),
|
||||
icon: IconName::ZedAssistant,
|
||||
label: SharedString::from(
|
||||
result
|
||||
.description
|
||||
.unwrap_or(format!("Result from {}", prompt_name)),
|
||||
),
|
||||
metadata: None,
|
||||
}],
|
||||
text: prompt,
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.to_event_stream())
|
||||
})
|
||||
}
|
||||
_ => Task::ready(Err(anyhow!("Context server not found"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,19 +229,20 @@ fn collect_diagnostics(
|
||||
options: Options,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Option<SlashCommandOutput>>> {
|
||||
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||
debug_assert_eq!(path_matcher.sources().len(), 1);
|
||||
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
||||
} else {
|
||||
None
|
||||
let error_source = match &options.path_matcher {
|
||||
Some(path_matcher) => {
|
||||
debug_assert_eq!(path_matcher.sources().len(), 1);
|
||||
Some(path_matcher.sources().first().cloned().unwrap_or_default())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let glob_is_exact_file_match = if let Some(path) = options
|
||||
let glob_is_exact_file_match = match options
|
||||
.path_matcher
|
||||
.as_ref()
|
||||
.and_then(|pm| pm.sources().first())
|
||||
{
|
||||
PathBuf::try_from(path)
|
||||
Some(path) => PathBuf::try_from(path)
|
||||
.ok()
|
||||
.and_then(|path| {
|
||||
project.read(cx).worktrees(cx).find_map(|worktree| {
|
||||
@@ -251,9 +252,8 @@ fn collect_diagnostics(
|
||||
worktree.absolutize(&relative_path).ok()
|
||||
})
|
||||
})
|
||||
.is_some()
|
||||
} else {
|
||||
false
|
||||
.is_some(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let project_handle = project.downgrade();
|
||||
|
||||
@@ -221,7 +221,7 @@ fn collect_files(
|
||||
project: Entity<Project>,
|
||||
glob_inputs: &[String],
|
||||
cx: &mut App,
|
||||
) -> impl Stream<Item = Result<SlashCommandEvent>> {
|
||||
) -> impl Stream<Item = Result<SlashCommandEvent>> + use<> {
|
||||
let Ok(matchers) = glob_inputs
|
||||
.into_iter()
|
||||
.map(|glob_input| {
|
||||
@@ -285,22 +285,25 @@ fn collect_files(
|
||||
if entry.is_dir() {
|
||||
// Auto-fold directories that contain no files
|
||||
let mut child_entries = snapshot.child_entries(&entry.path);
|
||||
if let Some(child) = child_entries.next() {
|
||||
if child_entries.next().is_none() && child.kind.is_dir() {
|
||||
if is_top_level_directory {
|
||||
is_top_level_directory = false;
|
||||
folded_directory_names_stack.push(
|
||||
path_including_worktree_name.to_string_lossy().to_string(),
|
||||
);
|
||||
} else {
|
||||
folded_directory_names_stack.push(filename.to_string());
|
||||
match child_entries.next() {
|
||||
Some(child) => {
|
||||
if child_entries.next().is_none() && child.kind.is_dir() {
|
||||
if is_top_level_directory {
|
||||
is_top_level_directory = false;
|
||||
folded_directory_names_stack.push(
|
||||
path_including_worktree_name.to_string_lossy().to_string(),
|
||||
);
|
||||
} else {
|
||||
folded_directory_names_stack.push(filename.to_string());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Skip empty directories
|
||||
folded_directory_names_stack.clear();
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Skip empty directories
|
||||
folded_directory_names_stack.clear();
|
||||
continue;
|
||||
}
|
||||
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
|
||||
if prefix_paths.is_empty() {
|
||||
|
||||
@@ -218,12 +218,9 @@ async fn project_symbols(
|
||||
|
||||
for symbol in symbols
|
||||
.iter()
|
||||
.filter(|symbol| {
|
||||
if let Some(regex) = ®ex {
|
||||
regex.is_match(&symbol.name)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
.filter(|symbol| match ®ex {
|
||||
Some(regex) => regex.is_match(&symbol.name),
|
||||
_ => true,
|
||||
})
|
||||
.skip(offset as usize)
|
||||
// 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();
|
||||
|
||||
let lang_name = lang.name();
|
||||
if let Some(lsp_adapter) = registry.lsp_adapters(&lang_name).first().cloned() {
|
||||
lsp_adapter
|
||||
match registry.lsp_adapters(&lang_name).first().cloned() {
|
||||
Some(lsp_adapter) => lsp_adapter
|
||||
.labels_for_symbols(&entries_for_labels, lang)
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
.ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
|
||||
@@ -63,16 +63,16 @@ impl Tool for DiagnosticsTool {
|
||||
}
|
||||
|
||||
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()
|
||||
.and_then(|input| match input.path {
|
||||
Some(path) if !path.is_empty() => Some(MarkdownString::inline_code(&path)),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
format!("Check diagnostics for {path}")
|
||||
} else {
|
||||
"Check project diagnostics".to_string()
|
||||
}) {
|
||||
Some(path) => {
|
||||
format!("Check diagnostics for {path}")
|
||||
}
|
||||
_ => "Check project diagnostics".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ impl Tool for FindReplaceFileTool {
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(diff) = result {
|
||||
match result { Some(diff) => {
|
||||
let edit_ids = buffer.update(cx, |buffer, cx| {
|
||||
buffer.finalize_last_transaction();
|
||||
buffer.apply_diff(diff, false, cx);
|
||||
@@ -205,7 +205,7 @@ impl Tool for FindReplaceFileTool {
|
||||
})?.await?;
|
||||
|
||||
Ok(format!("Edited {}", input.path.display()))
|
||||
} else {
|
||||
} _ => {
|
||||
let err = buffer.read_with(cx, |buffer, _cx| {
|
||||
let file_exists = buffer
|
||||
.file()
|
||||
@@ -224,7 +224,7 @@ impl Tool for FindReplaceFileTool {
|
||||
})?;
|
||||
|
||||
Err(err)
|
||||
}
|
||||
}}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl SoundRegistry {
|
||||
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) {
|
||||
return Ok(wav.clone());
|
||||
}
|
||||
|
||||
@@ -201,16 +201,19 @@ pub fn check(_: &Check, window: &mut Window, cx: &mut App) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(updater) = AutoUpdater::get(cx) {
|
||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||
} else {
|
||||
drop(window.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
"Could not check for updates",
|
||||
Some("Auto-updates disabled for non-bundled app."),
|
||||
&["Ok"],
|
||||
cx,
|
||||
));
|
||||
match AutoUpdater::get(cx) {
|
||||
Some(updater) => {
|
||||
updater.update(cx, |updater, cx| updater.poll(cx));
|
||||
}
|
||||
_ => {
|
||||
drop(window.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
"Could not check for updates",
|
||||
Some("Auto-updates disabled for non-bundled app."),
|
||||
&["Ok"],
|
||||
cx,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ impl Render for Breadcrumbs {
|
||||
}
|
||||
}
|
||||
})
|
||||
.tooltip(move |window, cx| {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
.tooltip(move |window, cx| match editor.upgrade() {
|
||||
Some(editor) => {
|
||||
let focus_handle = editor.read(cx).focus_handle(cx);
|
||||
Tooltip::for_action_in(
|
||||
"Show Symbol Outline",
|
||||
@@ -120,14 +120,13 @@ impl Render for Breadcrumbs {
|
||||
window,
|
||||
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
|
||||
|
||||
@@ -608,55 +608,58 @@ fn compute_hunks(
|
||||
) -> SumTree<InternalDiffHunk> {
|
||||
let mut tree = SumTree::new(&buffer);
|
||||
|
||||
if let Some((diff_base, diff_base_rope)) = diff_base {
|
||||
let buffer_text = buffer.as_rope().to_string();
|
||||
match diff_base {
|
||||
Some((diff_base, diff_base_rope)) => {
|
||||
let buffer_text = buffer.as_rope().to_string();
|
||||
|
||||
let mut options = GitOptions::default();
|
||||
options.context_lines(0);
|
||||
let patch = GitPatch::from_buffers(
|
||||
diff_base.as_bytes(),
|
||||
None,
|
||||
buffer_text.as_bytes(),
|
||||
None,
|
||||
Some(&mut options),
|
||||
)
|
||||
.log_err();
|
||||
let mut options = GitOptions::default();
|
||||
options.context_lines(0);
|
||||
let patch = GitPatch::from_buffers(
|
||||
diff_base.as_bytes(),
|
||||
None,
|
||||
buffer_text.as_bytes(),
|
||||
None,
|
||||
Some(&mut options),
|
||||
)
|
||||
.log_err();
|
||||
|
||||
// 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,
|
||||
// which is a bit odd.
|
||||
if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
|
||||
// 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,
|
||||
// which is a bit odd.
|
||||
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(
|
||||
InternalDiffHunk {
|
||||
buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
|
||||
diff_base_byte_range: 0..diff_base.len() - 1,
|
||||
buffer_range: Anchor::MIN..Anchor::MAX,
|
||||
diff_base_byte_range: 0..0,
|
||||
},
|
||||
&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
|
||||
@@ -776,7 +779,7 @@ impl BufferDiff {
|
||||
language: Option<Arc<Language>>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut App,
|
||||
) -> impl Future<Output = BufferDiffInner> {
|
||||
) -> impl Future<Output = BufferDiffInner> + use<> {
|
||||
let base_text_pair;
|
||||
let base_text_exists;
|
||||
let base_text_snapshot;
|
||||
@@ -818,7 +821,7 @@ impl BufferDiff {
|
||||
base_text: Option<Arc<String>>,
|
||||
base_text_snapshot: language::BufferSnapshot,
|
||||
cx: &App,
|
||||
) -> impl Future<Output = BufferDiffInner> {
|
||||
) -> impl Future<Output = BufferDiffInner> + use<> {
|
||||
let base_text_exists = base_text.is_some();
|
||||
let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone()));
|
||||
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 mut index_text = if rng.gen() {
|
||||
let mut index_text = if rng.r#gen() {
|
||||
Rope::from(head_text.as_str())
|
||||
} else {
|
||||
working_copy.as_rope().clone()
|
||||
|
||||
@@ -179,23 +179,21 @@ impl ActiveCall {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let room = if let Some(room) = self.room().cloned() {
|
||||
Some(Task::ready(Ok(room)).shared())
|
||||
} else {
|
||||
self.pending_room_creation.clone()
|
||||
let room = match self.room().cloned() {
|
||||
Some(room) => Some(Task::ready(Ok(room)).shared()),
|
||||
_ => self.pending_room_creation.clone(),
|
||||
};
|
||||
|
||||
let invite = if let Some(room) = room {
|
||||
cx.spawn(async move |_, cx| {
|
||||
let invite = match room {
|
||||
Some(room) => cx.spawn(async move |_, cx| {
|
||||
let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
|
||||
|
||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||
Some(
|
||||
let initial_project_id = match initial_project {
|
||||
Some(initial_project) => Some(
|
||||
room.update(cx, |room, cx| room.share_project(initial_project, cx))?
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
room.update(cx, move |room, cx| {
|
||||
@@ -204,41 +202,42 @@ impl ActiveCall {
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
} else {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
let room = cx
|
||||
.spawn(async move |this, cx| {
|
||||
let create_room = async {
|
||||
let room = cx
|
||||
.update(|cx| {
|
||||
Room::create(
|
||||
called_user_id,
|
||||
initial_project,
|
||||
client,
|
||||
user_store,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
}),
|
||||
_ => {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
let room = cx
|
||||
.spawn(async move |this, cx| {
|
||||
let create_room = async {
|
||||
let room = cx
|
||||
.update(|cx| {
|
||||
Room::create(
|
||||
called_user_id,
|
||||
initial_project,
|
||||
client,
|
||||
user_store,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.update(cx, |this, cx| this.set_room(Some(room.clone()), cx))?
|
||||
.await?;
|
||||
this.update(cx, |this, cx| this.set_room(Some(room.clone()), cx))?
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(room)
|
||||
};
|
||||
anyhow::Ok(room)
|
||||
};
|
||||
|
||||
let room = create_room.await;
|
||||
this.update(cx, |this, _| this.pending_room_creation = None)?;
|
||||
room.map_err(Arc::new)
|
||||
let room = create_room.await;
|
||||
this.update(cx, |this, _| this.pending_room_creation = None)?;
|
||||
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| {
|
||||
@@ -292,10 +291,11 @@ impl ActiveCall {
|
||||
return Task::ready(Err(anyhow!("cannot join while on another call")));
|
||||
}
|
||||
|
||||
let call = if let Some(call) = self.incoming_call.0.borrow_mut().take() {
|
||||
call
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("no incoming call")));
|
||||
let call = match self.incoming_call.0.borrow_mut().take() {
|
||||
Some(call) => call,
|
||||
_ => {
|
||||
return Task::ready(Err(anyhow!("no incoming call")));
|
||||
}
|
||||
};
|
||||
|
||||
if self.pending_room_creation.is_some() {
|
||||
@@ -373,11 +373,12 @@ impl ActiveCall {
|
||||
Audio::end_call(cx);
|
||||
|
||||
let channel_id = self.channel_id(cx);
|
||||
if let Some((room, _)) = self.room.take() {
|
||||
cx.emit(Event::RoomLeft { channel_id });
|
||||
room.update(cx, |room, cx| room.leave(cx))
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
match self.room.take() {
|
||||
Some((room, _)) => {
|
||||
cx.emit(Event::RoomLeft { channel_id });
|
||||
room.update(cx, |room, cx| room.leave(cx))
|
||||
}
|
||||
_ => Task::ready(Ok(())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,11 +387,12 @@ impl ActiveCall {
|
||||
project: Entity<Project>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("Project Shared", cx);
|
||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("no active call")))
|
||||
match self.room.as_ref() {
|
||||
Some((room, _)) => {
|
||||
self.report_call_event("Project Shared", cx);
|
||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
||||
}
|
||||
_ => Task::ready(Err(anyhow!("no active call"))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,11 +401,12 @@ impl ActiveCall {
|
||||
project: Entity<Project>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
self.report_call_event("Project Unshared", cx);
|
||||
room.update(cx, |room, cx| room.unshare_project(project, cx))
|
||||
} else {
|
||||
Err(anyhow!("no active call"))
|
||||
match self.room.as_ref() {
|
||||
Some((room, _)) => {
|
||||
self.report_call_event("Project Unshared", cx);
|
||||
room.update(cx, |room, cx| room.unshare_project(project, cx))
|
||||
}
|
||||
_ => Err(anyhow!("no active call")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,33 +433,36 @@ impl ActiveCall {
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
cx.notify();
|
||||
if let Some(room) = room {
|
||||
if room.read(cx).status().is_offline() {
|
||||
match room {
|
||||
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;
|
||||
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(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,10 +96,11 @@ impl Room {
|
||||
}
|
||||
|
||||
pub fn is_connected(&self, _: &App) -> bool {
|
||||
if let Some(live_kit) = self.live_kit.as_ref() {
|
||||
live_kit.room.connection_state() == livekit::ConnectionState::Connected
|
||||
} else {
|
||||
false
|
||||
match self.live_kit.as_ref() {
|
||||
Some(live_kit) => {
|
||||
live_kit.room.connection_state() == livekit::ConnectionState::Connected
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,15 +182,16 @@ impl Room {
|
||||
room
|
||||
})?;
|
||||
|
||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||
let initial_project_id = room
|
||||
.update(cx, |room, cx| {
|
||||
room.share_project(initial_project.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
Some(initial_project_id)
|
||||
} else {
|
||||
None
|
||||
let initial_project_id = match initial_project {
|
||||
Some(initial_project) => {
|
||||
let initial_project_id = room
|
||||
.update(cx, |room, cx| {
|
||||
room.share_project(initial_project.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
Some(initial_project_id)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
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 leave = self.leave_internal(cx);
|
||||
Some(cx.background_spawn(async move {
|
||||
@@ -665,7 +667,7 @@ impl Room {
|
||||
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();
|
||||
async move {
|
||||
while let Some(result) = done_rx.next().await {
|
||||
@@ -728,14 +730,14 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, cx| project.set_role(role, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
this.joined_projects
|
||||
.retain(|project| match project.upgrade() {
|
||||
Some(project) => {
|
||||
project.update(cx, |project, cx| project.set_role(role, cx));
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.local_participant.projects.clear();
|
||||
@@ -778,20 +780,18 @@ impl Room {
|
||||
}
|
||||
|
||||
for unshared_project_id in old_projects.difference(&new_projects) {
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
project.update(cx, |project, cx| {
|
||||
this.joined_projects
|
||||
.retain(|project| match project.upgrade() {
|
||||
Some(project) => project.update(cx, |project, cx| {
|
||||
if project.remote_id() == Some(*unshared_project_id) {
|
||||
project.disconnected_from_host(cx);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}),
|
||||
_ => false,
|
||||
});
|
||||
cx.emit(Event::RemoteProjectUnshared {
|
||||
project_id: *unshared_project_id,
|
||||
});
|
||||
@@ -800,56 +800,57 @@ impl Room {
|
||||
let role = participant.role();
|
||||
let location = ParticipantLocation::from_proto(participant.location)
|
||||
.unwrap_or(ParticipantLocation::External);
|
||||
if let Some(remote_participant) =
|
||||
this.remote_participants.get_mut(&participant.user_id)
|
||||
{
|
||||
remote_participant.peer_id = peer_id;
|
||||
remote_participant.projects = participant.projects;
|
||||
remote_participant.participant_index = participant_index;
|
||||
if location != remote_participant.location
|
||||
|| 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()))
|
||||
match this.remote_participants.get_mut(&participant.user_id) {
|
||||
Some(remote_participant) => {
|
||||
remote_participant.peer_id = peer_id;
|
||||
remote_participant.projects = participant.projects;
|
||||
remote_participant.participant_index = participant_index;
|
||||
if location != remote_participant.location
|
||||
|| role != remote_participant.role
|
||||
{
|
||||
for publication in
|
||||
livekit_participant.track_publications().into_values()
|
||||
remote_participant.location = location;
|
||||
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() {
|
||||
this.livekit_room_updated(
|
||||
RoomEvent::TrackSubscribed {
|
||||
track,
|
||||
publication,
|
||||
participant: livekit_participant.clone(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.warn_on_err();
|
||||
for publication in
|
||||
livekit_participant.track_publications().into_values()
|
||||
{
|
||||
if let Some(track) = publication.track() {
|
||||
this.livekit_room_updated(
|
||||
RoomEvent::TrackSubscribed {
|
||||
track,
|
||||
publication,
|
||||
participant: livekit_participant.clone(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.warn_on_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1141,13 +1142,11 @@ impl Room {
|
||||
Project::in_room(id, client, user_store, language_registry, fs, cx.clone()).await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.joined_projects.retain(|project| {
|
||||
if let Some(project) = project.upgrade() {
|
||||
!project.read(cx).is_disconnected(cx)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
this.joined_projects
|
||||
.retain(|project| match project.upgrade() {
|
||||
Some(project) => !project.read(cx).is_disconnected(cx),
|
||||
_ => false,
|
||||
});
|
||||
this.joined_projects.insert(project.downgrade());
|
||||
})?;
|
||||
Ok(project)
|
||||
@@ -1309,13 +1308,16 @@ impl Room {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
|
||||
let (room, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.microphone_track = LocalTrack::Pending { publish_id };
|
||||
cx.notify();
|
||||
(live_kit.room.clone(), publish_id)
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
let (room, publish_id) = match self.live_kit.as_mut() {
|
||||
Some(live_kit) => {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.microphone_track = LocalTrack::Pending { publish_id };
|
||||
cx.notify();
|
||||
(live_kit.room.clone(), publish_id)
|
||||
}
|
||||
_ => {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
}
|
||||
};
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
@@ -1326,13 +1328,11 @@ impl Room {
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
||||
|
||||
let canceled = if let LocalTrack::Pending {
|
||||
publish_id: cur_publish_id,
|
||||
} = &live_kit.microphone_track
|
||||
{
|
||||
*cur_publish_id != publish_id
|
||||
} else {
|
||||
true
|
||||
let canceled = match &live_kit.microphone_track {
|
||||
LocalTrack::Pending {
|
||||
publish_id: cur_publish_id,
|
||||
} => *cur_publish_id != publish_id,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
match publication {
|
||||
@@ -1376,13 +1376,16 @@ impl Room {
|
||||
return Task::ready(Err(anyhow!("screen was already shared")));
|
||||
}
|
||||
|
||||
let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.screen_track = LocalTrack::Pending { publish_id };
|
||||
cx.notify();
|
||||
(live_kit.room.local_participant(), publish_id)
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
let (participant, publish_id) = match self.live_kit.as_mut() {
|
||||
Some(live_kit) => {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.screen_track = LocalTrack::Pending { publish_id };
|
||||
cx.notify();
|
||||
(live_kit.room.local_participant(), publish_id)
|
||||
}
|
||||
_ => {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
}
|
||||
};
|
||||
|
||||
let sources = cx.screen_capture_sources();
|
||||
@@ -1399,13 +1402,11 @@ impl Room {
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
||||
|
||||
let canceled = if let LocalTrack::Pending {
|
||||
publish_id: cur_publish_id,
|
||||
} = &live_kit.screen_track
|
||||
{
|
||||
*cur_publish_id != publish_id
|
||||
} else {
|
||||
true
|
||||
let canceled = match &live_kit.screen_track {
|
||||
LocalTrack::Pending {
|
||||
publish_id: cur_publish_id,
|
||||
} => *cur_publish_id != publish_id,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
match publication {
|
||||
|
||||
@@ -183,7 +183,7 @@ impl ChannelChat {
|
||||
|
||||
let channel_id = self.channel_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(
|
||||
SumTree::from_item(
|
||||
ChannelMessage {
|
||||
@@ -257,7 +257,7 @@ impl ChannelChat {
|
||||
cx,
|
||||
);
|
||||
|
||||
let nonce: u128 = self.rng.gen();
|
||||
let nonce: u128 = self.rng.r#gen();
|
||||
|
||||
let request = self.rpc.request(proto::UpdateChannelMessage {
|
||||
channel_id: self.channel_id.0,
|
||||
|
||||
@@ -329,17 +329,16 @@ impl ChannelStore {
|
||||
.request(proto::GetChannelMessagesById { message_ids }),
|
||||
)
|
||||
};
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Some(request) = request {
|
||||
cx.spawn(async move |this, cx| match request {
|
||||
Some(request) => {
|
||||
let response = request.await?;
|
||||
let this = this
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("channel store dropped"))?;
|
||||
let user_store = this.update(cx, |this, _| this.user_store.clone())?;
|
||||
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 {
|
||||
match get_map(self).entry(channel_id) {
|
||||
hash_map::Entry::Occupied(e) => match e.get() {
|
||||
OpenEntityHandle::Open(entity) => {
|
||||
if let Some(entity) = entity.upgrade() {
|
||||
OpenEntityHandle::Open(entity) => match entity.upgrade() {
|
||||
Some(entity) => {
|
||||
break Task::ready(Ok(entity)).shared();
|
||||
} else {
|
||||
}
|
||||
_ => {
|
||||
get_map(self).remove(&channel_id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
},
|
||||
OpenEntityHandle::Loading(task) => {
|
||||
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();
|
||||
async move {
|
||||
client
|
||||
|
||||
@@ -228,13 +228,16 @@ fn main() -> Result<()> {
|
||||
paths.push(file.path().to_string_lossy().to_string());
|
||||
let (file, _) = file.keep()?;
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
bundle_path
|
||||
.canonicalize()
|
||||
|
||||
@@ -618,10 +618,9 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> Option<PeerId> {
|
||||
if let Status::Connected { peer_id, .. } = &*self.status().borrow() {
|
||||
Some(*peer_id)
|
||||
} else {
|
||||
None
|
||||
match &*self.status().borrow() {
|
||||
Status::Connected { peer_id, .. } => Some(*peer_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,7 +1023,7 @@ impl Client {
|
||||
&self,
|
||||
http: Arc<HttpClientWithUrl>,
|
||||
release_channel: Option<ReleaseChannel>,
|
||||
) -> impl Future<Output = Result<url::Url>> {
|
||||
) -> impl Future<Output = Result<url::Url>> + use<> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let url_override = self.rpc_url.read().clone();
|
||||
|
||||
@@ -1429,10 +1428,9 @@ impl Client {
|
||||
}
|
||||
|
||||
fn connection_id(&self) -> Result<ConnectionId> {
|
||||
if let Status::Connected { connection_id, .. } = *self.status().borrow() {
|
||||
Ok(connection_id)
|
||||
} else {
|
||||
Err(anyhow!("not connected"))
|
||||
match *self.status().borrow() {
|
||||
Status::Connected { connection_id, .. } => Ok(connection_id),
|
||||
_ => Err(anyhow!("not connected")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1444,7 +1442,7 @@ impl Client {
|
||||
pub fn request<T: RequestMessage>(
|
||||
&self,
|
||||
request: T,
|
||||
) -> impl Future<Output = Result<T::Response>> {
|
||||
) -> impl Future<Output = Result<T::Response>> + use<T> {
|
||||
self.request_envelope(request)
|
||||
.map_ok(|envelope| envelope.payload)
|
||||
}
|
||||
@@ -1452,7 +1450,8 @@ impl Client {
|
||||
pub fn request_stream<T: RequestMessage>(
|
||||
&self,
|
||||
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);
|
||||
log::debug!(
|
||||
"rpc request start. client_id:{}. name:{}",
|
||||
@@ -1476,7 +1475,7 @@ impl Client {
|
||||
pub fn request_envelope<T: RequestMessage>(
|
||||
&self,
|
||||
request: T,
|
||||
) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> {
|
||||
) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> + use<T> {
|
||||
let client_id = self.id();
|
||||
log::debug!(
|
||||
"rpc request start. client_id:{}. name:{}",
|
||||
@@ -1501,7 +1500,7 @@ impl Client {
|
||||
&self,
|
||||
envelope: proto::Envelope,
|
||||
request_type: &'static str,
|
||||
) -> impl Future<Output = Result<proto::Envelope>> {
|
||||
) -> impl Future<Output = Result<proto::Envelope>> + use<> {
|
||||
let client_id = self.id();
|
||||
log::debug!(
|
||||
"rpc request start. client_id:{}. name:{}",
|
||||
@@ -1528,44 +1527,47 @@ impl Client {
|
||||
let type_name = message.payload_type_name();
|
||||
let original_sender_id = message.original_sender_id();
|
||||
|
||||
if let Some(future) = ProtoMessageHandlerSet::handle_message(
|
||||
match ProtoMessageHandlerSet::handle_message(
|
||||
&self.handler_set,
|
||||
message,
|
||||
self.clone().into(),
|
||||
cx.clone(),
|
||||
) {
|
||||
let client_id = self.id();
|
||||
log::debug!(
|
||||
"rpc message received. client_id:{}, sender_id:{:?}, type:{}",
|
||||
client_id,
|
||||
original_sender_id,
|
||||
type_name
|
||||
);
|
||||
cx.spawn(async move |_| match future.await {
|
||||
Ok(()) => {
|
||||
log::debug!(
|
||||
"rpc message handled. client_id:{}, sender_id:{:?}, type:{}",
|
||||
client_id,
|
||||
original_sender_id,
|
||||
type_name
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
Some(future) => {
|
||||
let client_id = self.id();
|
||||
log::debug!(
|
||||
"rpc message received. client_id:{}, sender_id:{:?}, type:{}",
|
||||
client_id,
|
||||
original_sender_id,
|
||||
type_name
|
||||
);
|
||||
cx.spawn(async move |_| match future.await {
|
||||
Ok(()) => {
|
||||
log::debug!(
|
||||
"rpc message handled. client_id:{}, sender_id:{:?}, type:{}",
|
||||
client_id,
|
||||
original_sender_id,
|
||||
type_name
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
"error handling message. client_id:{}, sender_id:{:?}, type:{}, error:{:?}",
|
||||
client_id,
|
||||
original_sender_id,
|
||||
type_name,
|
||||
error
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
log::info!("unhandled message {}", type_name);
|
||||
self.peer
|
||||
.respond_with_unhandled_message(sender_id.into(), request_id, type_name)
|
||||
.log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
_ => {
|
||||
log::info!("unhandled message {}", type_name);
|
||||
self.peer
|
||||
.respond_with_unhandled_message(sender_id.into(), request_id, type_name)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ impl Telemetry {
|
||||
}
|
||||
|
||||
#[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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -171,11 +171,13 @@ impl UserStore {
|
||||
_maintain_contacts: cx.spawn(async move |this, cx| {
|
||||
let _subscriptions = rpc_subscriptions;
|
||||
while let Some(message) = update_contacts_rx.next().await {
|
||||
if let Ok(task) = this.update(cx, |this, cx| this.update_contacts(message, cx))
|
||||
{
|
||||
task.log_err().await;
|
||||
} else {
|
||||
break;
|
||||
match this.update(cx, |this, cx| this.update_contacts(message, cx)) {
|
||||
Ok(task) => {
|
||||
task.log_err().await;
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -191,12 +193,13 @@ impl UserStore {
|
||||
match status {
|
||||
Status::Connected { .. } => {
|
||||
if let Some(user_id) = client.user_id() {
|
||||
let fetch_user = if let Ok(fetch_user) =
|
||||
this.update(cx, |this, cx| this.get_user(user_id, cx).log_err())
|
||||
let fetch_user = match this
|
||||
.update(cx, |this, cx| this.get_user(user_id, cx).log_err())
|
||||
{
|
||||
fetch_user
|
||||
} else {
|
||||
break;
|
||||
Ok(fetch_user) => fetch_user,
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
let fetch_private_user_info =
|
||||
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();
|
||||
self.update_contacts_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();
|
||||
self.update_contacts_tx
|
||||
.unbounded_send(UpdateContacts::Wait(tx))
|
||||
@@ -703,8 +706,8 @@ impl UserStore {
|
||||
};
|
||||
|
||||
let client = self.client.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Some(client) = client.upgrade() {
|
||||
cx.spawn(async move |this, cx| match client.upgrade() {
|
||||
Some(client) => {
|
||||
let response = client
|
||||
.request(proto::AcceptTermsOfService {})
|
||||
.await
|
||||
@@ -714,9 +717,8 @@ impl UserStore {
|
||||
this.set_current_user_accepted_tos_at(Some(response.accepted_tos_at));
|
||||
cx.emit(Event::PrivateUserInfoUpdated);
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("client not found"))
|
||||
}
|
||||
_ => Err(anyhow!("client not found")),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -732,15 +734,14 @@ impl UserStore {
|
||||
cx: &Context<Self>,
|
||||
) -> Task<Result<Vec<Arc<User>>>> {
|
||||
let client = self.client.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Some(rpc) = client.upgrade() {
|
||||
cx.spawn(async move |this, cx| match client.upgrade() {
|
||||
Some(rpc) => {
|
||||
let response = rpc.request(request).await.context("error loading users")?;
|
||||
let users = response.users;
|
||||
|
||||
this.update(cx, |this, _| this.insert(users))
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
_ => Ok(Vec::new()),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -219,13 +219,16 @@ async fn create_access_token(
|
||||
let mut impersonated_user_id = None;
|
||||
if let Some(impersonate) = params.impersonate {
|
||||
if user.admin {
|
||||
if let Some(impersonated_user) = app.db.get_user_by_github_login(&impersonate).await? {
|
||||
impersonated_user_id = Some(impersonated_user.id);
|
||||
} else {
|
||||
return Err(Error::http(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
format!("user {impersonate} does not exist"),
|
||||
));
|
||||
match app.db.get_user_by_github_login(&impersonate).await? {
|
||||
Some(impersonated_user) => {
|
||||
impersonated_user_id = Some(impersonated_user.id);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::http(
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
format!("user {impersonate} does not exist"),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(Error::http(
|
||||
|
||||
@@ -103,8 +103,8 @@ async fn update_billing_preferences(
|
||||
let max_monthly_llm_usage_spending_in_cents =
|
||||
body.max_monthly_llm_usage_spending_in_cents.max(0);
|
||||
|
||||
let billing_preferences =
|
||||
if let Some(_billing_preferences) = app.db.get_billing_preferences(user.id).await? {
|
||||
let billing_preferences = match app.db.get_billing_preferences(user.id).await? {
|
||||
Some(_billing_preferences) => {
|
||||
app.db
|
||||
.update_billing_preferences(
|
||||
user.id,
|
||||
@@ -115,7 +115,8 @@ async fn update_billing_preferences(
|
||||
},
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
}
|
||||
_ => {
|
||||
app.db
|
||||
.create_billing_preferences(
|
||||
user.id,
|
||||
@@ -124,7 +125,8 @@ async fn update_billing_preferences(
|
||||
},
|
||||
)
|
||||
.await?
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
SnowflakeRow::new(
|
||||
"Spend Limit Updated",
|
||||
@@ -624,28 +626,31 @@ async fn handle_customer_event(
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(existing_customer) = app
|
||||
match app
|
||||
.db
|
||||
.get_billing_customer_by_stripe_customer_id(&customer.id)
|
||||
.await?
|
||||
{
|
||||
app.db
|
||||
.update_billing_customer(
|
||||
existing_customer.id,
|
||||
&UpdateBillingCustomerParams {
|
||||
// For now we just leave the information as-is, as it is not
|
||||
// likely to change.
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
app.db
|
||||
.create_billing_customer(&CreateBillingCustomerParams {
|
||||
user_id: user.id,
|
||||
stripe_customer_id: customer.id.to_string(),
|
||||
})
|
||||
.await?;
|
||||
Some(existing_customer) => {
|
||||
app.db
|
||||
.update_billing_customer(
|
||||
existing_customer.id,
|
||||
&UpdateBillingCustomerParams {
|
||||
// For now we just leave the information as-is, as it is not
|
||||
// likely to change.
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
_ => {
|
||||
app.db
|
||||
.create_billing_customer(&CreateBillingCustomerParams {
|
||||
user_id: user.id,
|
||||
stripe_customer_id: customer.id.to_string(),
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -689,72 +694,75 @@ async fn handle_customer_subscription_event(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(existing_subscription) = app
|
||||
match app
|
||||
.db
|
||||
.get_billing_subscription_by_stripe_subscription_id(&subscription.id)
|
||||
.await?
|
||||
{
|
||||
app.db
|
||||
.update_billing_subscription(
|
||||
existing_subscription.id,
|
||||
&UpdateBillingSubscriptionParams {
|
||||
billing_customer_id: ActiveValue::set(billing_customer.id),
|
||||
stripe_subscription_id: ActiveValue::set(subscription.id.to_string()),
|
||||
stripe_subscription_status: ActiveValue::set(subscription.status.into()),
|
||||
stripe_cancel_at: ActiveValue::set(
|
||||
subscription
|
||||
.cancel_at
|
||||
.and_then(|cancel_at| DateTime::from_timestamp(cancel_at, 0))
|
||||
.map(|time| time.naive_utc()),
|
||||
),
|
||||
stripe_cancellation_reason: ActiveValue::set(
|
||||
subscription
|
||||
.cancellation_details
|
||||
.and_then(|details| details.reason)
|
||||
.map(|reason| reason.into()),
|
||||
),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
// If the user already has an active billing subscription, ignore the
|
||||
// event and return an `Ok` to signal that it was processed
|
||||
// successfully.
|
||||
//
|
||||
// There is the possibility that this could cause us to not create a
|
||||
// subscription in the following scenario:
|
||||
//
|
||||
// 1. User has an active subscription A
|
||||
// 2. User cancels subscription A
|
||||
// 3. User creates a new subscription B
|
||||
// 4. We process the new subscription B before the cancellation of subscription A
|
||||
// 5. User ends up with no subscriptions
|
||||
//
|
||||
// In theory this situation shouldn't arise as we try to process the events in the order they occur.
|
||||
if app
|
||||
.db
|
||||
.has_active_billing_subscription(billing_customer.user_id)
|
||||
.await?
|
||||
{
|
||||
log::info!(
|
||||
Some(existing_subscription) => {
|
||||
app.db
|
||||
.update_billing_subscription(
|
||||
existing_subscription.id,
|
||||
&UpdateBillingSubscriptionParams {
|
||||
billing_customer_id: ActiveValue::set(billing_customer.id),
|
||||
stripe_subscription_id: ActiveValue::set(subscription.id.to_string()),
|
||||
stripe_subscription_status: ActiveValue::set(subscription.status.into()),
|
||||
stripe_cancel_at: ActiveValue::set(
|
||||
subscription
|
||||
.cancel_at
|
||||
.and_then(|cancel_at| DateTime::from_timestamp(cancel_at, 0))
|
||||
.map(|time| time.naive_utc()),
|
||||
),
|
||||
stripe_cancellation_reason: ActiveValue::set(
|
||||
subscription
|
||||
.cancellation_details
|
||||
.and_then(|details| details.reason)
|
||||
.map(|reason| reason.into()),
|
||||
),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
_ => {
|
||||
// If the user already has an active billing subscription, ignore the
|
||||
// event and return an `Ok` to signal that it was processed
|
||||
// successfully.
|
||||
//
|
||||
// There is the possibility that this could cause us to not create a
|
||||
// subscription in the following scenario:
|
||||
//
|
||||
// 1. User has an active subscription A
|
||||
// 2. User cancels subscription A
|
||||
// 3. User creates a new subscription B
|
||||
// 4. We process the new subscription B before the cancellation of subscription A
|
||||
// 5. User ends up with no subscriptions
|
||||
//
|
||||
// In theory this situation shouldn't arise as we try to process the events in the order they occur.
|
||||
if app
|
||||
.db
|
||||
.has_active_billing_subscription(billing_customer.user_id)
|
||||
.await?
|
||||
{
|
||||
log::info!(
|
||||
"user {user_id} already has an active subscription, skipping creation of subscription {subscription_id}",
|
||||
user_id = billing_customer.user_id,
|
||||
subscription_id = subscription.id
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
app.db
|
||||
.create_billing_subscription(&CreateBillingSubscriptionParams {
|
||||
billing_customer_id: billing_customer.id,
|
||||
stripe_subscription_id: subscription.id.to_string(),
|
||||
stripe_subscription_status: subscription.status.into(),
|
||||
stripe_cancellation_reason: subscription
|
||||
.cancellation_details
|
||||
.and_then(|details| details.reason)
|
||||
.map(|reason| reason.into()),
|
||||
})
|
||||
.await?;
|
||||
app.db
|
||||
.create_billing_subscription(&CreateBillingSubscriptionParams {
|
||||
billing_customer_id: billing_customer.id,
|
||||
stripe_subscription_id: subscription.id.to_string(),
|
||||
stripe_subscription_status: subscription.status.into(),
|
||||
stripe_cancellation_reason: subscription
|
||||
.cancellation_details
|
||||
.and_then(|details| details.reason)
|
||||
.map(|reason| reason.into()),
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// When the user's subscription changes, we want to refresh their LLM tokens
|
||||
|
||||
@@ -47,10 +47,15 @@ impl IpsFile {
|
||||
Some(panic_message) => format!("Panic `{}`", panic_message),
|
||||
None => "Crash `Abort trap: 6` (possible panic)".into(),
|
||||
}
|
||||
} else if let Some(msg) = &self.body.exception.message {
|
||||
format!("Exception `{}`", msg)
|
||||
} 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(queue) = thread.queue.as_ref() {
|
||||
@@ -81,10 +86,13 @@ impl IpsFile {
|
||||
return None;
|
||||
}
|
||||
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 {
|
||||
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<_>>();
|
||||
|
||||
@@ -105,47 +105,52 @@ impl Database {
|
||||
let mut accept_invite_result = None;
|
||||
|
||||
if role.is_none() {
|
||||
if let Some(invitation) = self
|
||||
match self
|
||||
.pending_invite_for_channel(&channel, user_id, &tx)
|
||||
.await?
|
||||
{
|
||||
// note, this may be a parent channel
|
||||
role = Some(invitation.role);
|
||||
channel_member::Entity::update(channel_member::ActiveModel {
|
||||
accepted: ActiveValue::Set(true),
|
||||
..invitation.into_active_model()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
Some(invitation) => {
|
||||
// note, this may be a parent channel
|
||||
role = Some(invitation.role);
|
||||
channel_member::Entity::update(channel_member::ActiveModel {
|
||||
accepted: ActiveValue::Set(true),
|
||||
..invitation.into_active_model()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
accept_invite_result = Some(
|
||||
self.calculate_membership_updated(&channel, user_id, &tx)
|
||||
.await?,
|
||||
);
|
||||
accept_invite_result = Some(
|
||||
self.calculate_membership_updated(&channel, user_id, &tx)
|
||||
.await?,
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
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 {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel.root_id()),
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Guest),
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
debug_assert!(
|
||||
self.channel_role_for_user(&channel, user_id, &tx).await? == role
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
if channel.visibility == ChannelVisibility::Public {
|
||||
role = Some(ChannelRole::Guest);
|
||||
channel_member::Entity::insert(channel_member::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
channel_id: ActiveValue::Set(channel.root_id()),
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
accepted: ActiveValue::Set(true),
|
||||
role: ActiveValue::Set(ChannelRole::Guest),
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
accept_invite_result = Some(
|
||||
self.calculate_membership_updated(&channel, user_id, &tx)
|
||||
.await?,
|
||||
);
|
||||
accept_invite_result = Some(
|
||||
self.calculate_membership_updated(&channel, user_id, &tx)
|
||||
.await?,
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
self.channel_role_for_user(&channel, user_id, &tx).await? == role
|
||||
);
|
||||
debug_assert!(
|
||||
self.channel_role_for_user(&channel, user_id, &tx).await? == role
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,12 +101,15 @@ impl Database {
|
||||
}
|
||||
|
||||
if let Some(wasm_api_version) = version.wasm_api_version.as_ref() {
|
||||
if let Some(version) = SemanticVersion::from_str(wasm_api_version).log_err() {
|
||||
if !constraints.wasm_api_versions.contains(&version) {
|
||||
match SemanticVersion::from_str(wasm_api_version).log_err() {
|
||||
Some(version) => {
|
||||
if !constraints.wasm_api_versions.contains(&version) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,28 +192,29 @@ impl Database {
|
||||
response: Option<bool>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Option<(UserId, proto::Notification)>> {
|
||||
if let Some(id) = self
|
||||
match self
|
||||
.find_notification(recipient_id, notification, tx)
|
||||
.await?
|
||||
{
|
||||
let row = notification::Entity::update(notification::ActiveModel {
|
||||
id: ActiveValue::Unchanged(id),
|
||||
recipient_id: ActiveValue::Unchanged(recipient_id),
|
||||
is_read: ActiveValue::Set(true),
|
||||
response: if let Some(response) = response {
|
||||
ActiveValue::Set(Some(response))
|
||||
} else {
|
||||
ActiveValue::NotSet
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.exec(tx)
|
||||
.await?;
|
||||
Ok(model_to_proto(self, row)
|
||||
.map(|notification| (recipient_id, notification))
|
||||
.ok())
|
||||
} else {
|
||||
Ok(None)
|
||||
Some(id) => {
|
||||
let row = notification::Entity::update(notification::ActiveModel {
|
||||
id: ActiveValue::Unchanged(id),
|
||||
recipient_id: ActiveValue::Unchanged(recipient_id),
|
||||
is_read: ActiveValue::Set(true),
|
||||
response: if let Some(response) = response {
|
||||
ActiveValue::Set(Some(response))
|
||||
} else {
|
||||
ActiveValue::NotSet
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.exec(tx)
|
||||
.await?;
|
||||
Ok(model_to_proto(self, row)
|
||||
.map(|notification| (recipient_id, notification))
|
||||
.ok())
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,50 +132,54 @@ impl Database {
|
||||
initial_channel_id: Option<ChannelId>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> 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)
|
||||
.await?
|
||||
{
|
||||
let mut existing_user = existing_user.into_active_model();
|
||||
existing_user.github_login = ActiveValue::set(github_login.into());
|
||||
existing_user.github_user_created_at = ActiveValue::set(Some(github_user_created_at));
|
||||
Some(existing_user) => {
|
||||
let mut existing_user = existing_user.into_active_model();
|
||||
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 {
|
||||
existing_user.email_address = ActiveValue::set(Some(github_email.into()));
|
||||
if let Some(github_email) = github_email {
|
||||
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 {
|
||||
existing_user.name = ActiveValue::set(Some(github_name.into()));
|
||||
}
|
||||
|
||||
Ok(existing_user.update(tx).await?)
|
||||
} else {
|
||||
let user = user::Entity::insert(user::ActiveModel {
|
||||
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()),
|
||||
github_user_id: ActiveValue::set(github_user_id),
|
||||
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),
|
||||
_ => {
|
||||
let user = user::Entity::insert(user::ActiveModel {
|
||||
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()),
|
||||
github_user_id: ActiveValue::set(github_user_id),
|
||||
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(tx)
|
||||
.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)
|
||||
.await?;
|
||||
}
|
||||
Ok(user)
|
||||
}
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ impl TestDb {
|
||||
let mut rng = StdRng::from_entropy();
|
||||
let url = format!(
|
||||
"postgres://postgres@localhost/zed-test-{}",
|
||||
rng.gen::<u128>()
|
||||
rng.r#gen::<u128>()
|
||||
);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
|
||||
@@ -101,10 +101,11 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
||||
);
|
||||
buffer_b.apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
|
||||
let operation = proto::deserialize_operation(operation).unwrap();
|
||||
if let language::Operation::Buffer(operation) = operation {
|
||||
operation
|
||||
} else {
|
||||
unreachable!()
|
||||
match operation {
|
||||
language::Operation::Buffer(operation) => operation,
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ pub fn get_dotenv_vars(current_dir: impl AsRef<Path>) -> Result<Vec<(String, Str
|
||||
|
||||
pub fn load_dotenv() -> Result<()> {
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
async move {
|
||||
match this {
|
||||
|
||||
@@ -26,7 +26,7 @@ impl TestLlmDb {
|
||||
let mut rng = StdRng::from_entropy();
|
||||
let url = format!(
|
||||
"postgres://postgres@localhost/zed-llm-test-{}",
|
||||
rng.gen::<u128>()
|
||||
rng.r#gen::<u128>()
|
||||
);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
|
||||
@@ -716,7 +716,7 @@ impl Server {
|
||||
system_id: Option<String>,
|
||||
send_connection_id: Option<oneshot::Sender<ConnectionId>>,
|
||||
executor: Executor,
|
||||
) -> impl Future<Output = ()> {
|
||||
) -> impl Future<Output = ()> + use<> {
|
||||
let this = self.clone();
|
||||
let span = info_span!("handle connection", %address,
|
||||
connection_id=field::Empty,
|
||||
@@ -810,7 +810,7 @@ impl Server {
|
||||
_ = foreground_message_handlers.next() => {}
|
||||
next_message = next_message => {
|
||||
let (permit, message) = next_message;
|
||||
if let Some(message) = message {
|
||||
match message { Some(message) => {
|
||||
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.
|
||||
// (https://github.com/tokio-rs/tracing/issues/2670).
|
||||
@@ -821,7 +821,7 @@ impl Server {
|
||||
);
|
||||
principal.update_span(&span);
|
||||
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 handle_message = (handler)(message, session.clone());
|
||||
drop(span_enter);
|
||||
@@ -835,13 +835,13 @@ impl Server {
|
||||
} else {
|
||||
foreground_message_handlers.push(handle_message);
|
||||
}
|
||||
} else {
|
||||
} _ => {
|
||||
tracing::error!("no message handler");
|
||||
}
|
||||
} else {
|
||||
}}
|
||||
} _ => {
|
||||
tracing::info!("connection closed");
|
||||
break;
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1313,9 +1313,8 @@ async fn join_room(
|
||||
.trace_err();
|
||||
}
|
||||
|
||||
let live_kit_connection_info = if let Some(live_kit) = session.app_state.livekit_client.as_ref()
|
||||
{
|
||||
live_kit
|
||||
let live_kit_connection_info = match session.app_state.livekit_client.as_ref() {
|
||||
Some(live_kit) => live_kit
|
||||
.room_token(
|
||||
&joined_room.room.livekit_room,
|
||||
&session.user_id().to_string(),
|
||||
@@ -1325,9 +1324,8 @@ async fn join_room(
|
||||
server_url: live_kit.url().into(),
|
||||
token,
|
||||
can_publish: true,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
response.send(proto::JoinRoomResponse {
|
||||
@@ -4393,23 +4391,26 @@ async fn leave_room_for_session(session: &Session, connection_id: ConnectionId)
|
||||
let room;
|
||||
let channel;
|
||||
|
||||
if let Some(mut left_room) = session.db().await.leave_room(connection_id).await? {
|
||||
contacts_to_update.insert(session.user_id());
|
||||
match session.db().await.leave_room(connection_id).await? {
|
||||
Some(mut left_room) => {
|
||||
contacts_to_update.insert(session.user_id());
|
||||
|
||||
for project in left_room.left_projects.values() {
|
||||
project_left(project, session);
|
||||
for project in left_room.left_projects.values() {
|
||||
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 {
|
||||
|
||||
@@ -130,74 +130,76 @@ impl StripeBilling {
|
||||
}
|
||||
|
||||
let mut state = self.state.write().await;
|
||||
let meter = if let Some(meter) = state.meters_by_event_name.get(meter_event_name) {
|
||||
meter.clone()
|
||||
} else {
|
||||
let meter = StripeMeter::create(
|
||||
&self.client,
|
||||
StripeCreateMeterParams {
|
||||
default_aggregation: DefaultAggregation { formula: "sum" },
|
||||
display_name: price_description.to_string(),
|
||||
event_name: meter_event_name,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
state
|
||||
.meters_by_event_name
|
||||
.insert(meter_event_name.to_string(), meter.clone());
|
||||
meter
|
||||
let meter = match state.meters_by_event_name.get(meter_event_name) {
|
||||
Some(meter) => meter.clone(),
|
||||
_ => {
|
||||
let meter = StripeMeter::create(
|
||||
&self.client,
|
||||
StripeCreateMeterParams {
|
||||
default_aggregation: DefaultAggregation { formula: "sum" },
|
||||
display_name: price_description.to_string(),
|
||||
event_name: meter_event_name,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
state
|
||||
.meters_by_event_name
|
||||
.insert(meter_event_name.to_string(), meter.clone());
|
||||
meter
|
||||
}
|
||||
};
|
||||
|
||||
let price_id = if let Some(price_id) = state.price_ids_by_meter_id.get(&meter.id) {
|
||||
price_id.clone()
|
||||
} else {
|
||||
let price = stripe::Price::create(
|
||||
&self.client,
|
||||
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,
|
||||
let price_id = match state.price_ids_by_meter_id.get(&meter.id) {
|
||||
Some(price_id) => price_id.clone(),
|
||||
_ => {
|
||||
let price = stripe::Price::create(
|
||||
&self.client,
|
||||
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,
|
||||
name: price_description.to_string(),
|
||||
statement_descriptor: None,
|
||||
tax_code: None,
|
||||
unit_label: None,
|
||||
}),
|
||||
recurring: Some(stripe::CreatePriceRecurring {
|
||||
aggregate_usage: None,
|
||||
interval: stripe::CreatePriceRecurringInterval::Month,
|
||||
interval_count: None,
|
||||
trial_period_days: None,
|
||||
usage_type: Some(stripe::CreatePriceRecurringUsageType::Metered),
|
||||
meter: Some(meter.id.clone()),
|
||||
}),
|
||||
tax_behavior: None,
|
||||
tiers: None,
|
||||
tiers_mode: None,
|
||||
transfer_lookup_key: None,
|
||||
transform_quantity: None,
|
||||
unit_amount: None,
|
||||
unit_amount_decimal: Some(&format!(
|
||||
"{:.12}",
|
||||
price_per_million_tokens.0 as f64 / 1_000_000f64
|
||||
)),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
state
|
||||
.price_ids_by_meter_id
|
||||
.insert(meter.id, price.id.clone());
|
||||
price.id
|
||||
nickname: None,
|
||||
product: None,
|
||||
product_data: Some(stripe::CreatePriceProductData {
|
||||
id: None,
|
||||
active: Some(true),
|
||||
metadata: None,
|
||||
name: price_description.to_string(),
|
||||
statement_descriptor: None,
|
||||
tax_code: None,
|
||||
unit_label: None,
|
||||
}),
|
||||
recurring: Some(stripe::CreatePriceRecurring {
|
||||
aggregate_usage: None,
|
||||
interval: stripe::CreatePriceRecurringInterval::Month,
|
||||
interval_count: None,
|
||||
trial_period_days: None,
|
||||
usage_type: Some(stripe::CreatePriceRecurringUsageType::Metered),
|
||||
meter: Some(meter.id.clone()),
|
||||
}),
|
||||
tax_behavior: None,
|
||||
tiers: None,
|
||||
tiers_mode: None,
|
||||
transfer_lookup_key: None,
|
||||
transform_quantity: None,
|
||||
unit_amount: None,
|
||||
unit_amount_decimal: Some(&format!(
|
||||
"{:.12}",
|
||||
price_per_million_tokens.0 as f64 / 1_000_000f64
|
||||
)),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
state
|
||||
.price_ids_by_meter_id
|
||||
.insert(meter.id, price.id.clone());
|
||||
price.id
|
||||
}
|
||||
};
|
||||
|
||||
Ok(StripeBillingPrice {
|
||||
|
||||
@@ -5602,7 +5602,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
|
||||
|
||||
let definitions;
|
||||
let buffer_b2;
|
||||
if rng.gen() {
|
||||
if rng.r#gen() {
|
||||
definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
|
||||
(buffer_b2, _) = project_b
|
||||
.update(cx_b, |p, cx| {
|
||||
|
||||
@@ -216,45 +216,47 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
// Open a new project
|
||||
0..=70 => {
|
||||
// Open a remote project
|
||||
if let Some(room) = call.read_with(cx, |call, _| call.room().cloned()) {
|
||||
let existing_dev_server_project_ids = cx.read(|cx| {
|
||||
client
|
||||
.dev_server_projects()
|
||||
.iter()
|
||||
.map(|p| p.read(cx).remote_id().unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let new_dev_server_projects = room.read_with(cx, |room, _| {
|
||||
room.remote_participants()
|
||||
.values()
|
||||
.flat_map(|participant| {
|
||||
participant.projects.iter().filter_map(|project| {
|
||||
if existing_dev_server_project_ids.contains(&project.id)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
UserId::from_proto(participant.user.id),
|
||||
project.worktree_root_names[0].clone(),
|
||||
))
|
||||
}
|
||||
match call.read_with(cx, |call, _| call.room().cloned()) {
|
||||
Some(room) => {
|
||||
let existing_dev_server_project_ids = cx.read(|cx| {
|
||||
client
|
||||
.dev_server_projects()
|
||||
.iter()
|
||||
.map(|p| p.read(cx).remote_id().unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let new_dev_server_projects = room.read_with(cx, |room, _| {
|
||||
room.remote_participants()
|
||||
.values()
|
||||
.flat_map(|participant| {
|
||||
participant.projects.iter().filter_map(|project| {
|
||||
if existing_dev_server_project_ids
|
||||
.contains(&project.id)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
UserId::from_proto(participant.user.id),
|
||||
project.worktree_root_names[0].clone(),
|
||||
))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
if !new_dev_server_projects.is_empty() {
|
||||
let (host_id, first_root_name) =
|
||||
new_dev_server_projects.choose(rng).unwrap().clone();
|
||||
break ClientOperation::OpenRemoteProject {
|
||||
host_id,
|
||||
first_root_name,
|
||||
};
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
if !new_dev_server_projects.is_empty() {
|
||||
let (host_id, first_root_name) =
|
||||
new_dev_server_projects.choose(rng).unwrap().clone();
|
||||
break ClientOperation::OpenRemoteProject {
|
||||
host_id,
|
||||
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 mut paths = client.fs().paths(false);
|
||||
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())
|
||||
} else {
|
||||
paths.choose(rng).unwrap().clone()
|
||||
@@ -309,7 +311,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
.choose(rng)
|
||||
});
|
||||
let Some(worktree) = worktree else { continue };
|
||||
let is_dir = rng.gen::<bool>();
|
||||
let is_dir = rng.r#gen::<bool>();
|
||||
let mut full_path =
|
||||
worktree.read_with(cx, |w, _| PathBuf::from(w.root_name()));
|
||||
full_path.push(gen_file_name(rng));
|
||||
@@ -387,7 +389,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
language::Bias::Left,
|
||||
)
|
||||
});
|
||||
let detach = rng.gen();
|
||||
let detach = rng.r#gen();
|
||||
break ClientOperation::RequestLspDataInBuffer {
|
||||
project_root_name,
|
||||
full_path,
|
||||
@@ -460,7 +462,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
|
||||
// Create or update a file or directory
|
||||
96.. => {
|
||||
let is_dir = rng.gen::<bool>();
|
||||
let is_dir = rng.r#gen::<bool>();
|
||||
let content;
|
||||
let mut path;
|
||||
let dir_paths = client.fs().directories(false);
|
||||
@@ -1270,12 +1272,14 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
Some((client.user_id().unwrap(), project, cx))
|
||||
});
|
||||
|
||||
let (host_user_id, host_project, host_cx) =
|
||||
if let Some((host_user_id, host_project, host_cx)) = host_project {
|
||||
let (host_user_id, host_project, host_cx) = match host_project {
|
||||
Some((host_user_id, host_project, host_cx)) => {
|
||||
(host_user_id, host_project, host_cx)
|
||||
} else {
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
for guest_buffer in guest_buffers {
|
||||
let buffer_id =
|
||||
|
||||
@@ -175,26 +175,26 @@ impl TestServer {
|
||||
|
||||
let clock = Arc::new(FakeSystemClock::new());
|
||||
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
|
||||
{
|
||||
user.id
|
||||
} else {
|
||||
let github_user_id = self.next_github_user_id;
|
||||
self.next_github_user_id += 1;
|
||||
self.app_state
|
||||
.db
|
||||
.create_user(
|
||||
&format!("{name}@example.com"),
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: name.into(),
|
||||
github_user_id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("creating user failed")
|
||||
.user_id
|
||||
let user_id = match self.app_state.db.get_user_by_github_login(name).await {
|
||||
Ok(Some(user)) => user.id,
|
||||
_ => {
|
||||
let github_user_id = self.next_github_user_id;
|
||||
self.next_github_user_id += 1;
|
||||
self.app_state
|
||||
.db
|
||||
.create_user(
|
||||
&format!("{name}@example.com"),
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: name.into(),
|
||||
github_user_id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("creating user failed")
|
||||
.user_id
|
||||
}
|
||||
};
|
||||
let client_name = name.to_string();
|
||||
let mut client = cx.update(|cx| Client::new(clock, http.clone(), cx));
|
||||
@@ -660,7 +660,7 @@ impl TestClient {
|
||||
pub fn buffers_for_project<'a>(
|
||||
&'a self,
|
||||
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| {
|
||||
state.buffers.entry(project.clone()).or_default()
|
||||
})
|
||||
|
||||
@@ -556,12 +556,9 @@ impl FollowableItem for ChannelView {
|
||||
Some(proto::view::Variant::ChannelView(
|
||||
proto::view::ChannelView {
|
||||
channel_id: channel_buffer.channel_id.0,
|
||||
editor: if let Some(proto::view::Variant::Editor(proto)) =
|
||||
self.editor.read(cx).to_state_proto(window, cx)
|
||||
{
|
||||
Some(proto)
|
||||
} else {
|
||||
None
|
||||
editor: match self.editor.read(cx).to_state_proto(window, cx) {
|
||||
Some(proto::view::Variant::Editor(proto)) => Some(proto),
|
||||
_ => None,
|
||||
},
|
||||
},
|
||||
))
|
||||
|
||||
@@ -102,14 +102,11 @@ impl ChatPanel {
|
||||
0,
|
||||
gpui::ListAlignment::Bottom,
|
||||
px(1000.),
|
||||
move |ix, window, cx| {
|
||||
if let Some(entity) = entity.upgrade() {
|
||||
entity.update(cx, |this: &mut Self, cx| {
|
||||
this.render_message(ix, window, cx).into_any_element()
|
||||
})
|
||||
} else {
|
||||
div().into_any()
|
||||
}
|
||||
move |ix, window, cx| match entity.upgrade() {
|
||||
Some(entity) => entity.update(cx, |this: &mut Self, cx| {
|
||||
this.render_message(ix, window, cx).into_any_element()
|
||||
}),
|
||||
_ => div().into_any(),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -200,15 +197,14 @@ impl ChatPanel {
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
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) })
|
||||
.await
|
||||
.log_err()
|
||||
.flatten()
|
||||
{
|
||||
Some(serde_json::from_str::<SerializedChatPanel>(&panel)?)
|
||||
} else {
|
||||
None
|
||||
Some(panel) => Some(serde_json::from_str::<SerializedChatPanel>(&panel)?),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
@@ -314,7 +310,7 @@ impl ChatPanel {
|
||||
message_id: Option<ChannelMessageId>,
|
||||
reply_to_message: &Option<ChannelMessage>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let reply_to_message = match reply_to_message {
|
||||
None => {
|
||||
return div().child(
|
||||
@@ -393,7 +389,7 @@ impl ChatPanel {
|
||||
ix: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let active_chat = &self.active_chat.as_ref().unwrap().0;
|
||||
let (message, is_continuation_from_previous, is_admin) =
|
||||
active_chat.update(cx, |active_chat, cx| {
|
||||
@@ -812,22 +808,30 @@ impl ChatPanel {
|
||||
.message_editor
|
||||
.update(cx, |editor, cx| editor.take_message(window, cx));
|
||||
|
||||
if let Some(id) = self.message_editor.read(cx).edit_message_id() {
|
||||
self.message_editor.update(cx, |editor, _| {
|
||||
editor.clear_edit_message_id();
|
||||
});
|
||||
match self.message_editor.read(cx).edit_message_id() {
|
||||
Some(id) => {
|
||||
self.message_editor.update(cx, |editor, _| {
|
||||
editor.clear_edit_message_id();
|
||||
});
|
||||
|
||||
if let Some(task) = chat
|
||||
.update(cx, |chat, cx| chat.update_message(id, message, cx))
|
||||
.log_err()
|
||||
{
|
||||
task.detach();
|
||||
if let Some(task) = chat
|
||||
.update(cx, |chat, cx| chat.update_message(id, message, cx))
|
||||
.log_err()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,12 +244,11 @@ impl CollabPanel {
|
||||
0,
|
||||
gpui::ListAlignment::Top,
|
||||
px(1000.),
|
||||
move |ix, window, cx| {
|
||||
if let Some(entity) = entity.upgrade() {
|
||||
move |ix, window, cx| match entity.upgrade() {
|
||||
Some(entity) => {
|
||||
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,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let project_name: SharedString = if worktree_root_names.is_empty() {
|
||||
"untitled".to_string()
|
||||
} else {
|
||||
@@ -919,7 +918,7 @@ impl CollabPanel {
|
||||
is_selected: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let id = peer_id.map_or(usize::MAX, |id| id.as_u64() as usize);
|
||||
|
||||
ListItem::new(("screen", id))
|
||||
@@ -960,7 +959,7 @@ impl CollabPanel {
|
||||
is_selected: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let has_channel_buffer_changed = channel_store.has_channel_buffer_changed(channel_id);
|
||||
ListItem::new("channel-notes")
|
||||
@@ -993,7 +992,7 @@ impl CollabPanel {
|
||||
is_selected: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let has_messages_notification = channel_store.has_new_messages(channel_id);
|
||||
ListItem::new("channel-chat")
|
||||
@@ -2278,7 +2277,7 @@ impl CollabPanel {
|
||||
&self,
|
||||
editor: &Entity<Editor>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: if editor.read(cx).read_only(cx) {
|
||||
@@ -2312,7 +2311,7 @@ impl CollabPanel {
|
||||
is_selected: bool,
|
||||
is_collapsed: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let mut channel_link = None;
|
||||
let mut channel_tooltip_text = None;
|
||||
let mut channel_icon = None;
|
||||
@@ -2411,7 +2410,7 @@ impl CollabPanel {
|
||||
calling: bool,
|
||||
is_selected: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let online = contact.online;
|
||||
let busy = contact.busy || calling;
|
||||
let github_login = SharedString::from(contact.user.github_login.clone());
|
||||
@@ -2492,7 +2491,7 @@ impl CollabPanel {
|
||||
is_incoming: bool,
|
||||
is_selected: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let github_login = SharedString::from(user.github_login.clone());
|
||||
let user_id = user.id;
|
||||
let is_response_pending = self.user_store.read(cx).is_contact_request_pending(user);
|
||||
@@ -2605,7 +2604,7 @@ impl CollabPanel {
|
||||
is_selected: bool,
|
||||
ix: usize,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let channel_id = channel.id;
|
||||
|
||||
let is_active = maybe!({
|
||||
@@ -2803,7 +2802,7 @@ impl CollabPanel {
|
||||
depth: usize,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let item = ListItem::new("channel-editor")
|
||||
.inset(false)
|
||||
// Add one level of depth for the disclosure arrow.
|
||||
@@ -2832,7 +2831,7 @@ fn render_tree_branch(
|
||||
overdraw: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let rem_size = window.rem_size();
|
||||
let line_height = window.text_style().line_height_in_pixels(rem_size);
|
||||
let width = rem_size * 1.5;
|
||||
|
||||
@@ -421,20 +421,17 @@ impl PickerDelegate for ChannelModalDelegate {
|
||||
el.child(IconButton::new("ellipsis", IconName::Ellipsis))
|
||||
})
|
||||
.when(is_me, |el| el.child(Label::new("You").color(Color::Muted)))
|
||||
.children(
|
||||
if let (Some((menu, _)), true) = (&self.context_menu, selected) {
|
||||
Some(
|
||||
deferred(
|
||||
anchored()
|
||||
.anchor(gpui::Corner::TopRight)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
.with_priority(1),
|
||||
.children(match (&self.context_menu, selected) {
|
||||
(Some((menu, _)), true) => Some(
|
||||
deferred(
|
||||
anchored()
|
||||
.anchor(gpui::Corner::TopRight)
|
||||
.child(menu.clone()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
.with_priority(1),
|
||||
),
|
||||
_ => None,
|
||||
}),
|
||||
Mode::InviteMembers => match request_status {
|
||||
Some(proto::channel_member::Kind::Invitee) => {
|
||||
slot.children(Some(Label::new("Invited")))
|
||||
|
||||
@@ -182,15 +182,14 @@ impl NotificationPanel {
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
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) })
|
||||
.await
|
||||
.log_err()
|
||||
.flatten()
|
||||
{
|
||||
Some(serde_json::from_str::<SerializedNotificationPanel>(&panel)?)
|
||||
} else {
|
||||
None
|
||||
Some(panel) => Some(serde_json::from_str::<SerializedNotificationPanel>(&panel)?),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
@@ -494,14 +493,15 @@ impl NotificationPanel {
|
||||
|
||||
if let Notification::ChannelMessageMention { channel_id, .. } = ¬ification {
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
return if let Some(panel) = workspace.read(cx).panel::<ChatPanel>(cx) {
|
||||
let panel = panel.read(cx);
|
||||
panel.is_scrolled_to_bottom()
|
||||
&& panel
|
||||
.active_chat()
|
||||
.map_or(false, |chat| chat.read(cx).channel_id.0 == *channel_id)
|
||||
} else {
|
||||
false
|
||||
return match workspace.read(cx).panel::<ChatPanel>(cx) {
|
||||
Some(panel) => {
|
||||
let panel = panel.read(cx);
|
||||
panel.is_scrolled_to_bottom()
|
||||
&& panel
|
||||
.active_chat()
|
||||
.map_or(false, |chat| chat.read(cx).channel_id.0 == *channel_id)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,10 +133,9 @@ impl CommandPaletteInterceptor {
|
||||
|
||||
/// Intercepts the given query from the command palette.
|
||||
pub fn intercept(&self, query: &str, cx: &App) -> Vec<CommandInterceptResult> {
|
||||
if let Some(handler) = self.0.as_ref() {
|
||||
(handler)(query, cx)
|
||||
} else {
|
||||
Vec::new()
|
||||
match self.0.as_ref() {
|
||||
Some(handler) => (handler)(query, cx),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ pub fn components() -> AllComponents {
|
||||
let data = COMPONENT_DATA.read();
|
||||
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 component_name = SharedString::new_static(name);
|
||||
let id = ComponentId(name);
|
||||
|
||||
@@ -244,7 +244,7 @@ impl ComponentPreview {
|
||||
ix: usize,
|
||||
entry: &PreviewEntry,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
match entry {
|
||||
PreviewEntry::Component(component_metadata) => {
|
||||
let id = component_metadata.id();
|
||||
@@ -318,7 +318,7 @@ impl ComponentPreview {
|
||||
title: SharedString,
|
||||
_window: &Window,
|
||||
_cx: &App,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.h_10()
|
||||
@@ -332,7 +332,7 @@ impl ComponentPreview {
|
||||
component: &ComponentMetadata,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let name = component.name();
|
||||
let scope = component.scope();
|
||||
|
||||
@@ -379,7 +379,7 @@ impl ComponentPreview {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_all_components(&self) -> impl IntoElement {
|
||||
fn render_all_components(&self) -> impl IntoElement + use<> {
|
||||
v_flex()
|
||||
.id("component-list")
|
||||
.px_8()
|
||||
@@ -397,7 +397,7 @@ impl ComponentPreview {
|
||||
component_id: &ComponentId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
) -> impl IntoElement + use<> {
|
||||
let component = self.component_map.get(&component_id);
|
||||
|
||||
if let Some(component) = component {
|
||||
|
||||
@@ -62,10 +62,9 @@ pub struct Client {
|
||||
pub struct ContextServerId(pub Arc<str>);
|
||||
|
||||
fn is_null_value<T: Serialize>(value: &T) -> bool {
|
||||
if let Ok(Value::Null) = serde_json::to_value(value) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
match serde_json::to_value(value) {
|
||||
Ok(Value::Null) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,10 +231,17 @@ impl Client {
|
||||
handler(Ok(message.to_string()));
|
||||
}
|
||||
}
|
||||
} else if let Ok(notification) = serde_json::from_str::<AnyNotification>(&message) {
|
||||
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());
|
||||
} else {
|
||||
match serde_json::from_str::<AnyNotification>(&message) {
|
||||
Ok(notification) => {
|
||||
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());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,47 +77,47 @@ impl Tool for ContextServerTool {
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
|
||||
let tool_name = self.tool.name.clone();
|
||||
let server_clone = server.clone();
|
||||
let input_clone = input.clone();
|
||||
match self.server_manager.read(cx).get_server(&self.server_id) {
|
||||
Some(server) => {
|
||||
let tool_name = self.tool.name.clone();
|
||||
let server_clone = server.clone();
|
||||
let input_clone = input.clone();
|
||||
|
||||
cx.spawn(async move |_cx| {
|
||||
let Some(protocol) = server_clone.client() else {
|
||||
bail!("Context server not initialized");
|
||||
};
|
||||
cx.spawn(async move |_cx| {
|
||||
let Some(protocol) = server_clone.client() else {
|
||||
bail!("Context server not initialized");
|
||||
};
|
||||
|
||||
let arguments = if let serde_json::Value::Object(map) = input_clone {
|
||||
Some(map.into_iter().collect())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let arguments = match input_clone {
|
||||
serde_json::Value::Object(map) => Some(map.into_iter().collect()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
log::trace!(
|
||||
"Running tool: {} with arguments: {:?}",
|
||||
tool_name,
|
||||
arguments
|
||||
);
|
||||
let response = protocol.run_tool(tool_name, arguments).await?;
|
||||
log::trace!(
|
||||
"Running tool: {} with arguments: {:?}",
|
||||
tool_name,
|
||||
arguments
|
||||
);
|
||||
let response = protocol.run_tool(tool_name, arguments).await?;
|
||||
|
||||
let mut result = String::new();
|
||||
for content in response.content {
|
||||
match content {
|
||||
types::ToolResponseContent::Text { text } => {
|
||||
result.push_str(&text);
|
||||
}
|
||||
types::ToolResponseContent::Image { .. } => {
|
||||
log::warn!("Ignoring image content from tool response");
|
||||
}
|
||||
types::ToolResponseContent::Resource { .. } => {
|
||||
log::warn!("Ignoring resource content from tool response");
|
||||
let mut result = String::new();
|
||||
for content in response.content {
|
||||
match content {
|
||||
types::ToolResponseContent::Text { text } => {
|
||||
result.push_str(&text);
|
||||
}
|
||||
types::ToolResponseContent::Image { .. } => {
|
||||
log::warn!("Ignoring image content from tool response");
|
||||
}
|
||||
types::ToolResponseContent::Resource { .. } => {
|
||||
log::warn!("Ignoring resource content from tool response");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("Context server not found")))
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
_ => Task::ready(Err(anyhow!("Context server not found"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::App;
|
||||
use schemars::gen::SchemaGenerator;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -348,7 +348,10 @@ impl Copilot {
|
||||
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) {
|
||||
CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })),
|
||||
_ => None,
|
||||
@@ -553,24 +556,27 @@ impl Copilot {
|
||||
}
|
||||
|
||||
pub fn sign_in(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
if let CopilotServer::Running(server) = &mut self.server {
|
||||
let task = match &server.sign_in_status {
|
||||
SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(),
|
||||
SignInStatus::SigningIn { task, .. } => {
|
||||
cx.notify();
|
||||
task.clone()
|
||||
}
|
||||
SignInStatus::SignedOut { .. } | SignInStatus::Unauthorized { .. } => {
|
||||
let lsp = server.lsp.clone();
|
||||
let task = cx
|
||||
.spawn(async move |this, cx| {
|
||||
let sign_in = async {
|
||||
let sign_in = lsp
|
||||
.request::<request::SignInInitiate>(
|
||||
request::SignInInitiateParams {},
|
||||
)
|
||||
.await?;
|
||||
match sign_in {
|
||||
match &mut self.server {
|
||||
CopilotServer::Running(server) => {
|
||||
let task =
|
||||
match &server.sign_in_status {
|
||||
SignInStatus::Authorized { .. } => Task::ready(Ok(())).shared(),
|
||||
SignInStatus::SigningIn { task, .. } => {
|
||||
cx.notify();
|
||||
task.clone()
|
||||
}
|
||||
SignInStatus::SignedOut { .. } | SignInStatus::Unauthorized { .. } => {
|
||||
let lsp = server.lsp.clone();
|
||||
let task = cx
|
||||
.spawn(async move |this, cx| {
|
||||
let sign_in =
|
||||
async {
|
||||
let sign_in = lsp
|
||||
.request::<request::SignInInitiate>(
|
||||
request::SignInInitiateParams {},
|
||||
)
|
||||
.await?;
|
||||
match sign_in {
|
||||
request::SignInInitiateResult::AlreadySignedIn { user } => {
|
||||
Ok(request::SignInStatus::Ok { user: Some(user) })
|
||||
}
|
||||
@@ -601,38 +607,40 @@ impl Copilot {
|
||||
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(),
|
||||
};
|
||||
|
||||
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();
|
||||
task
|
||||
}
|
||||
};
|
||||
cx.notify();
|
||||
task
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
Task::ready(Err(anyhow!("copilot hasn't started yet")))
|
||||
cx.background_spawn(task.map_err(|err| anyhow!("{:?}", err)))
|
||||
}
|
||||
_ => {
|
||||
// If we're downloading, wait until download is finished
|
||||
// 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>> {
|
||||
if let CopilotServer::Running(server) = &self.server {
|
||||
Some(&server.lsp)
|
||||
} else {
|
||||
None
|
||||
match &self.server {
|
||||
CopilotServer::Running(server) => Some(&server.lsp),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1078,7 +1078,7 @@ mod tests {
|
||||
cx: &mut EditorLspTestContext,
|
||||
marked_string: &str,
|
||||
completions: Vec<&'static str>,
|
||||
) -> impl Future<Output = ()> {
|
||||
) -> impl Future<Output = ()> + use<> {
|
||||
let complete_from_marker: TextRangeMarker = '|'.into();
|
||||
let replace_range_marker: TextRangeMarker = ('<', '>').into();
|
||||
let (_, mut marked_ranges) = marked_text_ranges_by(
|
||||
|
||||
@@ -139,7 +139,10 @@ impl CopilotCodeVerification {
|
||||
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
|
||||
.read_from_clipboard()
|
||||
.map(|item| item.text().as_ref() == Some(&data.user_code))
|
||||
@@ -172,7 +175,7 @@ impl CopilotCodeVerification {
|
||||
data: &PromptUserDeviceFlow,
|
||||
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl Element {
|
||||
) -> impl Element + use<> {
|
||||
let connect_button_label = if connect_clicked {
|
||||
"Waiting for connection..."
|
||||
} 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()
|
||||
.gap_2()
|
||||
.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()
|
||||
.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()
|
||||
.size_8()
|
||||
.path(IconName::ArrowCircle.path())
|
||||
|
||||
@@ -222,13 +222,12 @@ impl TransportDelegate {
|
||||
}
|
||||
|
||||
pub(crate) async fn send_message(&self, message: Message) -> Result<()> {
|
||||
if let Some(server_tx) = self.server_tx.lock().await.as_ref() {
|
||||
server_tx
|
||||
match self.server_tx.lock().await.as_ref() {
|
||||
Some(server_tx) => server_tx
|
||||
.send(message)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to send message: {}", e))
|
||||
} else {
|
||||
Err(anyhow!("Server tx already dropped"))
|
||||
.map_err(|e| anyhow!("Failed to send message: {}", e)),
|
||||
_ => Err(anyhow!("Server tx already dropped")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,12 +342,15 @@ impl TransportDelegate {
|
||||
|
||||
match message {
|
||||
Ok(Message::Response(res)) => {
|
||||
if let Some(tx) = pending_requests.lock().await.remove(&res.request_seq) {
|
||||
if let Err(e) = tx.send(Self::process_response(res)) {
|
||||
log::trace!("Did not send response `{:?}` for a cancelled", e);
|
||||
match pending_requests.lock().await.remove(&res.request_seq) {
|
||||
Some(tx) => {
|
||||
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) => {
|
||||
@@ -816,22 +818,25 @@ impl FakeTransport {
|
||||
.unwrap();
|
||||
writer.flush().await.unwrap();
|
||||
} else {
|
||||
if let Some(handle) = request_handlers
|
||||
match request_handlers
|
||||
.lock()
|
||||
.await
|
||||
.get_mut(request.command.as_str())
|
||||
{
|
||||
handle(
|
||||
request.seq,
|
||||
request.arguments.unwrap_or(json!({})),
|
||||
stdout_writer.clone(),
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
log::error!(
|
||||
"No request handler for {}",
|
||||
request.command
|
||||
);
|
||||
Some(handle) => {
|
||||
handle(
|
||||
request.seq,
|
||||
request.arguments.unwrap_or(json!({})),
|
||||
stdout_writer.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
_ => {
|
||||
log::error!(
|
||||
"No request handler for {}",
|
||||
request.command
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -850,14 +855,20 @@ impl FakeTransport {
|
||||
writer.flush().await.unwrap();
|
||||
}
|
||||
Message::Response(response) => {
|
||||
if let Some(handle) = response_handlers
|
||||
match response_handlers
|
||||
.lock()
|
||||
.await
|
||||
.get(response.command.as_str())
|
||||
{
|
||||
handle(response);
|
||||
} else {
|
||||
log::error!("No response handler for {}", response.command);
|
||||
Some(handle) => {
|
||||
handle(response);
|
||||
}
|
||||
_ => {
|
||||
log::error!(
|
||||
"No response handler for {}",
|
||||
response.command
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,17 +94,16 @@ impl DebugAdapter for PythonDebugAdapter {
|
||||
)
|
||||
.await;
|
||||
|
||||
let python_path = if let Some(toolchain) = toolchain {
|
||||
Some(toolchain.path.to_string())
|
||||
} else {
|
||||
BINARY_NAMES
|
||||
let python_path = match toolchain {
|
||||
Some(toolchain) => Some(toolchain.path.to_string()),
|
||||
_ => BINARY_NAMES
|
||||
.iter()
|
||||
.filter_map(|cmd| {
|
||||
delegate
|
||||
.which(OsStr::new(cmd))
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
})
|
||||
.find(|_| true)
|
||||
.find(|_| true),
|
||||
};
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
|
||||
@@ -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
|
||||
#[macro_export]
|
||||
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>);
|
||||
|
||||
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)))
|
||||
});
|
||||
};
|
||||
(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 )>);
|
||||
|
||||
impl ::std::ops::Deref for $t {
|
||||
|
||||
@@ -284,7 +284,7 @@ impl Render for 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 text_style = TextStyle {
|
||||
color: cx.theme().colors().text,
|
||||
|
||||
@@ -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 text_style = TextStyle {
|
||||
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 text_style = TextStyle {
|
||||
color: if self.console.read(cx).read_only(cx) {
|
||||
|
||||
@@ -807,14 +807,16 @@ impl VariableList {
|
||||
)
|
||||
.when(!dap.value.is_empty(), |this| {
|
||||
this.child(div().w_full().id(variable.item_value_id()).map(|this| {
|
||||
if let Some((_, editor)) = self
|
||||
match self
|
||||
.edited_path
|
||||
.as_ref()
|
||||
.filter(|(path, _)| path == &variable.path)
|
||||
{
|
||||
this.child(div().size_full().px_2().child(editor.clone()))
|
||||
} else {
|
||||
this.text_color(cx.theme().colors().text_muted)
|
||||
Some((_, editor)) => {
|
||||
this.child(div().size_full().px_2().child(editor.clone()))
|
||||
}
|
||||
_ => this
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.when(
|
||||
!self.disabled
|
||||
&& self
|
||||
@@ -853,7 +855,7 @@ impl VariableList {
|
||||
.when_some(variable_color, |this, color| {
|
||||
this.color(Color::from(color))
|
||||
}),
|
||||
)
|
||||
),
|
||||
}
|
||||
}))
|
||||
}),
|
||||
|
||||
@@ -32,12 +32,9 @@ impl StartingState {
|
||||
let _notify_parent = cx.spawn(async move |this, cx| {
|
||||
let entity = task.await;
|
||||
|
||||
this.update(cx, |_, cx| {
|
||||
if let Ok(entity) = entity {
|
||||
cx.emit(StartingEvent::Finished(entity))
|
||||
} else {
|
||||
cx.emit(StartingEvent::Failed)
|
||||
}
|
||||
this.update(cx, |_, cx| match entity {
|
||||
Ok(entity) => cx.emit(StartingEvent::Finished(entity)),
|
||||
_ => cx.emit(StartingEvent::Failed),
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
|
||||
@@ -305,29 +305,32 @@ impl ProjectDiagnosticsEditor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||
let is_active = workspace
|
||||
.active_item(cx)
|
||||
.is_some_and(|item| item.item_id() == existing.item_id());
|
||||
workspace.activate_item(&existing, true, !is_active, window, cx);
|
||||
} else {
|
||||
let workspace_handle = cx.entity().downgrade();
|
||||
match workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||
Some(existing) => {
|
||||
let is_active = workspace
|
||||
.active_item(cx)
|
||||
.is_some_and(|item| item.item_id() == existing.item_id());
|
||||
workspace.activate_item(&existing, true, !is_active, window, cx);
|
||||
}
|
||||
_ => {
|
||||
let workspace_handle = cx.entity().downgrade();
|
||||
|
||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
||||
Some(include_warnings) => include_warnings.0,
|
||||
None => ProjectSettings::get_global(cx).diagnostics.include_warnings,
|
||||
};
|
||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
||||
Some(include_warnings) => include_warnings.0,
|
||||
None => ProjectSettings::get_global(cx).diagnostics.include_warnings,
|
||||
};
|
||||
|
||||
let diagnostics = cx.new(|cx| {
|
||||
ProjectDiagnosticsEditor::new(
|
||||
workspace.project().clone(),
|
||||
include_warnings,
|
||||
workspace_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, window, cx);
|
||||
let diagnostics = cx.new(|cx| {
|
||||
ProjectDiagnosticsEditor::new(
|
||||
workspace.project().clone(),
|
||||
include_warnings,
|
||||
workspace_handle,
|
||||
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 {
|
||||
let mut group_state = DiagnosticGroupState {
|
||||
language_server_id,
|
||||
primary_diagnostic: group.entries[group.primary_ix].clone(),
|
||||
primary_excerpt_ix: 0,
|
||||
excerpts: Default::default(),
|
||||
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
|
||||
match to_insert {
|
||||
Some((language_server_id, group)) => {
|
||||
let mut group_state = DiagnosticGroupState {
|
||||
language_server_id,
|
||||
primary_diagnostic: group.entries[group.primary_ix].clone(),
|
||||
primary_excerpt_ix: 0,
|
||||
excerpts: Default::default(),
|
||||
blocks: Default::default(),
|
||||
block_count: 0,
|
||||
};
|
||||
if let Some((range, context_range, start_ix)) = &mut pending_range {
|
||||
if let Some(expanded_range) = expanded_range.clone() {
|
||||
// If the entries are overlapping or next to each-other, merge them into one excerpt.
|
||||
if context_range.end.row + 1 >= expanded_range.start.row {
|
||||
context_range.end = context_range.end.max(expanded_range.end);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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 = match &resolved_entry {
|
||||
Some(entry) => Some(
|
||||
context_range_for_entry(
|
||||
entry.range.clone(),
|
||||
context,
|
||||
snapshot.clone(),
|
||||
(**cx).clone(),
|
||||
)
|
||||
.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;
|
||||
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();
|
||||
.await,
|
||||
),
|
||||
_ => None,
|
||||
};
|
||||
if let Some((range, context_range, start_ix)) = &mut pending_range {
|
||||
if let Some(expanded_range) = expanded_range.clone() {
|
||||
// If the entries are overlapping or next to each-other, merge them into one excerpt.
|
||||
if context_range.end.row + 1 >= expanded_range.start.row {
|
||||
context_range.end =
|
||||
context_range.end.max(expanded_range.end);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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),
|
||||
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() {
|
||||
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() {
|
||||
let range = entry.range.clone();
|
||||
pending_range = Some((range, expanded_range.unwrap(), ix));
|
||||
}
|
||||
this.update(cx, |this, _| {
|
||||
new_group_ixs.push(this.path_states[path_ix].diagnostic_groups.len());
|
||||
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, _| {
|
||||
new_group_ixs.push(this.path_states[path_ix].diagnostic_groups.len());
|
||||
this.path_states[path_ix]
|
||||
.diagnostic_groups
|
||||
.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)
|
||||
})?;
|
||||
this.update(cx, |this, _| {
|
||||
this.path_states[path_ix]
|
||||
.diagnostic_groups
|
||||
.push(group_state)
|
||||
})?;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,26 +62,27 @@ impl Render for DiagnosticIndicator {
|
||||
.child(Label::new(warning_count.to_string()).size(LabelSize::Small)),
|
||||
};
|
||||
|
||||
let status = if let Some(diagnostic) = &self.current_diagnostic {
|
||||
let message = diagnostic.message.split('\n').next().unwrap().to_string();
|
||||
Some(
|
||||
Button::new("diagnostic_message", message)
|
||||
.label_size(LabelSize::Small)
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::for_action(
|
||||
"Next Diagnostic",
|
||||
&editor::actions::GoToDiagnostic,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.go_to_next_diagnostic(window, cx);
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
let status = match &self.current_diagnostic {
|
||||
Some(diagnostic) => {
|
||||
let message = diagnostic.message.split('\n').next().unwrap().to_string();
|
||||
Some(
|
||||
Button::new("diagnostic_message", message)
|
||||
.label_size(LabelSize::Small)
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::for_action(
|
||||
"Next Diagnostic",
|
||||
&editor::actions::GoToDiagnostic,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.go_to_next_diagnostic(window, cx);
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
h_flex()
|
||||
@@ -187,14 +188,17 @@ impl StatusItemView for DiagnosticIndicator {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
|
||||
self.active_editor = Some(editor.downgrade());
|
||||
self._observe_active_editor = Some(cx.observe_in(&editor, window, Self::update));
|
||||
self.update(editor, window, cx);
|
||||
} else {
|
||||
self.active_editor = None;
|
||||
self.current_diagnostic = None;
|
||||
self._observe_active_editor = None;
|
||||
match active_pane_item.and_then(|item| item.downcast::<Editor>()) {
|
||||
Some(editor) => {
|
||||
self.active_editor = Some(editor.downgrade());
|
||||
self._observe_active_editor = Some(cx.observe_in(&editor, window, Self::update));
|
||||
self.update(editor, window, cx);
|
||||
}
|
||||
_ => {
|
||||
self.active_editor = None;
|
||||
self.current_diagnostic = None;
|
||||
self._observe_active_editor = None;
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -83,11 +83,12 @@ impl ToolbarItemView for ToolbarControls {
|
||||
_: &mut Context<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
if let Some(pane_item) = active_pane_item.as_ref() {
|
||||
if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
|
||||
self.editor = Some(editor.downgrade());
|
||||
ToolbarItemLocation::PrimaryRight
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
match pane_item.downcast::<ProjectDiagnosticsEditor>() {
|
||||
Some(editor) => {
|
||||
self.editor = Some(editor.downgrade());
|
||||
ToolbarItemLocation::PrimaryRight
|
||||
}
|
||||
_ => ToolbarItemLocation::Hidden,
|
||||
}
|
||||
} else {
|
||||
ToolbarItemLocation::Hidden
|
||||
|
||||
@@ -21,10 +21,13 @@ fn main() -> Result<()> {
|
||||
let preprocessor =
|
||||
ZedDocsPreprocessor::new().context("Failed to create ZedDocsPreprocessor")?;
|
||||
|
||||
if let Some(sub_args) = matches.subcommand_matches("supports") {
|
||||
handle_supports(&preprocessor, sub_args);
|
||||
} else {
|
||||
handle_preprocessing(&preprocessor)?;
|
||||
match matches.subcommand_matches("supports") {
|
||||
Some(sub_args) => {
|
||||
handle_supports(&preprocessor, sub_args);
|
||||
}
|
||||
_ => {
|
||||
handle_preprocessing(&preprocessor)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -735,12 +735,12 @@ impl CompletionsMenu {
|
||||
|
||||
let completion = &completions[mat.candidate_id];
|
||||
let sort_key = completion.sort_key();
|
||||
let sort_text =
|
||||
if let CompletionSource::Lsp { lsp_completion, .. } = &completion.source {
|
||||
let sort_text = match &completion.source {
|
||||
CompletionSource::Lsp { lsp_completion, .. } => {
|
||||
lsp_completion.sort_text.as_deref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let score = Reverse(OrderedFloat(mat.score));
|
||||
|
||||
if mat.score >= 0.2 {
|
||||
|
||||
@@ -49,7 +49,7 @@ impl<'a> CommitAvatar<'a> {
|
||||
&'a self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<CommitTooltip>,
|
||||
) -> Option<impl IntoElement> {
|
||||
) -> Option<impl IntoElement + use<>> {
|
||||
let remote = self
|
||||
.commit
|
||||
.message
|
||||
|
||||
@@ -226,26 +226,22 @@ impl DisplayMap {
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
let mut block_map = self.block_map.write(snapshot, edits);
|
||||
let blocks = creases.into_iter().filter_map(|crease| {
|
||||
if let Crease::Block {
|
||||
let blocks = creases.into_iter().filter_map(|crease| match crease {
|
||||
Crease::Block {
|
||||
range,
|
||||
block_height,
|
||||
render_block,
|
||||
block_style,
|
||||
block_priority,
|
||||
..
|
||||
} = crease
|
||||
{
|
||||
Some((
|
||||
range,
|
||||
render_block,
|
||||
block_height,
|
||||
block_style,
|
||||
block_priority,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} => Some((
|
||||
range,
|
||||
render_block,
|
||||
block_height,
|
||||
block_style,
|
||||
block_priority,
|
||||
)),
|
||||
_ => None,
|
||||
});
|
||||
block_map.insert(
|
||||
blocks
|
||||
@@ -954,10 +950,9 @@ impl DisplaySnapshot {
|
||||
for chunk in self.highlighted_chunks(range, false, editor_style) {
|
||||
line.push_str(chunk.text);
|
||||
|
||||
let text_style = if let Some(style) = chunk.style {
|
||||
Cow::Owned(editor_style.text.clone().highlight(style))
|
||||
} else {
|
||||
Cow::Borrowed(&editor_style.text)
|
||||
let text_style = match chunk.style {
|
||||
Some(style) => Cow::Owned(editor_style.text.clone().highlight(style)),
|
||||
_ => Cow::Borrowed(&editor_style.text),
|
||||
};
|
||||
|
||||
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>> {
|
||||
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
|
||||
if let Some(crease) = self
|
||||
match self
|
||||
.crease_snapshot
|
||||
.query_row(buffer_row, &self.buffer_snapshot)
|
||||
{
|
||||
match crease {
|
||||
Some(crease) => match crease {
|
||||
Crease::Inline {
|
||||
range,
|
||||
placeholder,
|
||||
@@ -1219,52 +1214,55 @@ impl DisplaySnapshot {
|
||||
block_priority: *block_priority,
|
||||
render_toggle: render_toggle.clone(),
|
||||
}),
|
||||
}
|
||||
} else 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()
|
||||
},
|
||||
_ => {
|
||||
if self.starts_indent(MultiBufferRow(start.row))
|
||||
&& !self.is_line_folded(MultiBufferRow(start.row))
|
||||
{
|
||||
let prev_row = row - 1;
|
||||
end = Some(Point::new(
|
||||
prev_row,
|
||||
self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
|
||||
));
|
||||
break;
|
||||
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;
|
||||
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| {
|
||||
if rng.gen() {
|
||||
if rng.r#gen() {
|
||||
let len = rng.gen_range(0..10);
|
||||
let text = util::RandomCharIter::new(&mut rng)
|
||||
.take(len)
|
||||
@@ -1542,7 +1540,7 @@ pub mod tests {
|
||||
}
|
||||
30..=44 => {
|
||||
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 block_properties = (0..rng.gen_range(1..=1))
|
||||
.map(|_| {
|
||||
@@ -1552,7 +1550,7 @@ pub mod tests {
|
||||
Bias::Left,
|
||||
));
|
||||
|
||||
let placement = if rng.gen() {
|
||||
let placement = if rng.r#gen() {
|
||||
BlockPlacement::Above(position)
|
||||
} else {
|
||||
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);
|
||||
map.update(cx, |map, cx| {
|
||||
map.unfold_intersecting(ranges, true, cx);
|
||||
|
||||
@@ -1351,11 +1351,14 @@ impl BlockSnapshot {
|
||||
{
|
||||
break;
|
||||
}
|
||||
if let Some(block) = &transform.block {
|
||||
cursor.next(&());
|
||||
return Some((start_row, block));
|
||||
} else {
|
||||
cursor.next(&());
|
||||
match &transform.block {
|
||||
Some(block) => {
|
||||
cursor.next(&());
|
||||
return Some((start_row, block));
|
||||
}
|
||||
_ => {
|
||||
cursor.next(&());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -1405,12 +1408,17 @@ impl BlockSnapshot {
|
||||
cursor.seek(&wrap_row, Bias::Left, &());
|
||||
|
||||
while let Some(transform) = cursor.item() {
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.id() == block_id {
|
||||
return Some(block.clone());
|
||||
match transform.block.as_ref() {
|
||||
Some(block) => {
|
||||
if block.id() == block_id {
|
||||
return Some(block.clone());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if *cursor.start() > wrap_row {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if *cursor.start() > wrap_row {
|
||||
break;
|
||||
}
|
||||
|
||||
cursor.next(&());
|
||||
@@ -1482,18 +1490,23 @@ impl BlockSnapshot {
|
||||
pub(super) fn line_len(&self, row: BlockRow) -> u32 {
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||
cursor.seek(&BlockRow(row.0), Bias::Right, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
let (output_start, input_start) = cursor.start();
|
||||
let overshoot = row.0 - output_start.0;
|
||||
if transform.block.is_some() {
|
||||
0
|
||||
} else {
|
||||
self.wrap_snapshot.line_len(input_start.0 + overshoot)
|
||||
match cursor.item() {
|
||||
Some(transform) => {
|
||||
let (output_start, input_start) = cursor.start();
|
||||
let overshoot = row.0 - output_start.0;
|
||||
if transform.block.is_some() {
|
||||
0
|
||||
} 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;
|
||||
|
||||
loop {
|
||||
if let Some(transform) = cursor.item() {
|
||||
let (output_start_row, input_start_row) = cursor.start();
|
||||
let (output_end_row, input_end_row) = cursor.end(&());
|
||||
let output_start = Point::new(output_start_row.0, 0);
|
||||
let input_start = Point::new(input_start_row.0, 0);
|
||||
let input_end = Point::new(input_end_row.0, 0);
|
||||
match cursor.item() {
|
||||
Some(transform) => {
|
||||
let (output_start_row, input_start_row) = cursor.start();
|
||||
let (output_end_row, input_end_row) = cursor.end(&());
|
||||
let output_start = Point::new(output_start_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() {
|
||||
Some(block) => {
|
||||
if block.is_replacement() {
|
||||
if ((bias == Bias::Left || search_left) && output_start <= point.0)
|
||||
|| (!search_left && output_start >= point.0)
|
||||
{
|
||||
return BlockPoint(output_start);
|
||||
match transform.block.as_ref() {
|
||||
Some(block) => {
|
||||
if block.is_replacement() {
|
||||
if ((bias == Bias::Left || search_left) && output_start <= point.0)
|
||||
|| (!search_left && output_start >= point.0)
|
||||
{
|
||||
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) {
|
||||
let input_overshoot = input_point.0.saturating_sub(input_start);
|
||||
return BlockPoint(output_start + input_overshoot);
|
||||
}
|
||||
if search_left {
|
||||
cursor.prev(&());
|
||||
} else {
|
||||
cursor.next(&());
|
||||
}
|
||||
}
|
||||
|
||||
if search_left {
|
||||
cursor.prev(&());
|
||||
} else {
|
||||
cursor.next(&());
|
||||
_ => {
|
||||
if reversed {
|
||||
return self.max_point();
|
||||
} else {
|
||||
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 {
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
if transform.block.is_some() {
|
||||
BlockPoint::new(cursor.start().1 .0, 0)
|
||||
} else {
|
||||
let (input_start_row, output_start_row) = cursor.start();
|
||||
let input_start = Point::new(input_start_row.0, 0);
|
||||
let output_start = Point::new(output_start_row.0, 0);
|
||||
let input_overshoot = wrap_point.0 - input_start;
|
||||
BlockPoint(output_start + input_overshoot)
|
||||
match cursor.item() {
|
||||
Some(transform) => {
|
||||
if transform.block.is_some() {
|
||||
BlockPoint::new(cursor.start().1 .0, 0)
|
||||
} else {
|
||||
let (input_start_row, output_start_row) = cursor.start();
|
||||
let input_start = Point::new(input_start_row.0, 0);
|
||||
let output_start = Point::new(output_start_row.0, 0);
|
||||
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 {
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||
cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
match transform.block.as_ref() {
|
||||
match cursor.item() {
|
||||
Some(transform) => match transform.block.as_ref() {
|
||||
Some(block) => {
|
||||
if block.place_below() {
|
||||
let wrap_row = cursor.start().1 .0 - 1;
|
||||
@@ -1627,9 +1646,8 @@ impl BlockSnapshot {
|
||||
let wrap_row = cursor.start().1 .0 + overshoot;
|
||||
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 let Some(input_chunk) = self.input_chunks.next() {
|
||||
self.input_chunk = input_chunk;
|
||||
} else {
|
||||
if self.output_row < self.max_output_row {
|
||||
self.output_row += 1;
|
||||
self.advance();
|
||||
if self.transforms.item().is_some() {
|
||||
return Some(Chunk {
|
||||
text: "\n",
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
match self.input_chunks.next() {
|
||||
Some(input_chunk) => {
|
||||
self.input_chunk = input_chunk;
|
||||
}
|
||||
_ => {
|
||||
if self.output_row < self.max_output_row {
|
||||
self.output_row += 1;
|
||||
self.advance();
|
||||
if self.transforms.item().is_some() {
|
||||
return Some(Chunk {
|
||||
text: "\n",
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1783,18 +1804,19 @@ impl Iterator for BlockRows<'_> {
|
||||
}
|
||||
|
||||
let transform = self.transforms.item()?;
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.is_replacement() && self.transforms.start().0 == self.output_row {
|
||||
if matches!(block, Block::FoldedBuffer { .. }) {
|
||||
Some(RowInfo::default())
|
||||
match transform.block.as_ref() {
|
||||
Some(block) => {
|
||||
if block.is_replacement() && self.transforms.start().0 == self.output_row {
|
||||
if matches!(block, Block::FoldedBuffer { .. }) {
|
||||
Some(RowInfo::default())
|
||||
} else {
|
||||
Some(self.input_rows.next().unwrap())
|
||||
}
|
||||
} 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!("Excerpt Header Height: {:?}", excerpt_header_height);
|
||||
let is_singleton = rng.gen();
|
||||
let is_singleton = rng.r#gen();
|
||||
let buffer = if is_singleton {
|
||||
let len = rng.gen_range(0..10);
|
||||
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
|
||||
|
||||
@@ -602,21 +602,24 @@ impl FoldSnapshot {
|
||||
if let Some(transform) = cursor.item() {
|
||||
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;
|
||||
if let Some(placeholder) = transform.placeholder.as_ref() {
|
||||
summary = TextSummary::from(
|
||||
&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_end = self
|
||||
.inlay_snapshot
|
||||
.to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform));
|
||||
summary = self
|
||||
.inlay_snapshot
|
||||
.text_summary_for_range(inlay_start..inlay_end);
|
||||
match transform.placeholder.as_ref() {
|
||||
Some(placeholder) => {
|
||||
summary = TextSummary::from(
|
||||
&placeholder.text
|
||||
[start_in_transform.column as usize..end_in_transform.column as usize],
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
let inlay_start = self
|
||||
.inlay_snapshot
|
||||
.to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform));
|
||||
let inlay_end = self
|
||||
.inlay_snapshot
|
||||
.to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform));
|
||||
summary = self
|
||||
.inlay_snapshot
|
||||
.text_summary_for_range(inlay_start..inlay_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,17 +630,21 @@ impl FoldSnapshot {
|
||||
.output;
|
||||
if let Some(transform) = cursor.item() {
|
||||
let end_in_transform = range.end.0 - cursor.start().0 .0;
|
||||
if let Some(placeholder) = transform.placeholder.as_ref() {
|
||||
summary +=
|
||||
TextSummary::from(&placeholder.text[..end_in_transform.column as usize]);
|
||||
} else {
|
||||
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));
|
||||
summary += self
|
||||
.inlay_snapshot
|
||||
.text_summary_for_range(inlay_start..inlay_end);
|
||||
match transform.placeholder.as_ref() {
|
||||
Some(placeholder) => {
|
||||
summary += TextSummary::from(
|
||||
&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));
|
||||
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 {
|
||||
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
if let Some(transform) = cursor.item() {
|
||||
let transform_start = cursor.start().0 .0;
|
||||
if transform.placeholder.is_some() {
|
||||
if point.0 == transform_start || matches!(bias, Bias::Left) {
|
||||
FoldPoint(transform_start)
|
||||
match cursor.item() {
|
||||
Some(transform) => {
|
||||
let transform_start = cursor.start().0 .0;
|
||||
if transform.placeholder.is_some() {
|
||||
if point.0 == transform_start || matches!(bias, Bias::Left) {
|
||||
FoldPoint(transform_start)
|
||||
} else {
|
||||
FoldPoint(cursor.end(&()).0 .0)
|
||||
}
|
||||
} 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 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)
|
||||
} else {
|
||||
MultiBuffer::build_random(&mut rng, cx)
|
||||
@@ -1962,7 +1970,7 @@ mod tests {
|
||||
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
|
||||
to_unfold.push(start..end);
|
||||
}
|
||||
let inclusive = rng.gen();
|
||||
let inclusive = rng.r#gen();
|
||||
log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
|
||||
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
|
||||
snapshot_edits.push((snapshot, edits));
|
||||
|
||||
@@ -610,9 +610,9 @@ impl InlayMap {
|
||||
let mut to_insert = Vec::new();
|
||||
let snapshot = &mut self.snapshot;
|
||||
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 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) {
|
||||
0
|
||||
} else {
|
||||
@@ -809,33 +809,39 @@ impl InlaySnapshot {
|
||||
match cursor.item() {
|
||||
Some(Transform::Isomorphic(transform)) => {
|
||||
if cursor.start().0 == point {
|
||||
if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
|
||||
if inlay.position.bias() == Bias::Left {
|
||||
return point;
|
||||
} else if bias == Bias::Left {
|
||||
cursor.prev(&());
|
||||
} else if transform.first_line_chars == 0 {
|
||||
point.0 += Point::new(1, 0);
|
||||
} else {
|
||||
point.0 += Point::new(0, 1);
|
||||
match cursor.prev_item() {
|
||||
Some(Transform::Inlay(inlay)) => {
|
||||
if inlay.position.bias() == Bias::Left {
|
||||
return point;
|
||||
} else if bias == Bias::Left {
|
||||
cursor.prev(&());
|
||||
} else if transform.first_line_chars == 0 {
|
||||
point.0 += Point::new(1, 0);
|
||||
} else {
|
||||
point.0 += Point::new(0, 1);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return point;
|
||||
}
|
||||
} else {
|
||||
return point;
|
||||
}
|
||||
} else if cursor.end(&()).0 == point {
|
||||
if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
|
||||
if inlay.position.bias() == Bias::Right {
|
||||
return point;
|
||||
} else if bias == Bias::Right {
|
||||
cursor.next(&());
|
||||
} else if point.0.column == 0 {
|
||||
point.0.row -= 1;
|
||||
point.0.column = self.line_len(point.0.row);
|
||||
} else {
|
||||
point.0.column -= 1;
|
||||
match cursor.next_item() {
|
||||
Some(Transform::Inlay(inlay)) => {
|
||||
if inlay.position.bias() == Bias::Right {
|
||||
return point;
|
||||
} else if bias == Bias::Right {
|
||||
cursor.next(&());
|
||||
} else if point.0.column == 0 {
|
||||
point.0.row -= 1;
|
||||
point.0.column = self.line_len(point.0.row);
|
||||
} else {
|
||||
point.0.column -= 1;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return point;
|
||||
}
|
||||
} else {
|
||||
return point;
|
||||
}
|
||||
} else {
|
||||
let overshoot = point.0 - cursor.start().0 .0;
|
||||
@@ -1500,7 +1506,7 @@ mod tests {
|
||||
.unwrap_or(10);
|
||||
|
||||
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)
|
||||
.take(len)
|
||||
.collect::<String>();
|
||||
@@ -1709,19 +1715,22 @@ mod tests {
|
||||
buffer_point
|
||||
);
|
||||
|
||||
if let Some(ch) = buffer_chars.next() {
|
||||
if ch == '\n' {
|
||||
buffer_point += Point::new(1, 0);
|
||||
} else {
|
||||
buffer_point += Point::new(0, ch.len_utf8() as u32);
|
||||
}
|
||||
match buffer_chars.next() {
|
||||
Some(ch) => {
|
||||
if ch == '\n' {
|
||||
buffer_point += Point::new(1, 0);
|
||||
} 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.
|
||||
let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
|
||||
assert!(new_inlay_point > inlay_point);
|
||||
inlay_point = new_inlay_point;
|
||||
} else {
|
||||
break;
|
||||
// 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);
|
||||
assert!(new_inlay_point > inlay_point);
|
||||
inlay_point = new_inlay_point;
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -534,15 +534,18 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.chunk.text.is_empty() {
|
||||
if let Some(chunk) = self.fold_chunks.next() {
|
||||
self.chunk = chunk;
|
||||
if self.inside_leading_tab {
|
||||
self.chunk.text = &self.chunk.text[1..];
|
||||
self.inside_leading_tab = false;
|
||||
self.input_column += 1;
|
||||
match self.fold_chunks.next() {
|
||||
Some(chunk) => {
|
||||
self.chunk = chunk;
|
||||
if self.inside_leading_tab {
|
||||
self.chunk.text = &self.chunk.text[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) {
|
||||
let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
|
||||
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)
|
||||
.take(len)
|
||||
.collect::<String>();
|
||||
|
||||
@@ -1207,7 +1207,7 @@ mod tests {
|
||||
log::info!("Wrap width: {:?}", wrap_width);
|
||||
|
||||
let buffer = cx.update(|cx| {
|
||||
if rng.gen() {
|
||||
if rng.r#gen() {
|
||||
MultiBuffer::build_random(&mut rng, cx)
|
||||
} else {
|
||||
let len = rng.gen_range(0..10);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9526,15 +9526,17 @@ async fn test_word_completion(cx: &mut TestAppContext) {
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
completion_menu_entries(&menu),
|
||||
&["first", "last"],
|
||||
"When LSP server is fast to reply, no fallback word completions are used"
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert_eq!(
|
||||
completion_menu_entries(&menu),
|
||||
&["first", "last"],
|
||||
"When LSP server is fast to reply, no fallback word completions are used"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
}
|
||||
editor.cancel(&Cancel, window, cx);
|
||||
});
|
||||
@@ -9550,13 +9552,13 @@ async fn test_word_completion(cx: &mut TestAppContext) {
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
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"],
|
||||
"When LSP server is slow, document words can be shown instead, if configured accordingly");
|
||||
} else {
|
||||
} _ => {
|
||||
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())
|
||||
.await;
|
||||
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),
|
||||
&["first", "last", "second"],
|
||||
"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");
|
||||
}
|
||||
}}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9663,34 +9665,36 @@ async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
|
||||
cx.executor().run_until_parked();
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
cx.update_editor(|editor, _, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
completion_menu_entries(&menu),
|
||||
&["first", "last", "second"],
|
||||
"`ShowWordCompletions` action should show word completions"
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
cx.update_editor(
|
||||
|editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert_eq!(
|
||||
completion_menu_entries(&menu),
|
||||
&["first", "last", "second"],
|
||||
"`ShowWordCompletions` action should show word completions"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cx.simulate_keystroke("l");
|
||||
cx.executor().run_until_parked();
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
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),
|
||||
&["last"],
|
||||
"After showing word completions, further editing should filter them and not query the LSP"
|
||||
);
|
||||
} else {
|
||||
} _ => {
|
||||
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())
|
||||
.await;
|
||||
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!(
|
||||
completion_menu_entries(&menu),
|
||||
&["let"],
|
||||
"With no digits in the completion query, no digits should be in the word completions"
|
||||
);
|
||||
} else {
|
||||
} _ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
}}
|
||||
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())
|
||||
.await;
|
||||
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, \
|
||||
return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
|
||||
} else {
|
||||
} _ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
}}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9914,8 +9918,8 @@ async fn test_multiline_completion(cx: &mut TestAppContext) {
|
||||
|
||||
editor.update(cx, |editor, _| {
|
||||
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
|
||||
.completions
|
||||
.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:?}"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
} _ => {
|
||||
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.executor().run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, _, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
cx.update_editor(
|
||||
|editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
|
||||
}
|
||||
_ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.move_page_down(&MovePageDown::default(), window, cx);
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert!(
|
||||
menu.selected_item == 1,
|
||||
"expected PageDown to select the last item from the context menu"
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to stay open after PageDown");
|
||||
match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert!(
|
||||
menu.selected_item == 1,
|
||||
"expected PageDown to select the last item from the context menu"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!("expected completion menu to stay open after PageDown");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.move_page_up(&MovePageUp::default(), window, cx);
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert!(
|
||||
menu.selected_item == 0,
|
||||
"expected PageUp to select the first item from the context menu"
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to stay open after PageUp");
|
||||
match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert!(
|
||||
menu.selected_item == 0,
|
||||
"expected PageUp to select the first item from the context menu"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
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.update_editor(|editor, _, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
completion_menu_entries(&menu),
|
||||
&["r", "ret", "Range", "return"]
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
cx.update_editor(
|
||||
|editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert_eq!(
|
||||
completion_menu_entries(&menu),
|
||||
&["r", "ret", "Range", "return"]
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[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.
|
||||
cx.simulate_keystroke("-");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, _, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(
|
||||
completion_menu_entries(&menu),
|
||||
&["bg-red", "bg-blue", "bg-yellow"]
|
||||
);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
cx.update_editor(
|
||||
|editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert_eq!(
|
||||
completion_menu_entries(&menu),
|
||||
&["bg-red", "bg-blue", "bg-yellow"]
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
cx.simulate_keystroke("l");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, _, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
cx.update_editor(
|
||||
|editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
|
||||
}
|
||||
_ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// When filtering completions, consider the character after the '-' to
|
||||
// be the start of a subword.
|
||||
cx.set_state(r#"<p class="yelˇ" />"#);
|
||||
cx.simulate_keystroke("l");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, _, _| {
|
||||
if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
|
||||
{
|
||||
assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
|
||||
} else {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
});
|
||||
cx.update_editor(
|
||||
|editor, _, _| match editor.context_menu.borrow_mut().as_ref() {
|
||||
Some(CodeContextMenu::Completions(menu)) => {
|
||||
assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
|
||||
}
|
||||
_ => {
|
||||
panic!("expected completion menu to be open");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
cx: &mut EditorLspTestContext,
|
||||
mocked_response: lsp::SignatureHelp,
|
||||
) -> impl Future<Output = ()> {
|
||||
) -> impl Future<Output = ()> + use<> {
|
||||
let mut request =
|
||||
cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
|
||||
let mocked_response = mocked_response.clone();
|
||||
@@ -18711,7 +18729,7 @@ pub fn handle_completion_request(
|
||||
marked_string: &str,
|
||||
completions: Vec<&'static str>,
|
||||
counter: Arc<AtomicUsize>,
|
||||
) -> impl Future<Output = ()> {
|
||||
) -> impl Future<Output = ()> + use<> {
|
||||
let complete_from_marker: TextRangeMarker = '|'.into();
|
||||
let replace_range_marker: TextRangeMarker = ('<', '>').into();
|
||||
let (_, mut marked_ranges) = marked_text_ranges_by(
|
||||
@@ -18758,7 +18776,7 @@ pub fn handle_completion_request(
|
||||
fn handle_resolve_completion_request(
|
||||
cx: &mut EditorLspTestContext,
|
||||
edits: Option<Vec<(&'static str, &'static str)>>,
|
||||
) -> impl Future<Output = ()> {
|
||||
) -> impl Future<Output = ()> + use<> {
|
||||
let edits = edits.map(|edits| {
|
||||
edits
|
||||
.iter()
|
||||
|
||||
@@ -432,68 +432,95 @@ impl EditorElement {
|
||||
register_action(editor, window, Editor::expand_all_diff_hunks);
|
||||
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.format(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.format(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.format_selections(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.format_selections(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.organize_imports(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.organize_imports(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, Editor::restart_language_server);
|
||||
register_action(editor, window, Editor::show_character_palette);
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_completion(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.confirm_completion(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.compose_completion(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.compose_completion(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_code_action(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.confirm_code_action(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.rename(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.rename(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_rename(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.confirm_rename(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.find_all_references(action, window, cx) {
|
||||
task.detach_and_log_err(cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
match editor.find_all_references(action, window, cx) {
|
||||
Some(task) => {
|
||||
task.detach_and_log_err(cx);
|
||||
}
|
||||
_ => {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
});
|
||||
register_action(editor, window, Editor::show_signature_help);
|
||||
@@ -1097,81 +1124,87 @@ impl EditorElement {
|
||||
selections.push((player, layouts));
|
||||
}
|
||||
|
||||
if let Some(collaboration_hub) = &editor.collaboration_hub {
|
||||
// When following someone, render the local selections in their color.
|
||||
if let Some(leader_id) = editor.leader_peer_id {
|
||||
if let Some(collaborator) = collaboration_hub.collaborators(cx).get(&leader_id)
|
||||
{
|
||||
if let Some(participant_index) = collaboration_hub
|
||||
.user_participant_indices(cx)
|
||||
.get(&collaborator.user_id)
|
||||
match &editor.collaboration_hub {
|
||||
Some(collaboration_hub) => {
|
||||
// When following someone, render the local selections in their color.
|
||||
if let Some(leader_id) = editor.leader_peer_id {
|
||||
if let Some(collaborator) =
|
||||
collaboration_hub.collaborators(cx).get(&leader_id)
|
||||
{
|
||||
if let Some((local_selection_style, _)) = selections.first_mut() {
|
||||
*local_selection_style = cx
|
||||
.theme()
|
||||
.players()
|
||||
.color_for_participant(participant_index.0);
|
||||
if let Some(participant_index) = collaboration_hub
|
||||
.user_participant_indices(cx)
|
||||
.get(&collaborator.user_id)
|
||||
{
|
||||
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();
|
||||
for selection in snapshot.remote_selections_in_range(
|
||||
&(start_anchor..end_anchor),
|
||||
collaboration_hub.as_ref(),
|
||||
cx,
|
||||
) {
|
||||
let selection_style =
|
||||
Self::get_participant_color(selection.participant_index, cx);
|
||||
let mut remote_selections = HashMap::default();
|
||||
for selection in snapshot.remote_selections_in_range(
|
||||
&(start_anchor..end_anchor),
|
||||
collaboration_hub.as_ref(),
|
||||
cx,
|
||||
) {
|
||||
let selection_style =
|
||||
Self::get_participant_color(selection.participant_index, cx);
|
||||
|
||||
// Don't re-render the leader's selections, since the local selections
|
||||
// match theirs.
|
||||
if Some(selection.peer_id) == editor.leader_peer_id {
|
||||
continue;
|
||||
// Don't re-render the leader's selections, since the local selections
|
||||
// match theirs.
|
||||
if Some(selection.peer_id) == editor.leader_peer_id {
|
||||
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 =
|
||||
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 },
|
||||
));
|
||||
selections.extend(remote_selections.into_values());
|
||||
}
|
||||
_ => {
|
||||
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.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)
|
||||
@@ -2056,21 +2089,18 @@ impl EditorElement {
|
||||
cx: &mut App,
|
||||
) -> Vec<AnyElement> {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let active_task_indicator_row =
|
||||
if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
|
||||
let active_task_indicator_row = match editor.context_menu.borrow().as_ref() {
|
||||
Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
|
||||
deployed_from_indicator,
|
||||
actions,
|
||||
..
|
||||
})) = editor.context_menu.borrow().as_ref()
|
||||
{
|
||||
actions
|
||||
.tasks
|
||||
.as_ref()
|
||||
.map(|tasks| tasks.position.to_display_point(snapshot).row())
|
||||
.or(*deployed_from_indicator)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
})) => actions
|
||||
.tasks
|
||||
.as_ref()
|
||||
.map(|tasks| tasks.position.to_display_point(snapshot).row())
|
||||
.or(*deployed_from_indicator),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let offset_range_start =
|
||||
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| {
|
||||
if let Some(popover) = editor.signature_help_state.popover_mut() {
|
||||
let element = popover.render(max_size, cx);
|
||||
Some(element)
|
||||
} else {
|
||||
None
|
||||
match editor.signature_help_state.popover_mut() {
|
||||
Some(popover) => {
|
||||
let element = popover.render(max_size, cx);
|
||||
Some(element)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
if let Some(mut element) = maybe_element {
|
||||
@@ -4189,7 +4220,11 @@ impl EditorElement {
|
||||
None;
|
||||
for (&new_row, &new_background) in &layout.highlighted_rows {
|
||||
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 new_range_started = current_background != new_background
|
||||
|| current_range.end.next_row() != new_row;
|
||||
@@ -4916,7 +4951,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
if let Some((scrollbar_layout, axis)) = event
|
||||
match event
|
||||
.pressed_button
|
||||
.filter(|button| *button == MouseButton::Left)
|
||||
.and(editor.scroll_manager.dragging_scrollbar_axis())
|
||||
@@ -4924,27 +4959,29 @@ impl EditorElement {
|
||||
scrollbars_layout
|
||||
.iter_scrollbars()
|
||||
.find(|(_, a)| *a == axis)
|
||||
})
|
||||
{
|
||||
let ScrollbarLayout {
|
||||
hitbox,
|
||||
text_unit_size,
|
||||
..
|
||||
} = scrollbar_layout;
|
||||
}) {
|
||||
Some((scrollbar_layout, axis)) => {
|
||||
let ScrollbarLayout {
|
||||
hitbox,
|
||||
text_unit_size,
|
||||
..
|
||||
} = scrollbar_layout;
|
||||
|
||||
let old_position = mouse_position.along(axis);
|
||||
let new_position = event.position.along(axis);
|
||||
if (hitbox.origin.along(axis)..hitbox.bottom_right().along(axis))
|
||||
.contains(&old_position)
|
||||
{
|
||||
let position = editor.scroll_position(cx).apply_along(axis, |p| {
|
||||
(p + (new_position - old_position) / *text_unit_size).max(0.)
|
||||
});
|
||||
editor.set_scroll_position(position, window, cx);
|
||||
let old_position = mouse_position.along(axis);
|
||||
let new_position = event.position.along(axis);
|
||||
if (hitbox.origin.along(axis)..hitbox.bottom_right().along(axis))
|
||||
.contains(&old_position)
|
||||
{
|
||||
let position = editor.scroll_position(cx).apply_along(axis, |p| {
|
||||
(p + (new_position - old_position) / *text_unit_size).max(0.)
|
||||
});
|
||||
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() {
|
||||
@@ -5655,13 +5692,12 @@ pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
|
||||
|
||||
impl AcceptEditPredictionBinding {
|
||||
pub fn keystroke(&self) -> Option<&Keystroke> {
|
||||
if let Some(binding) = self.0.as_ref() {
|
||||
match &binding.keystrokes() {
|
||||
match self.0.as_ref() {
|
||||
Some(binding) => match &binding.keystrokes() {
|
||||
[keystroke] => Some(keystroke),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5949,89 +5985,9 @@ impl LineWithInvisibles {
|
||||
is_tab: false,
|
||||
replacement: None,
|
||||
}]) {
|
||||
if let Some(replacement) = highlighted_chunk.replacement {
|
||||
if !line.is_empty() {
|
||||
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));
|
||||
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 {
|
||||
match highlighted_chunk.replacement {
|
||||
Some(replacement) => {
|
||||
if !line.is_empty() {
|
||||
let shaped_line = window
|
||||
.text_system()
|
||||
.shape_line(line.clone().into(), font_size, &styles)
|
||||
@@ -6039,80 +5995,161 @@ impl LineWithInvisibles {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if !line_chunk.is_empty() && !line_exceeded_max_len {
|
||||
let text_style = if let Some(style) = highlighted_chunk.style {
|
||||
Cow::Owned(text_style.clone().highlight(style))
|
||||
} else {
|
||||
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(),
|
||||
});
|
||||
}
|
||||
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 {
|
||||
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
|
||||
}
|
||||
},
|
||||
))
|
||||
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 = 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 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
|
||||
.get(row as usize - scroll_position.y as usize)
|
||||
{
|
||||
if let Some(ix) = line.index_for_x(x) {
|
||||
(ix as u32, px(0.))
|
||||
} else {
|
||||
(line.len as u32, px(0.).max(x - line.width))
|
||||
Some(line) => {
|
||||
if let Some(ix) = line.index_for_x(x) {
|
||||
(ix as u32, px(0.))
|
||||
} 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);
|
||||
|
||||
@@ -192,7 +192,7 @@ impl GitBlame {
|
||||
&'a mut self,
|
||||
rows: &'a [RowInfo],
|
||||
cx: &App,
|
||||
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
|
||||
) -> impl 'a + Iterator<Item = Option<BlameEntry>> + use<'a> {
|
||||
self.sync(cx);
|
||||
|
||||
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));
|
||||
|
||||
for (oid, message) in messages {
|
||||
let permalink = if let Some((provider, git_remote)) = parsed_remote_url.as_ref() {
|
||||
Some(provider.build_commit_permalink(
|
||||
let permalink = match parsed_remote_url.as_ref() {
|
||||
Some((provider, git_remote)) => Some(provider.build_commit_permalink(
|
||||
git_remote,
|
||||
git::BuildCommitPermalinkParams {
|
||||
sha: oid.to_string().as_str(),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let remote = parsed_remote_url
|
||||
|
||||
@@ -472,21 +472,19 @@ pub fn show_link_definition(
|
||||
_ => GotoDefinitionKind::Type,
|
||||
};
|
||||
|
||||
let (mut hovered_link_state, is_cached) =
|
||||
if let Some(existing) = editor.hovered_link_state.take() {
|
||||
(existing, true)
|
||||
} else {
|
||||
(
|
||||
HoveredLinkState {
|
||||
last_trigger_point: trigger_point.clone(),
|
||||
symbol_range: None,
|
||||
preferred_kind,
|
||||
links: vec![],
|
||||
task: None,
|
||||
},
|
||||
false,
|
||||
)
|
||||
};
|
||||
let (mut hovered_link_state, is_cached) = match editor.hovered_link_state.take() {
|
||||
Some(existing) => (existing, true),
|
||||
_ => (
|
||||
HoveredLinkState {
|
||||
last_trigger_point: trigger_point.clone(),
|
||||
symbol_range: None,
|
||||
preferred_kind,
|
||||
links: vec![],
|
||||
task: None,
|
||||
},
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
if editor.pending_rename.is_some() {
|
||||
return;
|
||||
@@ -537,9 +535,9 @@ pub fn show_link_definition(
|
||||
hovered_link_state.task = Some(cx.spawn_in(window, async move |this, cx| {
|
||||
async move {
|
||||
let result = match &trigger_point {
|
||||
TriggerPoint::Text(_) => {
|
||||
if let Some((url_range, url)) = find_url(&buffer, buffer_position, cx.clone()) {
|
||||
this.update(cx, |_, _| {
|
||||
TriggerPoint::Text(_) => match find_url(&buffer, buffer_position, cx.clone()) {
|
||||
Some((url_range, url)) => this
|
||||
.update(cx, |_, _| {
|
||||
let range = maybe!({
|
||||
let start =
|
||||
snapshot.anchor_in_excerpt(excerpt_id, url_range.start)?;
|
||||
@@ -548,46 +546,58 @@ pub fn show_link_definition(
|
||||
});
|
||||
(range, vec![HoverLink::Url(url)])
|
||||
})
|
||||
.ok()
|
||||
} else if let Some((filename_range, filename)) =
|
||||
find_file(&buffer, project.clone(), buffer_position, cx).await
|
||||
{
|
||||
let range = maybe!({
|
||||
let start =
|
||||
snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?;
|
||||
let end = snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?;
|
||||
Some(RangeInEditor::Text(start..end))
|
||||
});
|
||||
.ok(),
|
||||
_ => match find_file(&buffer, project.clone(), buffer_position, cx).await {
|
||||
Some((filename_range, filename)) => {
|
||||
let range = maybe!({
|
||||
let start =
|
||||
snapshot.anchor_in_excerpt(excerpt_id, filename_range.start)?;
|
||||
let end =
|
||||
snapshot.anchor_in_excerpt(excerpt_id, filename_range.end)?;
|
||||
Some(RangeInEditor::Text(start..end))
|
||||
});
|
||||
|
||||
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
|
||||
Some((range, vec![HoverLink::File(filename)]))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => match provider {
|
||||
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((
|
||||
Some(RangeInEditor::Inlay(highlight.clone())),
|
||||
vec![HoverLink::InlayHint(lsp_location.clone(), *server_id)],
|
||||
@@ -606,46 +616,57 @@ pub fn show_link_definition(
|
||||
.as_ref()
|
||||
.and_then(|(symbol_range, _)| symbol_range.clone());
|
||||
|
||||
if let Some((symbol_range, definitions)) = result {
|
||||
hovered_link_state.links = definitions;
|
||||
match result {
|
||||
Some((symbol_range, definitions)) => {
|
||||
hovered_link_state.links = definitions;
|
||||
|
||||
let underline_hovered_link = !hovered_link_state.links.is_empty()
|
||||
|| hovered_link_state.symbol_range.is_some();
|
||||
let underline_hovered_link = !hovered_link_state.links.is_empty()
|
||||
|| hovered_link_state.symbol_range.is_some();
|
||||
|
||||
if underline_hovered_link {
|
||||
let style = gpui::HighlightStyle {
|
||||
underline: Some(gpui::UnderlineStyle {
|
||||
thickness: px(1.),
|
||||
if underline_hovered_link {
|
||||
let style = gpui::HighlightStyle {
|
||||
underline: Some(gpui::UnderlineStyle {
|
||||
thickness: px(1.),
|
||||
..Default::default()
|
||||
}),
|
||||
color: Some(cx.theme().colors().link_text_hover),
|
||||
..Default::default()
|
||||
}),
|
||||
color: Some(cx.theme().colors().link_text_hover),
|
||||
..Default::default()
|
||||
};
|
||||
let highlight_range =
|
||||
symbol_range.unwrap_or_else(|| match &trigger_point {
|
||||
TriggerPoint::Text(trigger_anchor) => {
|
||||
// If no symbol range returned from language server, use the surrounding word.
|
||||
let (offset_range, _) =
|
||||
snapshot.surrounding_word(*trigger_anchor, false);
|
||||
RangeInEditor::Text(
|
||||
snapshot.anchor_before(offset_range.start)
|
||||
..snapshot.anchor_after(offset_range.end),
|
||||
)
|
||||
}
|
||||
TriggerPoint::InlayHint(highlight, _, _) => {
|
||||
RangeInEditor::Inlay(highlight.clone())
|
||||
}
|
||||
});
|
||||
};
|
||||
let highlight_range =
|
||||
symbol_range.unwrap_or_else(|| match &trigger_point {
|
||||
TriggerPoint::Text(trigger_anchor) => {
|
||||
// If no symbol range returned from language server, use the surrounding word.
|
||||
let (offset_range, _) =
|
||||
snapshot.surrounding_word(*trigger_anchor, false);
|
||||
RangeInEditor::Text(
|
||||
snapshot.anchor_before(offset_range.start)
|
||||
..snapshot.anchor_after(offset_range.end),
|
||||
)
|
||||
}
|
||||
TriggerPoint::InlayHint(highlight, _, _) => {
|
||||
RangeInEditor::Inlay(highlight.clone())
|
||||
}
|
||||
});
|
||||
|
||||
match highlight_range {
|
||||
RangeInEditor::Text(text_range) => editor
|
||||
.highlight_text::<HoveredLinkState>(vec![text_range], style, cx),
|
||||
RangeInEditor::Inlay(highlight) => editor
|
||||
.highlight_inlays::<HoveredLinkState>(vec![highlight], style, cx),
|
||||
match highlight_range {
|
||||
RangeInEditor::Text(text_range) => editor
|
||||
.highlight_text::<HoveredLinkState>(
|
||||
vec![text_range],
|
||||
style,
|
||||
cx,
|
||||
),
|
||||
RangeInEditor::Inlay(highlight) => editor
|
||||
.highlight_inlays::<HoveredLinkState>(
|
||||
vec![highlight],
|
||||
style,
|
||||
cx,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editor.hide_hovered_link(cx);
|
||||
_ => {
|
||||
editor.hide_hovered_link(cx);
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
|
||||
@@ -289,126 +289,134 @@ fn show_hover(
|
||||
// Find the entry with the most specific range
|
||||
.min_by_key(|entry| entry.range.len());
|
||||
|
||||
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
|
||||
let text = match local_diagnostic.diagnostic.source {
|
||||
Some(ref source) => {
|
||||
format!("{source}: {}", local_diagnostic.diagnostic.message)
|
||||
}
|
||||
None => local_diagnostic.diagnostic.message.clone(),
|
||||
};
|
||||
let local_diagnostic = DiagnosticEntry {
|
||||
diagnostic: local_diagnostic.diagnostic,
|
||||
range: snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_before(local_diagnostic.range.start)
|
||||
..snapshot
|
||||
let diagnostic_popover = match local_diagnostic {
|
||||
Some(local_diagnostic) => {
|
||||
let text = match local_diagnostic.diagnostic.source {
|
||||
Some(ref source) => {
|
||||
format!("{source}: {}", local_diagnostic.diagnostic.message)
|
||||
}
|
||||
None => local_diagnostic.diagnostic.message.clone(),
|
||||
};
|
||||
let local_diagnostic = DiagnosticEntry {
|
||||
diagnostic: local_diagnostic.diagnostic,
|
||||
range: 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 background_color: Option<Hsla> = None;
|
||||
let mut border_color: Option<Hsla> = None;
|
||||
let mut background_color: Option<Hsla> = None;
|
||||
|
||||
let parsed_content = cx
|
||||
.new_window_entity(|window, cx| {
|
||||
let status_colors = cx.theme().status();
|
||||
let parsed_content = cx
|
||||
.new_window_entity(|window, cx| {
|
||||
let status_colors = cx.theme().status();
|
||||
|
||||
match local_diagnostic.diagnostic.severity {
|
||||
DiagnosticSeverity::ERROR => {
|
||||
background_color = Some(status_colors.error_background);
|
||||
border_color = Some(status_colors.error_border);
|
||||
}
|
||||
DiagnosticSeverity::WARNING => {
|
||||
background_color = Some(status_colors.warning_background);
|
||||
border_color = Some(status_colors.warning_border);
|
||||
}
|
||||
DiagnosticSeverity::INFORMATION => {
|
||||
background_color = Some(status_colors.info_background);
|
||||
border_color = Some(status_colors.info_border);
|
||||
}
|
||||
DiagnosticSeverity::HINT => {
|
||||
background_color = Some(status_colors.hint_background);
|
||||
border_color = Some(status_colors.hint_border);
|
||||
}
|
||||
_ => {
|
||||
background_color = Some(status_colors.ignored_background);
|
||||
border_color = Some(status_colors.ignored_border);
|
||||
}
|
||||
};
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let mut base_text_style = window.text_style();
|
||||
base_text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(settings.ui_font.family.clone()),
|
||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||
font_size: Some(settings.ui_font_size(cx).into()),
|
||||
color: Some(cx.theme().colors().editor_foreground),
|
||||
background_color: Some(gpui::transparent_black()),
|
||||
match local_diagnostic.diagnostic.severity {
|
||||
DiagnosticSeverity::ERROR => {
|
||||
background_color = Some(status_colors.error_background);
|
||||
border_color = Some(status_colors.error_border);
|
||||
}
|
||||
DiagnosticSeverity::WARNING => {
|
||||
background_color = Some(status_colors.warning_background);
|
||||
border_color = Some(status_colors.warning_border);
|
||||
}
|
||||
DiagnosticSeverity::INFORMATION => {
|
||||
background_color = Some(status_colors.info_background);
|
||||
border_color = Some(status_colors.info_border);
|
||||
}
|
||||
DiagnosticSeverity::HINT => {
|
||||
background_color = Some(status_colors.hint_background);
|
||||
border_color = Some(status_colors.hint_border);
|
||||
}
|
||||
_ => {
|
||||
background_color = Some(status_colors.ignored_background);
|
||||
border_color = Some(status_colors.ignored_border);
|
||||
}
|
||||
};
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let mut base_text_style = window.text_style();
|
||||
base_text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(settings.ui_font.family.clone()),
|
||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||
font_size: Some(settings.ui_font_size(cx).into()),
|
||||
color: Some(cx.theme().colors().editor_foreground),
|
||||
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()
|
||||
};
|
||||
Markdown::new_text(SharedString::new(text), markdown_style.clone(), cx)
|
||||
.open_url(open_markdown_url)
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
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()
|
||||
};
|
||||
Markdown::new_text(SharedString::new(text), markdown_style.clone(), cx)
|
||||
.open_url(open_markdown_url)
|
||||
})
|
||||
.ok();
|
||||
|
||||
Some(DiagnosticPopover {
|
||||
local_diagnostic,
|
||||
parsed_content,
|
||||
border_color,
|
||||
background_color,
|
||||
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
|
||||
anchor: Some(anchor),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
Some(DiagnosticPopover {
|
||||
local_diagnostic,
|
||||
parsed_content,
|
||||
border_color,
|
||||
background_color,
|
||||
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
|
||||
anchor: Some(anchor),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
this.hover_state.diagnostic_popover = diagnostic_popover;
|
||||
})?;
|
||||
|
||||
let invisible_char = if let Some(invisible) = snapshot
|
||||
let invisible_char = match snapshot
|
||||
.buffer_snapshot
|
||||
.chars_at(anchor)
|
||||
.next()
|
||||
.filter(|&c| is_invisible(c))
|
||||
{
|
||||
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
|
||||
.buffer_snapshot
|
||||
.reversed_chars_at(anchor)
|
||||
.next()
|
||||
.filter(|&c| is_invisible(c))
|
||||
{
|
||||
let before = snapshot.buffer_snapshot.anchor_before(
|
||||
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(),
|
||||
);
|
||||
Some(invisible) => {
|
||||
let after = snapshot.buffer_snapshot.anchor_after(
|
||||
anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(),
|
||||
);
|
||||
Some((invisible, anchor..after))
|
||||
}
|
||||
_ => {
|
||||
match snapshot
|
||||
.buffer_snapshot
|
||||
.reversed_chars_at(anchor)
|
||||
.next()
|
||||
.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))
|
||||
} else {
|
||||
None
|
||||
Some((invisible, before..anchor))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let hovers_response = if let Some(hover_request) = hover_request {
|
||||
hover_request.await
|
||||
} else {
|
||||
Vec::new()
|
||||
let hovers_response = match hover_request {
|
||||
Some(hover_request) => hover_request.await,
|
||||
_ => Vec::new(),
|
||||
};
|
||||
let snapshot = this.update_in(cx, |this, window, cx| this.snapshot(window, cx))?;
|
||||
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
|
||||
@@ -543,11 +551,12 @@ async fn parse_blocks(
|
||||
language: Option<Arc<Language>>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Option<Entity<Markdown>> {
|
||||
let fallback_language_name = if let Some(ref l) = language {
|
||||
let l = Arc::clone(l);
|
||||
Some(l.lsp_id().clone())
|
||||
} else {
|
||||
None
|
||||
let fallback_language_name = match language {
|
||||
Some(ref l) => {
|
||||
let l = Arc::clone(l);
|
||||
Some(l.lsp_id().clone())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let combined_text = blocks
|
||||
|
||||
@@ -36,16 +36,17 @@ impl Editor {
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<Vec<IndentGuide>> {
|
||||
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
|
||||
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
language_settings(
|
||||
buffer.read(cx).language().map(|l| l.name()),
|
||||
buffer.read(cx).file(),
|
||||
cx,
|
||||
)
|
||||
.indent_guides
|
||||
.enabled
|
||||
} else {
|
||||
true
|
||||
match self.buffer().read(cx).as_singleton() {
|
||||
Some(buffer) => {
|
||||
language_settings(
|
||||
buffer.read(cx).language().map(|l| l.name()),
|
||||
buffer.read(cx).file(),
|
||||
cx,
|
||||
)
|
||||
.indent_guides
|
||||
.enabled
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user