Compare commits
17 Commits
v0.218.x
...
v0.210.4-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a16d556398 | ||
|
|
cbb803c7b5 | ||
|
|
64ae3bfccc | ||
|
|
2be70b7cdb | ||
|
|
9a01c7e7d1 | ||
|
|
9844631b27 | ||
|
|
1d06e26b92 | ||
|
|
282b7b4309 | ||
|
|
fade12155d | ||
|
|
18f31e8bf9 | ||
|
|
780d60bc9d | ||
|
|
08ba1939c6 | ||
|
|
c4858f8ae6 | ||
|
|
f3768cfc40 | ||
|
|
80d1551671 | ||
|
|
00a14349de | ||
|
|
aa8128e2ff |
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -210,9 +210,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "agent-client-protocol"
|
||||
version = "0.5.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f655394a107cd601bd2e5375c2d909ea83adc65678a0e0e8d77613d3c848a7d"
|
||||
checksum = "525705e39c11cd73f7bc784e3681a9386aa30c8d0630808d3dc2237eb4f9cb1b"
|
||||
dependencies = [
|
||||
"agent-client-protocol-schema",
|
||||
"anyhow",
|
||||
@@ -228,9 +228,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "agent-client-protocol-schema"
|
||||
version = "0.4.11"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61be4454304d7df1a5b44c4ae55e707ffe72eac4dfb1ef8762510ce8d8f6d924"
|
||||
checksum = "ecf16c18fea41282d6bbadd1549a06be6836bddb1893f44a6235f340fa24e2af"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"derive_more 2.0.1",
|
||||
@@ -266,6 +266,7 @@ dependencies = [
|
||||
"log",
|
||||
"nix 0.29.0",
|
||||
"project",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -14156,7 +14157,6 @@ dependencies = [
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"rayon",
|
||||
"smallvec",
|
||||
"sum_tree",
|
||||
"unicode-segmentation",
|
||||
"util",
|
||||
@@ -20918,7 +20918,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.210.0"
|
||||
version = "0.210.4"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
|
||||
@@ -438,7 +438,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
agent-client-protocol = { version = "0.5.0", features = ["unstable"] }
|
||||
agent-client-protocol = { version = "0.7.0", features = ["unstable"] }
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = "0.25.1-rc1"
|
||||
any_vec = "0.14"
|
||||
|
||||
@@ -539,7 +539,7 @@
|
||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"ctrl-shift-space": "editor::ShowSignatureHelp",
|
||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
@@ -799,7 +799,7 @@
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1094,7 +1094,7 @@
|
||||
"paste": "terminal::Paste",
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
"alt-.": ["terminal::SendText", "\u001b."],
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
"cmd-\"": "editor::ExpandAllDiffHunks",
|
||||
"cmd-alt-g b": "git::Blame",
|
||||
"cmd-alt-g m": "git::OpenModifiedFiles",
|
||||
"cmd-shift-space": "editor::ShowSignatureHelp",
|
||||
"cmd-i": "editor::ShowSignatureHelp",
|
||||
"f9": "editor::ToggleBreakpoint",
|
||||
"shift-f9": "editor::EditLogBreakpoint",
|
||||
"ctrl-f12": "editor::GoToDeclaration",
|
||||
@@ -864,7 +864,7 @@
|
||||
"cmd-shift-e": "pane::RevealInProjectPanel",
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPreviousHunk",
|
||||
"cmd-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-:": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1168,7 +1168,7 @@
|
||||
"cmd-a": "editor::SelectAll",
|
||||
"cmd-k": "terminal::Clear",
|
||||
"cmd-n": "workspace::NewTerminal",
|
||||
"cmd-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-_": null, // emacs undo
|
||||
// Some nice conveniences
|
||||
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
|
||||
|
||||
@@ -548,7 +548,7 @@
|
||||
"ctrl-k ctrl-0": "editor::FoldAll",
|
||||
"ctrl-k ctrl-j": "editor::UnfoldAll",
|
||||
"ctrl-space": "editor::ShowCompletions",
|
||||
"ctrl-shift-space": "editor::ShowSignatureHelp",
|
||||
"ctrl-shift-space": "editor::ShowWordCompletions",
|
||||
"ctrl-.": "editor::ToggleCodeActions",
|
||||
"ctrl-k r": "editor::RevealInFileManager",
|
||||
"ctrl-k p": "editor::CopyPath",
|
||||
@@ -812,7 +812,7 @@
|
||||
"ctrl-shift-e": "pane::RevealInProjectPanel",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPreviousHunk",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"ctrl-shift-;": "editor::ToggleInlayHints"
|
||||
}
|
||||
},
|
||||
@@ -1120,7 +1120,7 @@
|
||||
"shift-insert": "terminal::Paste",
|
||||
"ctrl-v": "terminal::Paste",
|
||||
"ctrl-shift-v": "terminal::Paste",
|
||||
"ctrl-i": "assistant::InlineAssist",
|
||||
"ctrl-enter": "assistant::InlineAssist",
|
||||
"alt-b": ["terminal::SendText", "\u001bb"],
|
||||
"alt-f": ["terminal::SendText", "\u001bf"],
|
||||
"alt-.": ["terminal::SendText", "\u001b."],
|
||||
|
||||
@@ -1105,13 +1105,13 @@ impl AcpThread {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<(), acp::Error> {
|
||||
match update {
|
||||
acp::SessionUpdate::UserMessageChunk { content } => {
|
||||
acp::SessionUpdate::UserMessageChunk(acp::ContentChunk { content, .. }) => {
|
||||
self.push_user_content_block(None, content, cx);
|
||||
}
|
||||
acp::SessionUpdate::AgentMessageChunk { content } => {
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content, .. }) => {
|
||||
self.push_assistant_content_block(content, false, cx);
|
||||
}
|
||||
acp::SessionUpdate::AgentThoughtChunk { content } => {
|
||||
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk { content, .. }) => {
|
||||
self.push_assistant_content_block(content, true, cx);
|
||||
}
|
||||
acp::SessionUpdate::ToolCall(tool_call) => {
|
||||
@@ -1123,12 +1123,14 @@ impl AcpThread {
|
||||
acp::SessionUpdate::Plan(plan) => {
|
||||
self.update_plan(plan, cx);
|
||||
}
|
||||
acp::SessionUpdate::AvailableCommandsUpdate { available_commands } => {
|
||||
cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands))
|
||||
}
|
||||
acp::SessionUpdate::CurrentModeUpdate { current_mode_id } => {
|
||||
cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id))
|
||||
}
|
||||
acp::SessionUpdate::AvailableCommandsUpdate(acp::AvailableCommandsUpdate {
|
||||
available_commands,
|
||||
..
|
||||
}) => cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands)),
|
||||
acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate {
|
||||
current_mode_id,
|
||||
..
|
||||
}) => cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2586,17 +2588,19 @@ mod tests {
|
||||
thread.update(&mut cx, |thread, cx| {
|
||||
thread
|
||||
.handle_session_update(
|
||||
acp::SessionUpdate::AgentThoughtChunk {
|
||||
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk {
|
||||
content: "Thinking ".into(),
|
||||
},
|
||||
meta: None,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
thread
|
||||
.handle_session_update(
|
||||
acp::SessionUpdate::AgentThoughtChunk {
|
||||
acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk {
|
||||
content: "hard!".into(),
|
||||
},
|
||||
meta: None,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -3095,9 +3099,10 @@ mod tests {
|
||||
thread.update(&mut cx, |thread, cx| {
|
||||
thread
|
||||
.handle_session_update(
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
content: content.text.to_uppercase().into(),
|
||||
},
|
||||
meta: None,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -3454,9 +3459,10 @@ mod tests {
|
||||
thread.update(&mut cx, |thread, cx| {
|
||||
thread
|
||||
.handle_session_update(
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
content: content.text.to_uppercase().into(),
|
||||
},
|
||||
meta: None,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -38,6 +38,7 @@ language_model.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
reqwest_client = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -107,6 +107,14 @@ impl AcpConnection {
|
||||
|
||||
let sessions = Rc::new(RefCell::new(HashMap::default()));
|
||||
|
||||
let (release_channel, version) = cx.update(|cx| {
|
||||
(
|
||||
release_channel::ReleaseChannel::try_global(cx)
|
||||
.map(|release_channel| release_channel.display_name()),
|
||||
release_channel::AppVersion::global(cx).to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let client = ClientDelegate {
|
||||
sessions: sessions.clone(),
|
||||
cx: cx.clone(),
|
||||
@@ -174,6 +182,11 @@ impl AcpConnection {
|
||||
"terminal_output": true,
|
||||
})),
|
||||
},
|
||||
client_info: Some(acp::Implementation {
|
||||
name: "zed".to_owned(),
|
||||
title: release_channel.map(|c| c.to_owned()),
|
||||
version,
|
||||
}),
|
||||
meta: None,
|
||||
})
|
||||
.await?;
|
||||
@@ -702,7 +715,11 @@ impl acp::Client for ClientDelegate {
|
||||
.get(¬ification.session_id)
|
||||
.context("Failed to get session")?;
|
||||
|
||||
if let acp::SessionUpdate::CurrentModeUpdate { current_mode_id } = ¬ification.update {
|
||||
if let acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate {
|
||||
current_mode_id,
|
||||
..
|
||||
}) = ¬ification.update
|
||||
{
|
||||
if let Some(session_modes) = &session.session_modes {
|
||||
session_modes.borrow_mut().current_mode_id = current_mode_id.clone();
|
||||
} else {
|
||||
|
||||
@@ -6005,9 +6005,12 @@ pub(crate) mod tests {
|
||||
impl StubAgentServer<StubAgentConnection> {
|
||||
fn default_response() -> Self {
|
||||
let conn = StubAgentConnection::new();
|
||||
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: "Default response".into(),
|
||||
}]);
|
||||
conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: "Default response".into(),
|
||||
meta: None,
|
||||
},
|
||||
)]);
|
||||
Self::new(conn)
|
||||
}
|
||||
}
|
||||
@@ -6358,13 +6361,16 @@ pub(crate) mod tests {
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
meta: None,
|
||||
}),
|
||||
}]);
|
||||
},
|
||||
)]);
|
||||
|
||||
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
|
||||
add_to_workspace(thread_view.clone(), cx);
|
||||
@@ -6448,13 +6454,16 @@ pub(crate) mod tests {
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
meta: None,
|
||||
}),
|
||||
}]);
|
||||
},
|
||||
)]);
|
||||
|
||||
let (thread_view, cx) =
|
||||
setup_thread_view(StubAgentServer::new(connection.clone()), cx).await;
|
||||
@@ -6492,13 +6501,16 @@ pub(crate) mod tests {
|
||||
});
|
||||
|
||||
// Send
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "New Response".into(),
|
||||
annotations: None,
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "New Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
meta: None,
|
||||
}),
|
||||
}]);
|
||||
},
|
||||
)]);
|
||||
|
||||
user_message_editor.update_in(cx, |_editor, window, cx| {
|
||||
window.dispatch_action(Box::new(Chat), cx);
|
||||
@@ -6585,13 +6597,14 @@ pub(crate) mod tests {
|
||||
cx.update(|_, cx| {
|
||||
connection.send_update(
|
||||
session_id.clone(),
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
},
|
||||
meta: None,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
connection.end_turn(session_id, acp::StopReason::EndTurn);
|
||||
@@ -6643,9 +6656,10 @@ pub(crate) mod tests {
|
||||
cx.update(|_, cx| {
|
||||
connection.send_update(
|
||||
session_id.clone(),
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
content: "Message 1 resp".into(),
|
||||
},
|
||||
meta: None,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -6679,9 +6693,10 @@ pub(crate) mod tests {
|
||||
// Simulate a response sent after beginning to cancel
|
||||
connection.send_update(
|
||||
session_id.clone(),
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
content: "onse".into(),
|
||||
},
|
||||
meta: None,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -6712,9 +6727,10 @@ pub(crate) mod tests {
|
||||
cx.update(|_, cx| {
|
||||
connection.send_update(
|
||||
session_id.clone(),
|
||||
acp::SessionUpdate::AgentMessageChunk {
|
||||
acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk {
|
||||
content: "Message 2 response".into(),
|
||||
},
|
||||
meta: None,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
connection.end_turn(session_id.clone(), acp::StopReason::EndTurn);
|
||||
@@ -6752,13 +6768,16 @@ pub(crate) mod tests {
|
||||
init_test(cx);
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
meta: None,
|
||||
}),
|
||||
}]);
|
||||
},
|
||||
)]);
|
||||
|
||||
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
|
||||
add_to_workspace(thread_view.clone(), cx);
|
||||
@@ -6835,13 +6854,16 @@ pub(crate) mod tests {
|
||||
init_test(cx);
|
||||
|
||||
let connection = StubAgentConnection::new();
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk(
|
||||
acp::ContentChunk {
|
||||
content: acp::ContentBlock::Text(acp::TextContent {
|
||||
text: "Response".into(),
|
||||
annotations: None,
|
||||
meta: None,
|
||||
}),
|
||||
meta: None,
|
||||
}),
|
||||
}]);
|
||||
},
|
||||
)]);
|
||||
|
||||
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
|
||||
add_to_workspace(thread_view.clone(), cx);
|
||||
|
||||
@@ -581,13 +581,11 @@ impl Item for AgentDiffPane {
|
||||
_workspace_id: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Self::new(self.thread.clone(), self.workspace.clone(), window, cx)
|
||||
})))
|
||||
Some(cx.new(|cx| Self::new(self.thread.clone(), self.workspace.clone(), window, cx)))
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &App) -> bool {
|
||||
|
||||
@@ -2600,7 +2600,7 @@ async fn test_lsp_pull_diagnostics(
|
||||
capabilities: capabilities.clone(),
|
||||
initializer: Some(Box::new(move |fake_language_server| {
|
||||
let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
|
||||
"workspace/diagnostic-{}-1",
|
||||
"workspace/diagnostic/{}/1",
|
||||
fake_language_server.server.server_id()
|
||||
));
|
||||
let closure_workspace_diagnostics_pulls_result_ids = closure_workspace_diagnostics_pulls_result_ids.clone();
|
||||
|
||||
@@ -776,30 +776,26 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
|
||||
.unwrap();
|
||||
|
||||
// Clients A and B follow each other in split panes
|
||||
workspace_a
|
||||
.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.split_and_clone(
|
||||
workspace.active_pane().clone(),
|
||||
SplitDirection::Right,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
workspace_a.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.split_and_clone(
|
||||
workspace.active_pane().clone(),
|
||||
SplitDirection::Right,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
workspace_a.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.follow(client_b.peer_id().unwrap(), window, cx)
|
||||
});
|
||||
executor.run_until_parked();
|
||||
workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.split_and_clone(
|
||||
workspace.active_pane().clone(),
|
||||
SplitDirection::Right,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
workspace_b.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.split_and_clone(
|
||||
workspace.active_pane().clone(),
|
||||
SplitDirection::Right,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
workspace_b.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.follow(client_a.peer_id().unwrap(), window, cx)
|
||||
});
|
||||
@@ -1373,11 +1369,9 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
|
||||
);
|
||||
|
||||
// When client B activates a different pane, it continues following client A in the original pane.
|
||||
workspace_b
|
||||
.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, window, cx)
|
||||
})
|
||||
.await;
|
||||
workspace_b.update_in(cx_b, |workspace, window, cx| {
|
||||
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, window, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||
Some(leader_id.into())
|
||||
|
||||
@@ -6748,7 +6748,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.split(workspace::SplitDirection::Right, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let right_pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
|
||||
@@ -498,8 +498,8 @@ impl Item for ChannelView {
|
||||
_: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>> {
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
) -> Option<Entity<Self>> {
|
||||
Some(cx.new(|cx| {
|
||||
Self::new(
|
||||
self.project.clone(),
|
||||
self.workspace.clone(),
|
||||
@@ -508,7 +508,7 @@ impl Item for ChannelView {
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
fn navigate(
|
||||
|
||||
@@ -693,11 +693,11 @@ impl Item for BufferDiagnosticsEditor {
|
||||
_workspace_id: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Some(cx.new(|cx| {
|
||||
BufferDiagnosticsEditor::new(
|
||||
self.project_path.clone(),
|
||||
self.project.clone(),
|
||||
@@ -706,7 +706,7 @@ impl Item for BufferDiagnosticsEditor {
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
|
||||
@@ -732,11 +732,11 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
_workspace_id: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Some(cx.new(|cx| {
|
||||
ProjectDiagnosticsEditor::new(
|
||||
self.include_warnings,
|
||||
self.project.clone(),
|
||||
@@ -744,7 +744,7 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &App) -> bool {
|
||||
|
||||
@@ -1427,6 +1427,15 @@ impl DisplaySnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for DisplaySnapshot {
|
||||
type Target = BlockSnapshot;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.block_snapshot
|
||||
}
|
||||
}
|
||||
|
||||
/// A zero-indexed point in a text buffer consisting of a row and column adjusted for inserted blocks.
|
||||
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
pub struct DisplayPoint(BlockPoint);
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ impl From<CustomBlockId> for ElementId {
|
||||
}
|
||||
}
|
||||
|
||||
/// A zero-indexed point in a text buffer consisting of a row and column
|
||||
/// adjusted for inserted blocks, wrapped rows, tabs, folds and inlays.
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||
pub struct BlockPoint(pub Point);
|
||||
|
||||
@@ -80,11 +82,16 @@ struct WrapRow(u32);
|
||||
|
||||
pub type RenderBlock = Arc<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
|
||||
|
||||
/// Where to place a block.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum BlockPlacement<T> {
|
||||
/// Place the block above the given position.
|
||||
Above(T),
|
||||
/// Place the block below the given position.
|
||||
Below(T),
|
||||
/// Place the block next the given position.
|
||||
Near(T),
|
||||
/// Replace the given range of positions with the block.
|
||||
Replace(RangeInclusive<T>),
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,6 @@ use gpui::{
|
||||
UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
|
||||
div, point, prelude::*, pulsating_between, px, relative, size,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
|
||||
use hover_popover::{HoverState, hide_hover};
|
||||
use indent_guides::ActiveIndentGuidesState;
|
||||
@@ -163,9 +162,7 @@ use project::{
|
||||
use rand::seq::SliceRandom;
|
||||
use rpc::{ErrorCode, ErrorExt, proto::PeerId};
|
||||
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
|
||||
use selections_collection::{
|
||||
MutableSelectionsCollection, SelectionsCollection, resolve_selections,
|
||||
};
|
||||
use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
@@ -209,6 +206,7 @@ use crate::{
|
||||
editor_settings::MultiCursorModifier,
|
||||
hover_links::{find_url, find_url_from_range},
|
||||
scroll::{ScrollOffset, ScrollPixelOffset},
|
||||
selections_collection::resolve_selections_wrapping_blocks,
|
||||
signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
|
||||
};
|
||||
|
||||
@@ -3189,7 +3187,7 @@ impl Editor {
|
||||
refresh_linked_ranges(self, window, cx);
|
||||
|
||||
self.refresh_selected_text_highlights(false, window, cx);
|
||||
refresh_matching_bracket_highlights(self, cx);
|
||||
self.refresh_matching_bracket_highlights(window, cx);
|
||||
self.update_visible_edit_prediction(window, cx);
|
||||
self.edit_prediction_requires_modifier_in_indent_conflict = true;
|
||||
self.inline_blame_popover.take();
|
||||
@@ -4373,16 +4371,17 @@ impl Editor {
|
||||
let new_anchor_selections = new_selections.iter().map(|e| &e.0);
|
||||
let new_selection_deltas = new_selections.iter().map(|e| e.1);
|
||||
let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
|
||||
.zip(new_selection_deltas)
|
||||
.map(|(selection, delta)| Selection {
|
||||
id: selection.id,
|
||||
start: selection.start + delta,
|
||||
end: selection.end + delta,
|
||||
reversed: selection.reversed,
|
||||
goal: SelectionGoal::None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let new_selections =
|
||||
resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
|
||||
.zip(new_selection_deltas)
|
||||
.map(|(selection, delta)| Selection {
|
||||
id: selection.id,
|
||||
start: selection.start + delta,
|
||||
end: selection.end + delta,
|
||||
reversed: selection.reversed,
|
||||
goal: SelectionGoal::None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut i = 0;
|
||||
for (position, delta, selection_id, pair) in new_autoclose_regions {
|
||||
@@ -6974,6 +6973,7 @@ impl Editor {
|
||||
|
||||
fn prepare_highlight_query_from_selection(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<(String, Range<Anchor>)> {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
@@ -6985,24 +6985,23 @@ impl Editor {
|
||||
if self.selections.count() != 1 || self.selections.line_mode() {
|
||||
return None;
|
||||
}
|
||||
let selection = self.selections.newest_anchor();
|
||||
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let selection_point_range = selection.start.to_point(&multi_buffer_snapshot)
|
||||
..selection.end.to_point(&multi_buffer_snapshot);
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let selection = self.selections.newest::<Point>(&snapshot);
|
||||
// If the selection spans multiple rows OR it is empty
|
||||
if selection_point_range.start.row != selection_point_range.end.row
|
||||
|| selection_point_range.start.column == selection_point_range.end.column
|
||||
if selection.start.row != selection.end.row
|
||||
|| selection.start.column == selection.end.column
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let query = multi_buffer_snapshot
|
||||
.text_for_range(selection.range())
|
||||
let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
|
||||
let query = snapshot
|
||||
.buffer_snapshot()
|
||||
.text_for_range(selection_anchor_range.clone())
|
||||
.collect::<String>();
|
||||
if query.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some((query, selection.range()))
|
||||
Some((query, selection_anchor_range))
|
||||
}
|
||||
|
||||
fn update_selection_occurrence_highlights(
|
||||
@@ -7155,7 +7154,8 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
|
||||
let Some((query_text, query_range)) =
|
||||
self.prepare_highlight_query_from_selection(window, cx)
|
||||
else {
|
||||
self.clear_background_highlights::<SelectedTextHighlight>(cx);
|
||||
self.quick_selection_highlight_task.take();
|
||||
@@ -20965,7 +20965,7 @@ impl Editor {
|
||||
self.refresh_code_actions(window, cx);
|
||||
self.refresh_selected_text_highlights(true, window, cx);
|
||||
self.refresh_single_line_folds(window, cx);
|
||||
refresh_matching_bracket_highlights(self, cx);
|
||||
self.refresh_matching_bracket_highlights(window, cx);
|
||||
if self.has_active_edit_prediction() {
|
||||
self.update_visible_edit_prediction(window, cx);
|
||||
}
|
||||
|
||||
@@ -7233,16 +7233,9 @@ impl EditorElement {
|
||||
* ScrollPixelOffset::from(max_glyph_advance)
|
||||
- ScrollPixelOffset::from(delta.x * scroll_sensitivity))
|
||||
/ ScrollPixelOffset::from(max_glyph_advance);
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let y = (current_scroll_position.y
|
||||
* ScrollPixelOffset::from(line_height)
|
||||
* ScrollPixelOffset::from(scale_factor)
|
||||
let y = (current_scroll_position.y * ScrollPixelOffset::from(line_height)
|
||||
- ScrollPixelOffset::from(delta.y * scroll_sensitivity))
|
||||
.round()
|
||||
/ ScrollPixelOffset::from(line_height)
|
||||
/ ScrollPixelOffset::from(scale_factor);
|
||||
|
||||
/ ScrollPixelOffset::from(line_height);
|
||||
let mut scroll_position =
|
||||
point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
|
||||
let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
|
||||
|
||||
@@ -1,57 +1,60 @@
|
||||
use crate::{Editor, RangeToAnchorExt};
|
||||
use gpui::{Context, HighlightStyle};
|
||||
use gpui::{Context, HighlightStyle, Window};
|
||||
use language::CursorShape;
|
||||
use multi_buffer::ToOffset;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
enum MatchingBracketHighlight {}
|
||||
|
||||
pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut Context<Editor>) {
|
||||
editor.clear_highlights::<MatchingBracketHighlight>(cx);
|
||||
impl Editor {
|
||||
pub fn refresh_matching_bracket_highlights(
|
||||
&mut self,
|
||||
window: &Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.clear_highlights::<MatchingBracketHighlight>(cx);
|
||||
|
||||
let buffer_snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
let newest_selection = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.map(|anchor| anchor.to_offset(&buffer_snapshot));
|
||||
// Don't highlight brackets if the selection isn't empty
|
||||
if !newest_selection.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let head = newest_selection.head();
|
||||
if head > buffer_snapshot.len() {
|
||||
log::error!("bug: cursor offset is out of range while refreshing bracket highlights");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut tail = head;
|
||||
if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow)
|
||||
&& head < buffer_snapshot.len()
|
||||
{
|
||||
if let Some(tail_ch) = buffer_snapshot.chars_at(tail).next() {
|
||||
tail += tail_ch.len_utf8();
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let buffer_snapshot = snapshot.buffer_snapshot();
|
||||
let newest_selection = self.selections.newest::<usize>(&snapshot);
|
||||
// Don't highlight brackets if the selection isn't empty
|
||||
if !newest_selection.is_empty() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((opening_range, closing_range)) =
|
||||
buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None)
|
||||
{
|
||||
editor.highlight_text::<MatchingBracketHighlight>(
|
||||
vec![
|
||||
opening_range.to_anchors(&buffer_snapshot),
|
||||
closing_range.to_anchors(&buffer_snapshot),
|
||||
],
|
||||
HighlightStyle {
|
||||
background_color: Some(
|
||||
cx.theme()
|
||||
.colors()
|
||||
.editor_document_highlight_bracket_background,
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
let head = newest_selection.head();
|
||||
if head > buffer_snapshot.len() {
|
||||
log::error!("bug: cursor offset is out of range while refreshing bracket highlights");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut tail = head;
|
||||
if (self.cursor_shape == CursorShape::Block || self.cursor_shape == CursorShape::Hollow)
|
||||
&& head < buffer_snapshot.len()
|
||||
{
|
||||
if let Some(tail_ch) = buffer_snapshot.chars_at(tail).next() {
|
||||
tail += tail_ch.len_utf8();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((opening_range, closing_range)) =
|
||||
buffer_snapshot.innermost_enclosing_bracket_ranges(head..tail, None)
|
||||
{
|
||||
self.highlight_text::<MatchingBracketHighlight>(
|
||||
vec![
|
||||
opening_range.to_anchors(&buffer_snapshot),
|
||||
closing_range.to_anchors(&buffer_snapshot),
|
||||
],
|
||||
HighlightStyle {
|
||||
background_color: Some(
|
||||
cx.theme()
|
||||
.colors()
|
||||
.editor_document_highlight_bracket_background,
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -762,11 +762,11 @@ impl Item for Editor {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Editor>>>
|
||||
) -> Option<Entity<Editor>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| self.clone(window, cx))))
|
||||
Some(cx.new(|cx| self.clone(window, cx)))
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
|
||||
@@ -131,7 +131,7 @@ impl SelectionsCollection {
|
||||
&self,
|
||||
snapshot: &DisplaySnapshot,
|
||||
) -> Option<Selection<D>> {
|
||||
resolve_selections(self.pending_anchor(), &snapshot).next()
|
||||
resolve_selections_wrapping_blocks(self.pending_anchor(), &snapshot).next()
|
||||
}
|
||||
|
||||
pub(crate) fn pending_mode(&self) -> Option<SelectMode> {
|
||||
@@ -144,7 +144,8 @@ impl SelectionsCollection {
|
||||
{
|
||||
let disjoint_anchors = &self.disjoint;
|
||||
let mut disjoint =
|
||||
resolve_selections::<D, _>(disjoint_anchors.iter(), &snapshot).peekable();
|
||||
resolve_selections_wrapping_blocks::<D, _>(disjoint_anchors.iter(), &snapshot)
|
||||
.peekable();
|
||||
let mut pending_opt = self.pending::<D>(&snapshot);
|
||||
iter::from_fn(move || {
|
||||
if let Some(pending) = pending_opt.as_mut() {
|
||||
@@ -185,27 +186,6 @@ impl SelectionsCollection {
|
||||
selections
|
||||
}
|
||||
|
||||
/// Returns all of the selections, adjusted to take into account the selection line_mode. Uses a provided snapshot to resolve selections.
|
||||
pub fn all_adjusted_with_snapshot(
|
||||
&self,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Vec<Selection<Point>> {
|
||||
let mut selections = self
|
||||
.disjoint
|
||||
.iter()
|
||||
.chain(self.pending_anchor())
|
||||
.map(|anchor| anchor.map(|anchor| anchor.to_point(&snapshot)))
|
||||
.collect::<Vec<_>>();
|
||||
if self.line_mode {
|
||||
for selection in &mut selections {
|
||||
let new_range = snapshot.expand_to_line(selection.range());
|
||||
selection.start = new_range.start;
|
||||
selection.end = new_range.end;
|
||||
}
|
||||
}
|
||||
selections
|
||||
}
|
||||
|
||||
/// Returns the newest selection, adjusted to take into account the selection line_mode
|
||||
pub fn newest_adjusted(&self, snapshot: &DisplaySnapshot) -> Selection<Point> {
|
||||
let mut selection = self.newest::<Point>(&snapshot);
|
||||
@@ -259,7 +239,7 @@ impl SelectionsCollection {
|
||||
Ok(ix) => ix + 1,
|
||||
Err(ix) => ix,
|
||||
};
|
||||
resolve_selections(&self.disjoint[start_ix..end_ix], snapshot).collect()
|
||||
resolve_selections_wrapping_blocks(&self.disjoint[start_ix..end_ix], snapshot).collect()
|
||||
}
|
||||
|
||||
pub fn all_display(&self, snapshot: &DisplaySnapshot) -> Vec<Selection<DisplayPoint>> {
|
||||
@@ -305,7 +285,7 @@ impl SelectionsCollection {
|
||||
&self,
|
||||
snapshot: &DisplaySnapshot,
|
||||
) -> Selection<D> {
|
||||
resolve_selections([self.newest_anchor()], &snapshot)
|
||||
resolve_selections_wrapping_blocks([self.newest_anchor()], &snapshot)
|
||||
.next()
|
||||
.unwrap()
|
||||
}
|
||||
@@ -328,7 +308,7 @@ impl SelectionsCollection {
|
||||
&self,
|
||||
snapshot: &DisplaySnapshot,
|
||||
) -> Selection<D> {
|
||||
resolve_selections([self.oldest_anchor()], &snapshot)
|
||||
resolve_selections_wrapping_blocks([self.oldest_anchor()], &snapshot)
|
||||
.next()
|
||||
.unwrap()
|
||||
}
|
||||
@@ -658,7 +638,7 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
pub fn select_anchors(&mut self, selections: Vec<Selection<Anchor>>) {
|
||||
let map = self.display_map();
|
||||
let resolved_selections =
|
||||
resolve_selections::<usize, _>(&selections, &map).collect::<Vec<_>>();
|
||||
resolve_selections_wrapping_blocks::<usize, _>(&selections, &map).collect::<Vec<_>>();
|
||||
self.select(resolved_selections);
|
||||
}
|
||||
|
||||
@@ -940,7 +920,8 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
|
||||
if !adjusted_disjoint.is_empty() {
|
||||
let map = self.display_map();
|
||||
let resolved_selections = resolve_selections(adjusted_disjoint.iter(), &map).collect();
|
||||
let resolved_selections =
|
||||
resolve_selections_wrapping_blocks(adjusted_disjoint.iter(), &map).collect();
|
||||
self.select::<usize>(resolved_selections);
|
||||
}
|
||||
|
||||
@@ -1025,6 +1006,7 @@ fn resolve_selections_point<'a>(
|
||||
}
|
||||
|
||||
/// Panics if passed selections are not in order
|
||||
/// Resolves the anchors to display positions
|
||||
fn resolve_selections_display<'a>(
|
||||
selections: impl 'a + IntoIterator<Item = &'a Selection<Anchor>>,
|
||||
map: &'a DisplaySnapshot,
|
||||
@@ -1056,8 +1038,13 @@ fn resolve_selections_display<'a>(
|
||||
coalesce_selections(selections)
|
||||
}
|
||||
|
||||
/// Resolves the passed in anchors to [`TextDimension`]s `D`
|
||||
/// wrapping around blocks inbetween.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if passed selections are not in order
|
||||
pub(crate) fn resolve_selections<'a, D, I>(
|
||||
pub(crate) fn resolve_selections_wrapping_blocks<'a, D, I>(
|
||||
selections: I,
|
||||
map: &'a DisplaySnapshot,
|
||||
) -> impl 'a + Iterator<Item = Selection<D>>
|
||||
@@ -1065,6 +1052,8 @@ where
|
||||
D: TextDimension + Ord + Sub<D, Output = D>,
|
||||
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
|
||||
{
|
||||
// Transforms `Anchor -> DisplayPoint -> Point -> DisplayPoint -> D`
|
||||
// todo(lw): We should be able to short circuit the `Anchor -> DisplayPoint -> Point` to `Anchor -> Point`
|
||||
let (to_convert, selections) = resolve_selections_display(selections, map).tee();
|
||||
let mut converted_endpoints =
|
||||
map.buffer_snapshot()
|
||||
|
||||
@@ -4,8 +4,8 @@ use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects, multibuffer_con
|
||||
use git::repository::{CommitDetails, CommitDiff, RepoPath};
|
||||
use gpui::{
|
||||
Action, AnyElement, AnyView, App, AppContext as _, AsyncApp, AsyncWindowContext, Context,
|
||||
Entity, EventEmitter, FocusHandle, Focusable, IntoElement, PromptLevel, Render, Task,
|
||||
WeakEntity, Window, actions,
|
||||
Entity, EventEmitter, FocusHandle, Focusable, IntoElement, PromptLevel, Render, WeakEntity,
|
||||
Window, actions,
|
||||
};
|
||||
use language::{
|
||||
Anchor, Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _,
|
||||
@@ -561,11 +561,11 @@ impl Item for CommitView {
|
||||
_workspace_id: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Some(cx.new(|cx| {
|
||||
let editor = cx.new(|cx| {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.clone(window, cx))
|
||||
@@ -577,7 +577,7 @@ impl Item for CommitView {
|
||||
commit: self.commit.clone(),
|
||||
stash: self.stash,
|
||||
}
|
||||
})))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -625,16 +625,12 @@ impl Item for ProjectDiff {
|
||||
_workspace_id: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
ProjectDiff::new(self.project.clone(), workspace, window, cx)
|
||||
})))
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
Some(cx.new(|cx| ProjectDiff::new(self.project.clone(), workspace, window, cx)))
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &App) -> bool {
|
||||
|
||||
@@ -111,15 +111,14 @@ impl CursorPosition {
|
||||
}
|
||||
editor::EditorMode::Full { .. } => {
|
||||
let mut last_selection = None::<Selection<Point>>;
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
if snapshot.excerpts().count() > 0 {
|
||||
for selection in
|
||||
editor.selections.all_adjusted_with_snapshot(&snapshot)
|
||||
{
|
||||
let snapshot = editor.display_snapshot(cx);
|
||||
if snapshot.buffer_snapshot().excerpts().count() > 0 {
|
||||
for selection in editor.selections.all_adjusted(&snapshot) {
|
||||
let selection_summary = snapshot
|
||||
.buffer_snapshot()
|
||||
.text_summary_for_range::<text::TextSummary, _>(
|
||||
selection.start..selection.end,
|
||||
);
|
||||
selection.start..selection.end,
|
||||
);
|
||||
cursor_position.selected_count.characters +=
|
||||
selection_summary.chars;
|
||||
if selection.end != selection.start {
|
||||
@@ -136,8 +135,12 @@ impl CursorPosition {
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor_position.position = last_selection
|
||||
.map(|s| UserCaretPosition::at_selection_end(&s, &snapshot));
|
||||
cursor_position.position = last_selection.map(|s| {
|
||||
UserCaretPosition::at_selection_end(
|
||||
&s,
|
||||
snapshot.buffer_snapshot(),
|
||||
)
|
||||
});
|
||||
cursor_position.context = Some(editor.focus_handle(cx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
use anyhow::Context as _;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
mem,
|
||||
ops::Range,
|
||||
@@ -334,12 +335,11 @@ impl TextLayout {
|
||||
.line_height
|
||||
.to_pixels(font_size.into(), window.rem_size());
|
||||
|
||||
let mut runs = if let Some(runs) = runs {
|
||||
let runs = if let Some(runs) = runs {
|
||||
runs
|
||||
} else {
|
||||
vec![text_style.to_run(text.len())]
|
||||
};
|
||||
|
||||
window.request_measured_layout(Default::default(), {
|
||||
let element_state = self.clone();
|
||||
|
||||
@@ -378,15 +378,15 @@ impl TextLayout {
|
||||
}
|
||||
|
||||
let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size);
|
||||
let text = if let Some(truncate_width) = truncate_width {
|
||||
let (text, runs) = if let Some(truncate_width) = truncate_width {
|
||||
line_wrapper.truncate_line(
|
||||
text.clone(),
|
||||
truncate_width,
|
||||
&truncation_suffix,
|
||||
&mut runs,
|
||||
&runs,
|
||||
)
|
||||
} else {
|
||||
text.clone()
|
||||
(text.clone(), Cow::Borrowed(&*runs))
|
||||
};
|
||||
let len = text.len();
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ pub use gpui_macros::{
|
||||
overflow_style_methods, padding_style_methods, position_style_methods,
|
||||
visibility_style_methods,
|
||||
};
|
||||
|
||||
const ELLIPSIS: SharedString = SharedString::new_static("…");
|
||||
|
||||
/// A trait for elements that can be styled.
|
||||
|
||||
@@ -418,22 +418,21 @@ impl WindowTextSystem {
|
||||
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
|
||||
|
||||
let mut lines = SmallVec::new();
|
||||
let mut line_start = 0;
|
||||
let mut max_wrap_lines = line_clamp.unwrap_or(usize::MAX);
|
||||
let mut max_wrap_lines = line_clamp;
|
||||
let mut wrapped_lines = 0;
|
||||
|
||||
let mut process_line = |line_text: SharedString| {
|
||||
let mut process_line = |line_text: SharedString, line_start, line_end| {
|
||||
font_runs.clear();
|
||||
let line_end = line_start + line_text.len();
|
||||
|
||||
let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
|
||||
let mut run_start = line_start;
|
||||
while run_start < line_end {
|
||||
let Some(run) = runs.peek_mut() else {
|
||||
log::warn!("`TextRun`s do not cover the entire to be shaped text");
|
||||
break;
|
||||
};
|
||||
|
||||
let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start;
|
||||
let run_len_within_line = cmp::min(line_end - run_start, run.len);
|
||||
|
||||
let decoration_changed = if let Some(last_run) = decoration_runs.last_mut()
|
||||
&& last_run.color == run.color
|
||||
@@ -467,11 +466,10 @@ impl WindowTextSystem {
|
||||
});
|
||||
}
|
||||
|
||||
if run_len_within_line == run.len {
|
||||
// Preserve the remainder of the run for the next line
|
||||
run.len -= run_len_within_line;
|
||||
if run.len == 0 {
|
||||
runs.next();
|
||||
} else {
|
||||
// Preserve the remainder of the run for the next line
|
||||
run.len -= run_len_within_line;
|
||||
}
|
||||
run_start += run_len_within_line;
|
||||
}
|
||||
@@ -481,7 +479,7 @@ impl WindowTextSystem {
|
||||
font_size,
|
||||
&font_runs,
|
||||
wrap_width,
|
||||
Some(max_wrap_lines - wrapped_lines),
|
||||
max_wrap_lines.map(|max| max.saturating_sub(wrapped_lines)),
|
||||
);
|
||||
wrapped_lines += layout.wrap_boundaries.len();
|
||||
|
||||
@@ -492,7 +490,6 @@ impl WindowTextSystem {
|
||||
});
|
||||
|
||||
// Skip `\n` character.
|
||||
line_start = line_end + 1;
|
||||
if let Some(run) = runs.peek_mut() {
|
||||
run.len -= 1;
|
||||
if run.len == 0 {
|
||||
@@ -502,21 +499,34 @@ impl WindowTextSystem {
|
||||
};
|
||||
|
||||
let mut split_lines = text.split('\n');
|
||||
let mut processed = false;
|
||||
|
||||
// Special case single lines to prevent allocating a sharedstring
|
||||
if let Some(first_line) = split_lines.next()
|
||||
&& let Some(second_line) = split_lines.next()
|
||||
{
|
||||
processed = true;
|
||||
process_line(first_line.to_string().into());
|
||||
process_line(second_line.to_string().into());
|
||||
let mut line_start = 0;
|
||||
process_line(
|
||||
SharedString::new(first_line),
|
||||
line_start,
|
||||
line_start + first_line.len(),
|
||||
);
|
||||
line_start += first_line.len() + '\n'.len_utf8();
|
||||
process_line(
|
||||
SharedString::new(second_line),
|
||||
line_start,
|
||||
line_start + second_line.len(),
|
||||
);
|
||||
for line_text in split_lines {
|
||||
process_line(line_text.to_string().into());
|
||||
line_start += line_text.len() + '\n'.len_utf8();
|
||||
process_line(
|
||||
SharedString::new(line_text),
|
||||
line_start,
|
||||
line_start + line_text.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if !processed {
|
||||
process_line(text);
|
||||
} else {
|
||||
let end = text.len();
|
||||
process_line(text, 0, end);
|
||||
}
|
||||
|
||||
self.font_runs_pool.lock().push(font_runs);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{FontId, FontRun, Pixels, PlatformTextSystem, SharedString, TextRun, px};
|
||||
use collections::HashMap;
|
||||
use std::{iter, sync::Arc};
|
||||
use std::{borrow::Cow, iter, sync::Arc};
|
||||
|
||||
/// The GPUI line wrapper, used to wrap lines of text to a given width.
|
||||
pub struct LineWrapper {
|
||||
@@ -129,13 +129,13 @@ impl LineWrapper {
|
||||
}
|
||||
|
||||
/// Truncate a line of text to the given width with this wrapper's font and font size.
|
||||
pub fn truncate_line(
|
||||
pub fn truncate_line<'a>(
|
||||
&mut self,
|
||||
line: SharedString,
|
||||
truncate_width: Pixels,
|
||||
truncation_suffix: &str,
|
||||
runs: &mut Vec<TextRun>,
|
||||
) -> SharedString {
|
||||
runs: &'a [TextRun],
|
||||
) -> (SharedString, Cow<'a, [TextRun]>) {
|
||||
let mut width = px(0.);
|
||||
let mut suffix_width = truncation_suffix
|
||||
.chars()
|
||||
@@ -154,13 +154,14 @@ impl LineWrapper {
|
||||
if width.floor() > truncate_width {
|
||||
let result =
|
||||
SharedString::from(format!("{}{}", &line[..truncate_ix], truncation_suffix));
|
||||
update_runs_after_truncation(&result, truncation_suffix, runs);
|
||||
let mut runs = runs.to_vec();
|
||||
update_runs_after_truncation(&result, truncation_suffix, &mut runs);
|
||||
|
||||
return result;
|
||||
return (result, Cow::Owned(runs));
|
||||
}
|
||||
}
|
||||
|
||||
line
|
||||
(line, Cow::Borrowed(runs))
|
||||
}
|
||||
|
||||
/// Any character in this list should be treated as a word character,
|
||||
@@ -493,15 +494,14 @@ mod tests {
|
||||
fn perform_test(
|
||||
wrapper: &mut LineWrapper,
|
||||
text: &'static str,
|
||||
result: &'static str,
|
||||
expected: &'static str,
|
||||
ellipsis: &str,
|
||||
) {
|
||||
let dummy_run_lens = vec![text.len()];
|
||||
let mut dummy_runs = generate_test_runs(&dummy_run_lens);
|
||||
assert_eq!(
|
||||
wrapper.truncate_line(text.into(), px(220.), ellipsis, &mut dummy_runs),
|
||||
result
|
||||
);
|
||||
let dummy_runs = generate_test_runs(&dummy_run_lens);
|
||||
let (result, dummy_runs) =
|
||||
wrapper.truncate_line(text.into(), px(220.), ellipsis, &dummy_runs);
|
||||
assert_eq!(result, expected);
|
||||
assert_eq!(dummy_runs.first().unwrap().len, result.len());
|
||||
}
|
||||
|
||||
@@ -532,16 +532,15 @@ mod tests {
|
||||
fn perform_test(
|
||||
wrapper: &mut LineWrapper,
|
||||
text: &'static str,
|
||||
result: &str,
|
||||
expected: &str,
|
||||
run_lens: &[usize],
|
||||
result_run_len: &[usize],
|
||||
line_width: Pixels,
|
||||
) {
|
||||
let mut dummy_runs = generate_test_runs(run_lens);
|
||||
assert_eq!(
|
||||
wrapper.truncate_line(text.into(), line_width, "…", &mut dummy_runs),
|
||||
result
|
||||
);
|
||||
let dummy_runs = generate_test_runs(run_lens);
|
||||
let (result, dummy_runs) =
|
||||
wrapper.truncate_line(text.into(), line_width, "…", &dummy_runs);
|
||||
assert_eq!(result, expected);
|
||||
for (run, result_len) in dummy_runs.iter().zip(result_run_len) {
|
||||
assert_eq!(run.len, *result_len);
|
||||
}
|
||||
|
||||
@@ -3243,14 +3243,11 @@ impl Window {
|
||||
/// returns a `Size`.
|
||||
///
|
||||
/// This method should only be called as part of the request_layout or prepaint phase of element drawing.
|
||||
pub fn request_measured_layout<
|
||||
F: FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut Window, &mut App) -> Size<Pixels>
|
||||
pub fn request_measured_layout<F>(&mut self, style: Style, measure: F) -> LayoutId
|
||||
where
|
||||
F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>, &mut Window, &mut App) -> Size<Pixels>
|
||||
+ 'static,
|
||||
>(
|
||||
&mut self,
|
||||
style: Style,
|
||||
measure: F,
|
||||
) -> LayoutId {
|
||||
{
|
||||
self.invalidator.debug_assert_prepaint();
|
||||
|
||||
let rem_size = self.rem_size();
|
||||
|
||||
@@ -179,15 +179,15 @@ impl Item for ImageView {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| Self {
|
||||
Some(cx.new(|cx| Self {
|
||||
image_item: self.image_item.clone(),
|
||||
project: self.project.clone(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
fn has_deleted_file(&self, cx: &App) -> bool {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use gpui::{
|
||||
Action, App, AppContext as _, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
KeyBindingContextPredicate, KeyContext, Keystroke, MouseButton, Render, Subscription, Task,
|
||||
actions,
|
||||
KeyBindingContextPredicate, KeyContext, Keystroke, MouseButton, Render, Subscription, actions,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use serde_json::json;
|
||||
@@ -158,11 +157,11 @@ impl Item for KeyContextView {
|
||||
_workspace_id: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| KeyContextView::new(window, cx))))
|
||||
Some(cx.new(|cx| KeyContextView::new(window, cx)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use copilot::Copilot;
|
||||
use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};
|
||||
use gpui::{
|
||||
AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||
ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, actions, div,
|
||||
ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{LanguageServerId, language_settings::SoftWrap};
|
||||
@@ -763,11 +763,11 @@ impl Item for LspLogView {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Some(cx.new(|cx| {
|
||||
let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), window, cx);
|
||||
if let Some(server_id) = self.current_server_id {
|
||||
match self.active_entry_kind {
|
||||
@@ -778,7 +778,7 @@ impl Item for LspLogView {
|
||||
}
|
||||
}
|
||||
new_view
|
||||
})))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use editor::{Anchor, Editor, ExcerptId, SelectionEffects, scroll::Autoscroll};
|
||||
use gpui::{
|
||||
App, AppContext as _, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||
Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
ParentElement, Render, ScrollStrategy, SharedString, Styled, Task, UniformListScrollHandle,
|
||||
ParentElement, Render, ScrollStrategy, SharedString, Styled, UniformListScrollHandle,
|
||||
WeakEntity, Window, actions, div, rems, uniform_list,
|
||||
};
|
||||
use language::{Buffer, OwnedSyntaxLayer};
|
||||
@@ -573,17 +573,17 @@ impl Item for SyntaxTreeView {
|
||||
_: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Some(cx.new(|cx| {
|
||||
let mut clone = Self::new(self.workspace_handle.clone(), None, window, cx);
|
||||
if let Some(editor) = &self.editor {
|
||||
clone.set_editor(editor.editor.clone(), window, cx)
|
||||
}
|
||||
clone
|
||||
})))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ fn restore_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Res
|
||||
} else {
|
||||
vec![formatter.clone()]
|
||||
};
|
||||
if formatter_array.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut code_action_formatters = Vec::new();
|
||||
for formatter in formatter_array {
|
||||
let Some(code_action) = formatter.get("code_action") else {
|
||||
|
||||
@@ -17,6 +17,7 @@ pub fn make_file_finder_include_ignored_an_enum(value: &mut Value) -> Result<()>
|
||||
Value::Bool(true) => Value::String("all".to_string()),
|
||||
Value::Bool(false) => Value::String("indexed".to_string()),
|
||||
Value::Null => Value::String("smart".to_string()),
|
||||
Value::String(s) if s == "all" || s == "indexed" || s == "smart" => return Ok(()),
|
||||
_ => anyhow::bail!("Expected include_ignored to be a boolean or null"),
|
||||
};
|
||||
Ok(())
|
||||
|
||||
@@ -366,7 +366,13 @@ mod tests {
|
||||
#[track_caller]
|
||||
fn assert_migrate_settings(input: &str, output: Option<&str>) {
|
||||
let migrated = migrate_settings(input).unwrap();
|
||||
assert_migrated_correctly(migrated, output);
|
||||
assert_migrated_correctly(migrated.clone(), output);
|
||||
|
||||
// expect that rerunning the migration does not result in another migration
|
||||
if let Some(migrated) = migrated {
|
||||
let rerun = migrate_settings(&migrated).unwrap();
|
||||
assert_migrated_correctly(rerun, None);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -376,7 +382,13 @@ mod tests {
|
||||
output: Option<&str>,
|
||||
) {
|
||||
let migrated = run_migrations(input, migrations).unwrap();
|
||||
assert_migrated_correctly(migrated, output);
|
||||
assert_migrated_correctly(migrated.clone(), output);
|
||||
|
||||
// expect that rerunning the migration does not result in another migration
|
||||
if let Some(migrated) = migrated {
|
||||
let rerun = run_migrations(&migrated, migrations).unwrap();
|
||||
assert_migrated_correctly(rerun, None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2090,6 +2102,21 @@ mod tests {
|
||||
.unindent(),
|
||||
),
|
||||
);
|
||||
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[MigrationType::Json(
|
||||
migrations::m_2025_10_16::restore_code_actions_on_format,
|
||||
)],
|
||||
&r#"{
|
||||
"formatter": [],
|
||||
"code_actions_on_format": {
|
||||
"bar": true,
|
||||
"baz": false
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -6389,17 +6389,6 @@ impl MultiBufferSnapshot {
|
||||
debug_ranges.insert(key, text_ranges, format!("{value:?}").into())
|
||||
});
|
||||
}
|
||||
|
||||
// used by line_mode selections and tries to match vim behavior
|
||||
pub fn expand_to_line(&self, range: Range<Point>) -> Range<Point> {
|
||||
let new_start = MultiBufferPoint::new(range.start.row, 0);
|
||||
let new_end = if range.end.column > 0 {
|
||||
MultiBufferPoint::new(range.end.row, self.line_len(MultiBufferRow(range.end.row)))
|
||||
} else {
|
||||
range.end
|
||||
};
|
||||
new_start..new_end
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -384,14 +384,14 @@ impl Item for Onboarding {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>> {
|
||||
Task::ready(Some(cx.new(|cx| Onboarding {
|
||||
) -> Option<Entity<Self>> {
|
||||
Some(cx.new(|cx| Onboarding {
|
||||
workspace: self.workspace.clone(),
|
||||
user_store: self.user_store.clone(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
_settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||
|
||||
@@ -26,8 +26,8 @@ use language::{
|
||||
use lsp::{
|
||||
AdapterServerCapabilities, CodeActionKind, CodeActionOptions, CodeDescription,
|
||||
CompletionContext, CompletionListItemDefaultsEditRange, CompletionTriggerKind,
|
||||
DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities,
|
||||
OneOf, RenameOptions, ServerCapabilities,
|
||||
DiagnosticServerCapabilities, DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||
LinkedEditingRangeServerCapabilities, OneOf, RenameOptions, ServerCapabilities,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
|
||||
@@ -262,6 +262,9 @@ pub(crate) struct LinkedEditingRange {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct GetDocumentDiagnostics {
|
||||
/// We cannot blindly rely on server's capabilities.diagnostic_provider, as they're a singular field, whereas
|
||||
/// a server can register multiple diagnostic providers post-mortem.
|
||||
pub dynamic_caps: DiagnosticServerCapabilities,
|
||||
pub previous_result_id: Option<String>,
|
||||
}
|
||||
|
||||
@@ -4031,26 +4034,22 @@ impl LspCommand for GetDocumentDiagnostics {
|
||||
"Get diagnostics"
|
||||
}
|
||||
|
||||
fn check_capabilities(&self, server_capabilities: AdapterServerCapabilities) -> bool {
|
||||
server_capabilities
|
||||
.server_capabilities
|
||||
.diagnostic_provider
|
||||
.is_some()
|
||||
fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
language_server: &Arc<LanguageServer>,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &App,
|
||||
) -> Result<lsp::DocumentDiagnosticParams> {
|
||||
let identifier = match language_server.capabilities().diagnostic_provider {
|
||||
Some(lsp::DiagnosticServerCapabilities::Options(options)) => options.identifier,
|
||||
Some(lsp::DiagnosticServerCapabilities::RegistrationOptions(options)) => {
|
||||
options.diagnostic_options.identifier
|
||||
let identifier = match &self.dynamic_caps {
|
||||
lsp::DiagnosticServerCapabilities::Options(options) => options.identifier.clone(),
|
||||
lsp::DiagnosticServerCapabilities::RegistrationOptions(options) => {
|
||||
options.diagnostic_options.identifier.clone()
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(lsp::DocumentDiagnosticParams {
|
||||
|
||||
@@ -71,12 +71,12 @@ use language::{
|
||||
range_from_lsp, range_to_lsp,
|
||||
};
|
||||
use lsp::{
|
||||
AdapterServerCapabilities, CodeActionKind, CompletionContext, DiagnosticSeverity,
|
||||
DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, Edit, FileOperationFilter,
|
||||
FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher,
|
||||
LSP_REQUEST_TIMEOUT, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions,
|
||||
LanguageServerId, LanguageServerName, LanguageServerSelector, LspRequestFuture,
|
||||
MessageActionItem, MessageType, OneOf, RenameFilesParams, SymbolKind,
|
||||
AdapterServerCapabilities, CodeActionKind, CompletionContext, DiagnosticServerCapabilities,
|
||||
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, Edit,
|
||||
FileOperationFilter, FileOperationPatternKind, FileOperationRegistrationOptions, FileRename,
|
||||
FileSystemWatcher, LSP_REQUEST_TIMEOUT, LanguageServer, LanguageServerBinary,
|
||||
LanguageServerBinaryOptions, LanguageServerId, LanguageServerName, LanguageServerSelector,
|
||||
LspRequestFuture, MessageActionItem, MessageType, OneOf, RenameFilesParams, SymbolKind,
|
||||
TextDocumentSyncSaveOptions, TextEdit, Uri, WillRenameFiles, WorkDoneProgressCancelParams,
|
||||
WorkspaceFolder, notification::DidRenameFiles,
|
||||
};
|
||||
@@ -182,6 +182,12 @@ pub struct DocumentDiagnostics {
|
||||
version: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DynamicRegistrations {
|
||||
did_change_watched_files: HashMap<String, Vec<FileSystemWatcher>>,
|
||||
diagnostics: HashMap<Option<String>, DiagnosticServerCapabilities>,
|
||||
}
|
||||
|
||||
pub struct LocalLspStore {
|
||||
weak: WeakEntity<LspStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
@@ -199,8 +205,7 @@ pub struct LocalLspStore {
|
||||
watched_manifest_filenames: HashSet<ManifestName>,
|
||||
language_server_paths_watched_for_rename:
|
||||
HashMap<LanguageServerId, RenamePathsWatchedForServer>,
|
||||
language_server_watcher_registrations:
|
||||
HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
|
||||
language_server_dynamic_registrations: HashMap<LanguageServerId, DynamicRegistrations>,
|
||||
supplementary_language_servers:
|
||||
HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
|
||||
prettier_store: Entity<PrettierStore>,
|
||||
@@ -3174,7 +3179,7 @@ impl LocalLspStore {
|
||||
|
||||
for watcher in watchers {
|
||||
if let Some((worktree, literal_prefix, pattern)) =
|
||||
self.worktree_and_path_for_file_watcher(&worktrees, watcher, cx)
|
||||
Self::worktree_and_path_for_file_watcher(&worktrees, watcher, cx)
|
||||
{
|
||||
worktree.update(cx, |worktree, _| {
|
||||
if let Some((tree, glob)) =
|
||||
@@ -3272,7 +3277,6 @@ impl LocalLspStore {
|
||||
}
|
||||
|
||||
fn worktree_and_path_for_file_watcher(
|
||||
&self,
|
||||
worktrees: &[Entity<Worktree>],
|
||||
watcher: &FileSystemWatcher,
|
||||
cx: &App,
|
||||
@@ -3320,15 +3324,18 @@ impl LocalLspStore {
|
||||
language_server_id: LanguageServerId,
|
||||
cx: &mut Context<LspStore>,
|
||||
) {
|
||||
let Some(watchers) = self
|
||||
.language_server_watcher_registrations
|
||||
let Some(registrations) = self
|
||||
.language_server_dynamic_registrations
|
||||
.get(&language_server_id)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let watch_builder =
|
||||
self.rebuild_watched_paths_inner(language_server_id, watchers.values().flatten(), cx);
|
||||
let watch_builder = self.rebuild_watched_paths_inner(
|
||||
language_server_id,
|
||||
registrations.did_change_watched_files.values().flatten(),
|
||||
cx,
|
||||
);
|
||||
let watcher = watch_builder.build(self.fs.clone(), language_server_id, cx);
|
||||
self.language_server_watched_paths
|
||||
.insert(language_server_id, watcher);
|
||||
@@ -3344,11 +3351,13 @@ impl LocalLspStore {
|
||||
cx: &mut Context<LspStore>,
|
||||
) {
|
||||
let registrations = self
|
||||
.language_server_watcher_registrations
|
||||
.language_server_dynamic_registrations
|
||||
.entry(language_server_id)
|
||||
.or_default();
|
||||
|
||||
registrations.insert(registration_id.to_string(), params.watchers);
|
||||
registrations
|
||||
.did_change_watched_files
|
||||
.insert(registration_id.to_string(), params.watchers);
|
||||
|
||||
self.rebuild_watched_paths(language_server_id, cx);
|
||||
}
|
||||
@@ -3360,11 +3369,15 @@ impl LocalLspStore {
|
||||
cx: &mut Context<LspStore>,
|
||||
) {
|
||||
let registrations = self
|
||||
.language_server_watcher_registrations
|
||||
.language_server_dynamic_registrations
|
||||
.entry(language_server_id)
|
||||
.or_default();
|
||||
|
||||
if registrations.remove(registration_id).is_some() {
|
||||
if registrations
|
||||
.did_change_watched_files
|
||||
.remove(registration_id)
|
||||
.is_some()
|
||||
{
|
||||
log::info!(
|
||||
"language server {}: unregistered workspace/DidChangeWatchedFiles capability with id {}",
|
||||
language_server_id,
|
||||
@@ -3729,7 +3742,7 @@ impl LspStore {
|
||||
last_workspace_edits_by_language_server: Default::default(),
|
||||
language_server_watched_paths: Default::default(),
|
||||
language_server_paths_watched_for_rename: Default::default(),
|
||||
language_server_watcher_registrations: Default::default(),
|
||||
language_server_dynamic_registrations: Default::default(),
|
||||
buffers_being_formatted: Default::default(),
|
||||
buffer_snapshots: Default::default(),
|
||||
prettier_store,
|
||||
@@ -4317,7 +4330,7 @@ impl LspStore {
|
||||
cx: &Context<Self>,
|
||||
) -> bool
|
||||
where
|
||||
F: Fn(&lsp::ServerCapabilities) -> bool,
|
||||
F: FnMut(&lsp::ServerCapabilities) -> bool,
|
||||
{
|
||||
let Some(language) = buffer.read(cx).language().cloned() else {
|
||||
return false;
|
||||
@@ -6319,12 +6332,30 @@ impl LspStore {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
|
||||
if let Some((client, upstream_project_id)) = self.upstream_client() {
|
||||
let mut suitable_capabilities = None;
|
||||
// Are we capable for proto request?
|
||||
let any_server_has_diagnostics_provider = self.check_if_capable_for_proto_request(
|
||||
&buffer,
|
||||
|capabilities| {
|
||||
if let Some(caps) = &capabilities.diagnostic_provider {
|
||||
suitable_capabilities = Some(caps.clone());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
cx,
|
||||
);
|
||||
// We don't really care which caps are passed into the request, as they're ignored by RPC anyways.
|
||||
let Some(dynamic_caps) = suitable_capabilities else {
|
||||
return Task::ready(Ok(None));
|
||||
};
|
||||
assert!(any_server_has_diagnostics_provider);
|
||||
|
||||
let request = GetDocumentDiagnostics {
|
||||
previous_result_id: None,
|
||||
dynamic_caps,
|
||||
};
|
||||
if !self.is_capable_for_proto_request(&buffer, &request, cx) {
|
||||
return Task::ready(Ok(None));
|
||||
}
|
||||
let request_task = client.request_lsp(
|
||||
upstream_project_id,
|
||||
LSP_REQUEST_TIMEOUT,
|
||||
@@ -6339,23 +6370,44 @@ impl LspStore {
|
||||
Ok(None)
|
||||
})
|
||||
} else {
|
||||
let server_ids = buffer.update(cx, |buffer, cx| {
|
||||
let servers = buffer.update(cx, |buffer, cx| {
|
||||
self.language_servers_for_local_buffer(buffer, cx)
|
||||
.map(|(_, server)| server.server_id())
|
||||
.map(|(_, server)| server.clone())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let pull_diagnostics = server_ids
|
||||
|
||||
let pull_diagnostics = servers
|
||||
.into_iter()
|
||||
.map(|server_id| {
|
||||
let result_id = self.result_id(server_id, buffer_id, cx);
|
||||
self.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
GetDocumentDiagnostics {
|
||||
previous_result_id: result_id,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.flat_map(|server| {
|
||||
let result = maybe!({
|
||||
let local = self.as_local()?;
|
||||
let server_id = server.server_id();
|
||||
let providers_with_identifiers = local
|
||||
.language_server_dynamic_registrations
|
||||
.get(&server_id)
|
||||
.into_iter()
|
||||
.flat_map(|registrations| registrations.diagnostics.values().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
Some(
|
||||
providers_with_identifiers
|
||||
.into_iter()
|
||||
.map(|dynamic_caps| {
|
||||
let result_id = self.result_id(server_id, buffer_id, cx);
|
||||
self.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
GetDocumentDiagnostics {
|
||||
previous_result_id: result_id,
|
||||
dynamic_caps,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
});
|
||||
|
||||
result.unwrap_or_default()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -8875,14 +8927,17 @@ impl LspStore {
|
||||
);
|
||||
}
|
||||
lsp::ProgressParamsValue::WorkspaceDiagnostic(report) => {
|
||||
let identifier = token.split_once("id:").map(|(_, id)| id.to_owned());
|
||||
if let Some(LanguageServerState::Running {
|
||||
workspace_refresh_task: Some(workspace_refresh_task),
|
||||
workspace_diagnostics_refresh_tasks,
|
||||
..
|
||||
}) = self
|
||||
.as_local_mut()
|
||||
.and_then(|local| local.language_servers.get_mut(&language_server_id))
|
||||
&& let Some(workspace_diagnostics) =
|
||||
workspace_diagnostics_refresh_tasks.get_mut(&identifier)
|
||||
{
|
||||
workspace_refresh_task.progress_tx.try_send(()).ok();
|
||||
workspace_diagnostics.progress_tx.try_send(()).ok();
|
||||
self.apply_workspace_diagnostic_report(language_server_id, report, cx)
|
||||
}
|
||||
}
|
||||
@@ -10380,13 +10435,31 @@ impl LspStore {
|
||||
let workspace_folders = workspace_folders.lock().clone();
|
||||
language_server.set_workspace_folders(workspace_folders);
|
||||
|
||||
let workspace_diagnostics_refresh_tasks = language_server
|
||||
.capabilities()
|
||||
.diagnostic_provider
|
||||
.and_then(|provider| {
|
||||
let workspace_refresher = lsp_workspace_diagnostics_refresh(
|
||||
None,
|
||||
provider.clone(),
|
||||
language_server.clone(),
|
||||
cx,
|
||||
)?;
|
||||
local
|
||||
.language_server_dynamic_registrations
|
||||
.entry(server_id)
|
||||
.or_default()
|
||||
.diagnostics
|
||||
.entry(None)
|
||||
.or_insert(provider);
|
||||
Some((None, workspace_refresher))
|
||||
})
|
||||
.into_iter()
|
||||
.collect();
|
||||
local.language_servers.insert(
|
||||
server_id,
|
||||
LanguageServerState::Running {
|
||||
workspace_refresh_task: lsp_workspace_diagnostics_refresh(
|
||||
language_server.clone(),
|
||||
cx,
|
||||
),
|
||||
workspace_diagnostics_refresh_tasks,
|
||||
adapter: adapter.clone(),
|
||||
server: language_server.clone(),
|
||||
simulate_disk_based_diagnostics_completion: None,
|
||||
@@ -11096,13 +11169,15 @@ impl LspStore {
|
||||
|
||||
pub fn pull_workspace_diagnostics(&mut self, server_id: LanguageServerId) {
|
||||
if let Some(LanguageServerState::Running {
|
||||
workspace_refresh_task: Some(workspace_refresh_task),
|
||||
workspace_diagnostics_refresh_tasks,
|
||||
..
|
||||
}) = self
|
||||
.as_local_mut()
|
||||
.and_then(|local| local.language_servers.get_mut(&server_id))
|
||||
{
|
||||
workspace_refresh_task.refresh_tx.try_send(()).ok();
|
||||
for diagnostics in workspace_diagnostics_refresh_tasks.values_mut() {
|
||||
diagnostics.refresh_tx.try_send(()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11118,11 +11193,13 @@ impl LspStore {
|
||||
local.language_server_ids_for_buffer(buffer, cx)
|
||||
}) {
|
||||
if let Some(LanguageServerState::Running {
|
||||
workspace_refresh_task: Some(workspace_refresh_task),
|
||||
workspace_diagnostics_refresh_tasks,
|
||||
..
|
||||
}) = local.language_servers.get_mut(&server_id)
|
||||
{
|
||||
workspace_refresh_task.refresh_tx.try_send(()).ok();
|
||||
for diagnostics in workspace_diagnostics_refresh_tasks.values_mut() {
|
||||
diagnostics.refresh_tx.try_send(()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11448,26 +11525,49 @@ impl LspStore {
|
||||
"textDocument/diagnostic" => {
|
||||
if let Some(caps) = reg
|
||||
.register_options
|
||||
.map(serde_json::from_value)
|
||||
.map(serde_json::from_value::<DiagnosticServerCapabilities>)
|
||||
.transpose()?
|
||||
{
|
||||
let state = self
|
||||
let local = self
|
||||
.as_local_mut()
|
||||
.context("Expected LSP Store to be local")?
|
||||
.context("Expected LSP Store to be local")?;
|
||||
let state = local
|
||||
.language_servers
|
||||
.get_mut(&server_id)
|
||||
.context("Could not obtain Language Servers state")?;
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.diagnostic_provider = Some(caps);
|
||||
});
|
||||
local
|
||||
.language_server_dynamic_registrations
|
||||
.get_mut(&server_id)
|
||||
.and_then(|registrations| {
|
||||
registrations
|
||||
.diagnostics
|
||||
.insert(Some(reg.id.clone()), caps.clone())
|
||||
});
|
||||
|
||||
let mut can_now_provide_diagnostics = false;
|
||||
if let LanguageServerState::Running {
|
||||
workspace_refresh_task,
|
||||
workspace_diagnostics_refresh_tasks,
|
||||
..
|
||||
} = state
|
||||
&& workspace_refresh_task.is_none()
|
||||
&& let Some(task) = lsp_workspace_diagnostics_refresh(
|
||||
Some(reg.id.clone()),
|
||||
caps.clone(),
|
||||
server.clone(),
|
||||
cx,
|
||||
)
|
||||
{
|
||||
*workspace_refresh_task =
|
||||
lsp_workspace_diagnostics_refresh(server.clone(), cx)
|
||||
workspace_diagnostics_refresh_tasks.insert(Some(reg.id), task);
|
||||
can_now_provide_diagnostics = true;
|
||||
}
|
||||
|
||||
// We don't actually care about capabilities.diagnostic_provider, but it IS relevant for the remote peer
|
||||
// to know that there's at least one provider. Otherwise, it will never ask us to issue documentdiagnostic calls on their behalf,
|
||||
// as it'll think that they're not supported.
|
||||
if can_now_provide_diagnostics {
|
||||
server.update_capabilities(|capabilities| {
|
||||
debug_assert!(capabilities.diagnostic_provider.is_none());
|
||||
capabilities.diagnostic_provider = Some(caps);
|
||||
});
|
||||
}
|
||||
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
@@ -11630,22 +11730,45 @@ impl LspStore {
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/diagnostic" => {
|
||||
server.update_capabilities(|capabilities| {
|
||||
capabilities.diagnostic_provider = None;
|
||||
});
|
||||
let state = self
|
||||
let local = self
|
||||
.as_local_mut()
|
||||
.context("Expected LSP Store to be local")?
|
||||
.context("Expected LSP Store to be local")?;
|
||||
|
||||
let state = local
|
||||
.language_servers
|
||||
.get_mut(&server_id)
|
||||
.context("Could not obtain Language Servers state")?;
|
||||
if let LanguageServerState::Running {
|
||||
workspace_refresh_task,
|
||||
..
|
||||
} = state
|
||||
let options = local
|
||||
.language_server_dynamic_registrations
|
||||
.get_mut(&server_id)
|
||||
.with_context(|| {
|
||||
format!("Expected dynamic registration to exist for server {server_id}")
|
||||
})?.diagnostics
|
||||
.remove(&Some(unreg.id.clone()))
|
||||
.with_context(|| format!(
|
||||
"Attempted to unregister non-existent diagnostic registration with ID {}",
|
||||
unreg.id)
|
||||
)?;
|
||||
|
||||
let mut has_any_diagnostic_providers_still = true;
|
||||
if let Some(identifier) = diagnostic_identifier(&options)
|
||||
&& let LanguageServerState::Running {
|
||||
workspace_diagnostics_refresh_tasks,
|
||||
..
|
||||
} = state
|
||||
{
|
||||
_ = workspace_refresh_task.take();
|
||||
workspace_diagnostics_refresh_tasks.remove(&identifier);
|
||||
has_any_diagnostic_providers_still =
|
||||
!workspace_diagnostics_refresh_tasks.is_empty();
|
||||
}
|
||||
|
||||
if !has_any_diagnostic_providers_still {
|
||||
server.update_capabilities(|capabilities| {
|
||||
debug_assert!(capabilities.diagnostic_provider.is_some());
|
||||
capabilities.diagnostic_provider = None;
|
||||
});
|
||||
}
|
||||
|
||||
notify_server_capabilities_updated(&server, cx);
|
||||
}
|
||||
"textDocument/documentColor" => {
|
||||
@@ -11831,24 +11954,12 @@ fn subscribe_to_binary_statuses(
|
||||
}
|
||||
|
||||
fn lsp_workspace_diagnostics_refresh(
|
||||
registration_id: Option<String>,
|
||||
options: DiagnosticServerCapabilities,
|
||||
server: Arc<LanguageServer>,
|
||||
cx: &mut Context<'_, LspStore>,
|
||||
) -> Option<WorkspaceRefreshTask> {
|
||||
let identifier = match server.capabilities().diagnostic_provider? {
|
||||
lsp::DiagnosticServerCapabilities::Options(diagnostic_options) => {
|
||||
if !diagnostic_options.workspace_diagnostics {
|
||||
return None;
|
||||
}
|
||||
diagnostic_options.identifier
|
||||
}
|
||||
lsp::DiagnosticServerCapabilities::RegistrationOptions(registration_options) => {
|
||||
let diagnostic_options = registration_options.diagnostic_options;
|
||||
if !diagnostic_options.workspace_diagnostics {
|
||||
return None;
|
||||
}
|
||||
diagnostic_options.identifier
|
||||
}
|
||||
};
|
||||
let identifier = diagnostic_identifier(&options)?;
|
||||
|
||||
let (progress_tx, mut progress_rx) = mpsc::channel(1);
|
||||
let (mut refresh_tx, mut refresh_rx) = mpsc::channel(1);
|
||||
@@ -11894,7 +12005,14 @@ fn lsp_workspace_diagnostics_refresh(
|
||||
return;
|
||||
};
|
||||
|
||||
let token = format!("workspace/diagnostic-{}-{}", server.server_id(), requests);
|
||||
let token = if let Some(identifier) = ®istration_id {
|
||||
format!(
|
||||
"workspace/diagnostic/{}/{requests}/id:{identifier}",
|
||||
server.server_id(),
|
||||
)
|
||||
} else {
|
||||
format!("workspace/diagnostic/{}/{requests}", server.server_id())
|
||||
};
|
||||
|
||||
progress_rx.try_recv().ok();
|
||||
let timer =
|
||||
@@ -11960,6 +12078,24 @@ fn lsp_workspace_diagnostics_refresh(
|
||||
})
|
||||
}
|
||||
|
||||
fn diagnostic_identifier(options: &DiagnosticServerCapabilities) -> Option<Option<String>> {
|
||||
match &options {
|
||||
lsp::DiagnosticServerCapabilities::Options(diagnostic_options) => {
|
||||
if !diagnostic_options.workspace_diagnostics {
|
||||
return None;
|
||||
}
|
||||
Some(diagnostic_options.identifier.clone())
|
||||
}
|
||||
lsp::DiagnosticServerCapabilities::RegistrationOptions(registration_options) => {
|
||||
let diagnostic_options = ®istration_options.diagnostic_options;
|
||||
if !diagnostic_options.workspace_diagnostics {
|
||||
return None;
|
||||
}
|
||||
Some(diagnostic_options.identifier.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_word_completion(snapshot: &BufferSnapshot, completion: &mut Completion) {
|
||||
let CompletionSource::BufferWord {
|
||||
word_range,
|
||||
@@ -12364,7 +12500,7 @@ pub enum LanguageServerState {
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
server: Arc<LanguageServer>,
|
||||
simulate_disk_based_diagnostics_completion: Option<Task<()>>,
|
||||
workspace_refresh_task: Option<WorkspaceRefreshTask>,
|
||||
workspace_diagnostics_refresh_tasks: HashMap<Option<String>, WorkspaceRefreshTask>,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -139,146 +139,141 @@ impl Project {
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let builder = project
|
||||
.update(cx, move |_, cx| {
|
||||
let format_to_run = || {
|
||||
if let Some(command) = &spawn_task.command {
|
||||
let mut command: Option<Cow<str>> = shell_kind.try_quote(command);
|
||||
if let Some(command) = &mut command
|
||||
&& command.starts_with('"')
|
||||
&& let Some(prefix) = shell_kind.command_prefix()
|
||||
{
|
||||
*command = Cow::Owned(format!("{prefix}{command}"));
|
||||
}
|
||||
|
||||
let args = spawn_task
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| shell_kind.try_quote(&arg));
|
||||
|
||||
command.into_iter().chain(args).join(" ")
|
||||
} else {
|
||||
// todo: this breaks for remotes to windows
|
||||
format!("exec {shell} -l")
|
||||
project.update(cx, move |this, cx| {
|
||||
let format_to_run = || {
|
||||
if let Some(command) = &spawn_task.command {
|
||||
let mut command: Option<Cow<str>> = shell_kind.try_quote(command);
|
||||
if let Some(command) = &mut command
|
||||
&& command.starts_with('"')
|
||||
&& let Some(prefix) = shell_kind.command_prefix()
|
||||
{
|
||||
*command = Cow::Owned(format!("{prefix}{command}"));
|
||||
}
|
||||
};
|
||||
|
||||
let (shell, env) = {
|
||||
env.extend(spawn_task.env);
|
||||
match remote_client {
|
||||
Some(remote_client) => match activation_script.clone() {
|
||||
activation_script if !activation_script.is_empty() => {
|
||||
let activation_script = activation_script.join("; ");
|
||||
let to_run = format_to_run();
|
||||
let args = vec![
|
||||
"-c".to_owned(),
|
||||
format!("{activation_script}; {to_run}"),
|
||||
];
|
||||
create_remote_shell(
|
||||
Some((
|
||||
&remote_client
|
||||
.read(cx)
|
||||
.shell()
|
||||
.unwrap_or_else(get_default_system_shell),
|
||||
&args,
|
||||
)),
|
||||
env,
|
||||
path,
|
||||
remote_client,
|
||||
cx,
|
||||
)?
|
||||
}
|
||||
_ => create_remote_shell(
|
||||
spawn_task
|
||||
.command
|
||||
.as_ref()
|
||||
.map(|command| (command, &spawn_task.args)),
|
||||
let args = spawn_task
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| shell_kind.try_quote(&arg));
|
||||
|
||||
command.into_iter().chain(args).join(" ")
|
||||
} else {
|
||||
// todo: this breaks for remotes to windows
|
||||
format!("exec {shell} -l")
|
||||
}
|
||||
};
|
||||
|
||||
let (shell, env) = {
|
||||
env.extend(spawn_task.env);
|
||||
match remote_client {
|
||||
Some(remote_client) => match activation_script.clone() {
|
||||
activation_script if !activation_script.is_empty() => {
|
||||
let activation_script = activation_script.join("; ");
|
||||
let to_run = format_to_run();
|
||||
let args =
|
||||
vec!["-c".to_owned(), format!("{activation_script}; {to_run}")];
|
||||
create_remote_shell(
|
||||
Some((
|
||||
&remote_client
|
||||
.read(cx)
|
||||
.shell()
|
||||
.unwrap_or_else(get_default_system_shell),
|
||||
&args,
|
||||
)),
|
||||
env,
|
||||
path,
|
||||
remote_client,
|
||||
cx,
|
||||
)?,
|
||||
},
|
||||
None => match activation_script.clone() {
|
||||
activation_script if !activation_script.is_empty() => {
|
||||
let separator = shell_kind.sequential_commands_separator();
|
||||
let activation_script =
|
||||
activation_script.join(&format!("{separator} "));
|
||||
let to_run = format_to_run();
|
||||
)?
|
||||
}
|
||||
_ => create_remote_shell(
|
||||
spawn_task
|
||||
.command
|
||||
.as_ref()
|
||||
.map(|command| (command, &spawn_task.args)),
|
||||
env,
|
||||
path,
|
||||
remote_client,
|
||||
cx,
|
||||
)?,
|
||||
},
|
||||
None => match activation_script.clone() {
|
||||
activation_script if !activation_script.is_empty() => {
|
||||
let separator = shell_kind.sequential_commands_separator();
|
||||
let activation_script =
|
||||
activation_script.join(&format!("{separator} "));
|
||||
let to_run = format_to_run();
|
||||
|
||||
let mut arg =
|
||||
format!("{activation_script}{separator} {to_run}");
|
||||
if shell_kind == ShellKind::Cmd {
|
||||
// We need to put the entire command in quotes since otherwise CMD tries to execute them
|
||||
// as separate commands rather than chaining one after another.
|
||||
arg = format!("\"{arg}\"");
|
||||
}
|
||||
|
||||
let args = shell_kind.args_for_shell(false, arg);
|
||||
|
||||
(
|
||||
Shell::WithArguments {
|
||||
program: shell,
|
||||
args,
|
||||
title_override: None,
|
||||
},
|
||||
env,
|
||||
)
|
||||
let mut arg = format!("{activation_script}{separator} {to_run}");
|
||||
if shell_kind == ShellKind::Cmd {
|
||||
// We need to put the entire command in quotes since otherwise CMD tries to execute them
|
||||
// as separate commands rather than chaining one after another.
|
||||
arg = format!("\"{arg}\"");
|
||||
}
|
||||
_ => (
|
||||
if let Some(program) = spawn_task.command {
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args: spawn_task.args,
|
||||
title_override: None,
|
||||
}
|
||||
} else {
|
||||
Shell::System
|
||||
|
||||
let args = shell_kind.args_for_shell(false, arg);
|
||||
|
||||
(
|
||||
Shell::WithArguments {
|
||||
program: shell,
|
||||
args,
|
||||
title_override: None,
|
||||
},
|
||||
env,
|
||||
),
|
||||
},
|
||||
}
|
||||
};
|
||||
anyhow::Ok(TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
task_state,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
activation_script,
|
||||
))
|
||||
})??
|
||||
.await?;
|
||||
project.update(cx, move |this, cx| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
)
|
||||
}
|
||||
_ => (
|
||||
if let Some(program) = spawn_task.command {
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args: spawn_task.args,
|
||||
title_override: None,
|
||||
}
|
||||
} else {
|
||||
Shell::System
|
||||
},
|
||||
env,
|
||||
),
|
||||
},
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
};
|
||||
TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
task_state,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
activation_script,
|
||||
)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
})?
|
||||
})
|
||||
}
|
||||
|
||||
@@ -359,55 +354,53 @@ impl Project {
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let builder = project
|
||||
.update(cx, move |_, cx| {
|
||||
let (shell, env) = {
|
||||
match remote_client {
|
||||
Some(remote_client) => {
|
||||
create_remote_shell(None, env, path, remote_client, cx)?
|
||||
}
|
||||
None => (settings.shell, env),
|
||||
}
|
||||
};
|
||||
anyhow::Ok(TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
None,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
None,
|
||||
cx,
|
||||
activation_script,
|
||||
))
|
||||
})??
|
||||
.await?;
|
||||
project.update(cx, move |this, cx| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
let (shell, env) = {
|
||||
match remote_client {
|
||||
Some(remote_client) => {
|
||||
create_remote_shell(None, env, path, remote_client, cx)?
|
||||
}
|
||||
None => (settings.shell, env),
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
};
|
||||
TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
None,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape,
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
None,
|
||||
cx,
|
||||
activation_script,
|
||||
)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
this.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
let id = terminal_handle.entity_id();
|
||||
cx.observe_release(&terminal_handle, move |project, _terminal, cx| {
|
||||
let handles = &mut project.terminals.local_handles;
|
||||
|
||||
if let Some(index) = handles
|
||||
.iter()
|
||||
.position(|terminal| terminal.entity_id() == id)
|
||||
{
|
||||
handles.remove(index);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
})?
|
||||
})
|
||||
}
|
||||
|
||||
@@ -416,27 +409,20 @@ impl Project {
|
||||
terminal: &Entity<Terminal>,
|
||||
cx: &mut Context<'_, Project>,
|
||||
cwd: Option<PathBuf>,
|
||||
) -> Task<Result<Entity<Terminal>>> {
|
||||
// We cannot clone the task's terminal, as it will effectively re-spawn the task, which might not be desirable.
|
||||
// For now, create a new shell instead.
|
||||
if terminal.read(cx).task().is_some() {
|
||||
return self.create_terminal_shell(cwd, cx);
|
||||
}
|
||||
|
||||
) -> Result<Entity<Terminal>> {
|
||||
let local_path = if self.is_via_remote_server() {
|
||||
None
|
||||
} else {
|
||||
cwd
|
||||
};
|
||||
|
||||
let builder = terminal.read(cx).clone_builder(cx, local_path);
|
||||
cx.spawn(async |project, cx| {
|
||||
let terminal = builder.await?;
|
||||
project.update(cx, |project, cx| {
|
||||
let terminal_handle = cx.new(|cx| terminal.subscribe(cx));
|
||||
terminal
|
||||
.read(cx)
|
||||
.clone_builder(cx, local_path)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
project
|
||||
.terminals
|
||||
self.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
@@ -456,7 +442,6 @@ impl Project {
|
||||
|
||||
terminal_handle
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn terminal_settings<'a>(
|
||||
|
||||
@@ -709,13 +709,11 @@ impl Item for NotebookEditor {
|
||||
_workspace_id: Option<workspace::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Self::new(self.project.clone(), self.notebook_item.clone(), window, cx)
|
||||
})))
|
||||
Some(cx.new(|cx| Self::new(self.project.clone(), self.notebook_item.clone(), window, cx)))
|
||||
}
|
||||
|
||||
fn buffer_kind(&self, _: &App) -> workspace::item::ItemBufferKind {
|
||||
|
||||
@@ -15,7 +15,6 @@ path = "src/rope.rs"
|
||||
arrayvec = "0.7.1"
|
||||
log.workspace = true
|
||||
rayon.workspace = true
|
||||
smallvec.workspace = true
|
||||
sum_tree.workspace = true
|
||||
unicode-segmentation.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -93,6 +93,71 @@ impl Chunk {
|
||||
pub fn tabs(&self) -> Bitmap {
|
||||
self.tabs
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_char_boundary(&self, offset: usize) -> bool {
|
||||
(1 as Bitmap).unbounded_shl(offset as u32) & self.chars != 0 || offset == self.text.len()
|
||||
}
|
||||
|
||||
pub fn floor_char_boundary(&self, index: usize) -> usize {
|
||||
#[inline]
|
||||
pub(crate) const fn is_utf8_char_boundary(u8: u8) -> bool {
|
||||
// This is bit magic equivalent to: b < 128 || b >= 192
|
||||
(u8 as i8) >= -0x40
|
||||
}
|
||||
|
||||
if index >= self.text.len() {
|
||||
self.text.len()
|
||||
} else {
|
||||
let mut i = index;
|
||||
while i > 0 {
|
||||
if is_utf8_char_boundary(self.text.as_bytes()[i]) {
|
||||
break;
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn assert_char_boundary(&self, offset: usize) {
|
||||
if self.is_char_boundary(offset) {
|
||||
return;
|
||||
}
|
||||
panic_char_boundary(self, offset);
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
fn panic_char_boundary(chunk: &Chunk, offset: usize) {
|
||||
if offset > chunk.text.len() {
|
||||
panic!(
|
||||
"byte index {} is out of bounds of `{:?}` (length: {})",
|
||||
offset,
|
||||
chunk.text,
|
||||
chunk.text.len()
|
||||
);
|
||||
}
|
||||
// find the character
|
||||
let char_start = chunk.floor_char_boundary(offset);
|
||||
// `char_start` must be less than len and a char boundary
|
||||
let ch = chunk
|
||||
.text
|
||||
.get(char_start..)
|
||||
.unwrap()
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap();
|
||||
let char_range = char_start..char_start + ch.len_utf8();
|
||||
panic!(
|
||||
"byte index {} is not a char boundary; it is inside {:?} (bytes {:?})",
|
||||
offset, ch, char_range,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -4,8 +4,8 @@ mod point;
|
||||
mod point_utf16;
|
||||
mod unclipped;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator as _};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cmp, fmt, io, mem,
|
||||
ops::{self, AddAssign, Range},
|
||||
@@ -192,10 +192,19 @@ impl Rope {
|
||||
(),
|
||||
);
|
||||
|
||||
if text.len() > 2048 {
|
||||
#[cfg(not(test))]
|
||||
const NUM_CHUNKS: usize = 16;
|
||||
#[cfg(test)]
|
||||
const NUM_CHUNKS: usize = 4;
|
||||
|
||||
// We accommodate for NUM_CHUNKS chunks of size MAX_BASE
|
||||
// but given the chunk boundary can land within a character
|
||||
// we need to accommodate for the worst case where every chunk gets cut short by up to 4 bytes
|
||||
if text.len() > NUM_CHUNKS * chunk::MAX_BASE - NUM_CHUNKS * 4 {
|
||||
return self.push_large(text);
|
||||
}
|
||||
let mut new_chunks = SmallVec::<[_; 16]>::new();
|
||||
// 16 is enough as otherwise we will hit the branch above
|
||||
let mut new_chunks = ArrayVec::<_, NUM_CHUNKS>::new();
|
||||
|
||||
while !text.is_empty() {
|
||||
let mut split_ix = cmp::min(chunk::MAX_BASE, text.len());
|
||||
@@ -206,19 +215,8 @@ impl Rope {
|
||||
new_chunks.push(chunk);
|
||||
text = remainder;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
const PARALLEL_THRESHOLD: usize = 4;
|
||||
#[cfg(not(test))]
|
||||
const PARALLEL_THRESHOLD: usize = 4 * (2 * sum_tree::TREE_BASE);
|
||||
|
||||
if new_chunks.len() >= PARALLEL_THRESHOLD {
|
||||
self.chunks
|
||||
.par_extend(new_chunks.into_vec().into_par_iter().map(Chunk::new), ());
|
||||
} else {
|
||||
self.chunks
|
||||
.extend(new_chunks.into_iter().map(Chunk::new), ());
|
||||
}
|
||||
self.chunks
|
||||
.extend(new_chunks.into_iter().map(Chunk::new), ());
|
||||
|
||||
self.check_invariants();
|
||||
}
|
||||
|
||||
@@ -572,14 +572,12 @@ impl Item for ProjectSearchView {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let model = self.entity.update(cx, |model, cx| model.clone(cx));
|
||||
Task::ready(Some(cx.new(|cx| {
|
||||
Self::new(self.workspace.clone(), model, window, cx, None)
|
||||
})))
|
||||
Some(cx.new(|cx| Self::new(self.workspace.clone(), model, window, cx, None)))
|
||||
}
|
||||
|
||||
fn added_to_workspace(
|
||||
@@ -3696,7 +3694,6 @@ pub mod tests {
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 1);
|
||||
|
||||
@@ -3892,7 +3889,6 @@ pub mod tests {
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 1);
|
||||
assert!(
|
||||
|
||||
@@ -727,15 +727,11 @@ pub fn to_pretty_json(
|
||||
indent_size: usize,
|
||||
indent_prefix_len: usize,
|
||||
) -> String {
|
||||
const SPACES: [u8; 32] = [b' '; 32];
|
||||
|
||||
debug_assert!(indent_size <= SPACES.len());
|
||||
debug_assert!(indent_prefix_len <= SPACES.len());
|
||||
|
||||
let mut output = Vec::new();
|
||||
let indent = " ".repeat(indent_size);
|
||||
let mut ser = serde_json::Serializer::with_formatter(
|
||||
&mut output,
|
||||
serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]),
|
||||
serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes()),
|
||||
);
|
||||
|
||||
value.serialize(&mut ser).unwrap();
|
||||
@@ -744,7 +740,7 @@ pub fn to_pretty_json(
|
||||
let mut adjusted_text = String::new();
|
||||
for (i, line) in text.split('\n').enumerate() {
|
||||
if i > 0 {
|
||||
adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap());
|
||||
adjusted_text.extend(std::iter::repeat(' ').take(indent_prefix_len));
|
||||
}
|
||||
adjusted_text.push_str(line);
|
||||
adjusted_text.push('\n');
|
||||
|
||||
@@ -3,7 +3,7 @@ mod tree_map;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
pub use cursor::{Cursor, FilterCursor, Iter};
|
||||
use rayon::prelude::*;
|
||||
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator as _};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc};
|
||||
|
||||
@@ -423,233 +423,232 @@ impl TerminalBuilder {
|
||||
completion_tx: Option<Sender<Option<ExitStatus>>>,
|
||||
cx: &App,
|
||||
activation_script: Vec<String>,
|
||||
) -> Task<Result<TerminalBuilder>> {
|
||||
let version = release_channel::AppVersion::global(cx);
|
||||
cx.background_spawn(async move {
|
||||
// If the parent environment doesn't have a locale set
|
||||
// (As is the case when launched from a .app on MacOS),
|
||||
// and the Project doesn't have a locale set, then
|
||||
// set a fallback for our child environment to use.
|
||||
if std::env::var("LANG").is_err() {
|
||||
env.entry("LANG".to_string())
|
||||
.or_insert_with(|| "en_US.UTF-8".to_string());
|
||||
}
|
||||
) -> Result<TerminalBuilder> {
|
||||
// If the parent environment doesn't have a locale set
|
||||
// (As is the case when launched from a .app on MacOS),
|
||||
// and the Project doesn't have a locale set, then
|
||||
// set a fallback for our child environment to use.
|
||||
if std::env::var("LANG").is_err() {
|
||||
env.entry("LANG".to_string())
|
||||
.or_insert_with(|| "en_US.UTF-8".to_string());
|
||||
}
|
||||
|
||||
env.insert("ZED_TERM".to_string(), "true".to_string());
|
||||
env.insert("TERM_PROGRAM".to_string(), "zed".to_string());
|
||||
env.insert("TERM".to_string(), "xterm-256color".to_string());
|
||||
env.insert("COLORTERM".to_string(), "truecolor".to_string());
|
||||
env.insert("TERM_PROGRAM_VERSION".to_string(), version.to_string());
|
||||
env.insert("ZED_TERM".to_string(), "true".to_string());
|
||||
env.insert("TERM_PROGRAM".to_string(), "zed".to_string());
|
||||
env.insert("TERM".to_string(), "xterm-256color".to_string());
|
||||
env.insert("COLORTERM".to_string(), "truecolor".to_string());
|
||||
env.insert(
|
||||
"TERM_PROGRAM_VERSION".to_string(),
|
||||
release_channel::AppVersion::global(cx).to_string(),
|
||||
);
|
||||
|
||||
#[derive(Default)]
|
||||
struct ShellParams {
|
||||
#[derive(Default)]
|
||||
struct ShellParams {
|
||||
program: String,
|
||||
args: Option<Vec<String>>,
|
||||
title_override: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl ShellParams {
|
||||
fn new(
|
||||
program: String,
|
||||
args: Option<Vec<String>>,
|
||||
title_override: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl ShellParams {
|
||||
fn new(
|
||||
program: String,
|
||||
args: Option<Vec<String>>,
|
||||
title_override: Option<SharedString>,
|
||||
) -> Self {
|
||||
log::debug!("Using {program} as shell");
|
||||
Self {
|
||||
program,
|
||||
args,
|
||||
title_override,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let shell_params = match shell.clone() {
|
||||
Shell::System => {
|
||||
if cfg!(windows) {
|
||||
Some(ShellParams::new(
|
||||
util::shell::get_windows_system_shell(),
|
||||
None,
|
||||
None,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Shell::Program(program) => Some(ShellParams::new(program, None, None)),
|
||||
Shell::WithArguments {
|
||||
) -> Self {
|
||||
log::info!("Using {program} as shell");
|
||||
Self {
|
||||
program,
|
||||
args,
|
||||
title_override,
|
||||
} => Some(ShellParams::new(program, Some(args), title_override)),
|
||||
};
|
||||
let terminal_title_override =
|
||||
shell_params.as_ref().and_then(|e| e.title_override.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
let shell_program = shell_params.as_ref().map(|params| {
|
||||
use util::ResultExt;
|
||||
let shell_params = match shell.clone() {
|
||||
Shell::System => {
|
||||
if cfg!(windows) {
|
||||
Some(ShellParams::new(
|
||||
util::shell::get_windows_system_shell(),
|
||||
None,
|
||||
None,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Shell::Program(program) => Some(ShellParams::new(program, None, None)),
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override,
|
||||
} => Some(ShellParams::new(program, Some(args), title_override)),
|
||||
};
|
||||
let terminal_title_override = shell_params.as_ref().and_then(|e| e.title_override.clone());
|
||||
|
||||
Self::resolve_path(¶ms.program)
|
||||
.log_err()
|
||||
.unwrap_or(params.program.clone())
|
||||
#[cfg(windows)]
|
||||
let shell_program = shell_params.as_ref().map(|params| {
|
||||
use util::ResultExt;
|
||||
|
||||
Self::resolve_path(¶ms.program)
|
||||
.log_err()
|
||||
.unwrap_or(params.program.clone())
|
||||
});
|
||||
|
||||
// Note: when remoting, this shell_kind will scrutinize `ssh` or
|
||||
// `wsl.exe` as a shell and fall back to posix or powershell based on
|
||||
// the compilation target. This is fine right now due to the restricted
|
||||
// way we use the return value, but would become incorrect if we
|
||||
// supported remoting into windows.
|
||||
let shell_kind = shell.shell_kind(cfg!(windows));
|
||||
|
||||
let pty_options = {
|
||||
let alac_shell = shell_params.as_ref().map(|params| {
|
||||
alacritty_terminal::tty::Shell::new(
|
||||
params.program.clone(),
|
||||
params.args.clone().unwrap_or_default(),
|
||||
)
|
||||
});
|
||||
|
||||
// Note: when remoting, this shell_kind will scrutinize `ssh` or
|
||||
// `wsl.exe` as a shell and fall back to posix or powershell based on
|
||||
// the compilation target. This is fine right now due to the restricted
|
||||
// way we use the return value, but would become incorrect if we
|
||||
// supported remoting into windows.
|
||||
let shell_kind = shell.shell_kind(cfg!(windows));
|
||||
|
||||
let pty_options = {
|
||||
let alac_shell = shell_params.as_ref().map(|params| {
|
||||
alacritty_terminal::tty::Shell::new(
|
||||
params.program.clone(),
|
||||
params.args.clone().unwrap_or_default(),
|
||||
)
|
||||
});
|
||||
|
||||
alacritty_terminal::tty::Options {
|
||||
shell: alac_shell,
|
||||
working_directory: working_directory.clone(),
|
||||
drain_on_exit: true,
|
||||
env: env.clone().into_iter().collect(),
|
||||
// We do not want to escape arguments if we are using CMD as our shell.
|
||||
// If we do we end up with too many quotes/escaped quotes for CMD to handle.
|
||||
#[cfg(windows)]
|
||||
escape_args: shell_kind != util::shell::ShellKind::Cmd,
|
||||
}
|
||||
};
|
||||
|
||||
let default_cursor_style = AlacCursorStyle::from(cursor_shape);
|
||||
let scrolling_history = if task.is_some() {
|
||||
// Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling.
|
||||
// After the task finishes, we do not allow appending to that terminal, so small tasks output should not
|
||||
// cause excessive memory usage over time.
|
||||
MAX_SCROLL_HISTORY_LINES
|
||||
} else {
|
||||
max_scroll_history_lines
|
||||
.unwrap_or(DEFAULT_SCROLL_HISTORY_LINES)
|
||||
.min(MAX_SCROLL_HISTORY_LINES)
|
||||
};
|
||||
let config = Config {
|
||||
scrolling_history,
|
||||
default_cursor_style,
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
//Spawn a task so the Alacritty EventLoop can communicate with us
|
||||
//TODO: Remove with a bounded sender which can be dispatched on &self
|
||||
let (events_tx, events_rx) = unbounded();
|
||||
//Set up the terminal...
|
||||
let mut term = Term::new(
|
||||
config.clone(),
|
||||
&TerminalBounds::default(),
|
||||
ZedListener(events_tx.clone()),
|
||||
);
|
||||
|
||||
//Alacritty defaults to alternate scrolling being on, so we just need to turn it off.
|
||||
if let AlternateScroll::Off = alternate_scroll {
|
||||
term.unset_private_mode(PrivateMode::Named(NamedPrivateMode::AlternateScroll));
|
||||
}
|
||||
|
||||
let term = Arc::new(FairMutex::new(term));
|
||||
|
||||
//Setup the pty...
|
||||
let pty = match tty::new(&pty_options, TerminalBounds::default().into(), window_id) {
|
||||
Ok(pty) => pty,
|
||||
Err(error) => {
|
||||
bail!(TerminalError {
|
||||
directory: working_directory,
|
||||
program: shell_params.as_ref().map(|params| params.program.clone()),
|
||||
args: shell_params.as_ref().and_then(|params| params.args.clone()),
|
||||
title_override: terminal_title_override,
|
||||
source: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let pty_info = PtyProcessInfo::new(&pty);
|
||||
|
||||
//And connect them together
|
||||
let event_loop = EventLoop::new(
|
||||
term.clone(),
|
||||
ZedListener(events_tx),
|
||||
pty,
|
||||
pty_options.drain_on_exit,
|
||||
false,
|
||||
)
|
||||
.context("failed to create event loop")?;
|
||||
|
||||
//Kick things off
|
||||
let pty_tx = event_loop.channel();
|
||||
let _io_thread = event_loop.spawn(); // DANGER
|
||||
|
||||
let no_task = task.is_none();
|
||||
|
||||
let terminal = Terminal {
|
||||
task,
|
||||
terminal_type: TerminalType::Pty {
|
||||
pty_tx: Notifier(pty_tx),
|
||||
info: pty_info,
|
||||
},
|
||||
completion_tx,
|
||||
term,
|
||||
term_config: config,
|
||||
title_override: terminal_title_override,
|
||||
events: VecDeque::with_capacity(10), //Should never get this high.
|
||||
last_content: Default::default(),
|
||||
last_mouse: None,
|
||||
matches: Vec::new(),
|
||||
selection_head: None,
|
||||
breadcrumb_text: String::new(),
|
||||
scroll_px: px(0.),
|
||||
next_link_id: 0,
|
||||
selection_phase: SelectionPhase::Ended,
|
||||
hyperlink_regex_searches: RegexSearches::new(),
|
||||
vi_mode_enabled: false,
|
||||
is_ssh_terminal,
|
||||
last_mouse_move_time: Instant::now(),
|
||||
last_hyperlink_search_position: None,
|
||||
alacritty_terminal::tty::Options {
|
||||
shell: alac_shell,
|
||||
working_directory: working_directory.clone(),
|
||||
drain_on_exit: true,
|
||||
env: env.clone().into_iter().collect(),
|
||||
// We do not want to escape arguments if we are using CMD as our shell.
|
||||
// If we do we end up with too many quotes/escaped quotes for CMD to handle.
|
||||
#[cfg(windows)]
|
||||
shell_program,
|
||||
activation_script: activation_script.clone(),
|
||||
template: CopyTemplate {
|
||||
shell,
|
||||
env,
|
||||
cursor_shape,
|
||||
alternate_scroll,
|
||||
max_scroll_history_lines,
|
||||
window_id,
|
||||
},
|
||||
child_exited: None,
|
||||
};
|
||||
escape_args: shell_kind != util::shell::ShellKind::Cmd,
|
||||
}
|
||||
};
|
||||
|
||||
if !activation_script.is_empty() && no_task {
|
||||
for activation_script in activation_script {
|
||||
terminal.write_to_pty(activation_script.into_bytes());
|
||||
// Simulate enter key press
|
||||
// NOTE(PowerShell): using `\r\n` will put PowerShell in a continuation mode (infamous >> character)
|
||||
// and generally mess up the rendering.
|
||||
terminal.write_to_pty(b"\x0d");
|
||||
}
|
||||
// In order to clear the screen at this point, we have two options:
|
||||
// 1. We can send a shell-specific command such as "clear" or "cls"
|
||||
// 2. We can "echo" a marker message that we will then catch when handling a Wakeup event
|
||||
// and clear the screen using `terminal.clear()` method
|
||||
// We cannot issue a `terminal.clear()` command at this point as alacritty is evented
|
||||
// and while we have sent the activation script to the pty, it will be executed asynchronously.
|
||||
// Therefore, we somehow need to wait for the activation script to finish executing before we
|
||||
// can proceed with clearing the screen.
|
||||
terminal.write_to_pty(shell_kind.clear_screen_command().as_bytes());
|
||||
let default_cursor_style = AlacCursorStyle::from(cursor_shape);
|
||||
let scrolling_history = if task.is_some() {
|
||||
// Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling.
|
||||
// After the task finishes, we do not allow appending to that terminal, so small tasks output should not
|
||||
// cause excessive memory usage over time.
|
||||
MAX_SCROLL_HISTORY_LINES
|
||||
} else {
|
||||
max_scroll_history_lines
|
||||
.unwrap_or(DEFAULT_SCROLL_HISTORY_LINES)
|
||||
.min(MAX_SCROLL_HISTORY_LINES)
|
||||
};
|
||||
let config = Config {
|
||||
scrolling_history,
|
||||
default_cursor_style,
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
//Spawn a task so the Alacritty EventLoop can communicate with us
|
||||
//TODO: Remove with a bounded sender which can be dispatched on &self
|
||||
let (events_tx, events_rx) = unbounded();
|
||||
//Set up the terminal...
|
||||
let mut term = Term::new(
|
||||
config.clone(),
|
||||
&TerminalBounds::default(),
|
||||
ZedListener(events_tx.clone()),
|
||||
);
|
||||
|
||||
//Alacritty defaults to alternate scrolling being on, so we just need to turn it off.
|
||||
if let AlternateScroll::Off = alternate_scroll {
|
||||
term.unset_private_mode(PrivateMode::Named(NamedPrivateMode::AlternateScroll));
|
||||
}
|
||||
|
||||
let term = Arc::new(FairMutex::new(term));
|
||||
|
||||
//Setup the pty...
|
||||
let pty = match tty::new(&pty_options, TerminalBounds::default().into(), window_id) {
|
||||
Ok(pty) => pty,
|
||||
Err(error) => {
|
||||
bail!(TerminalError {
|
||||
directory: working_directory,
|
||||
program: shell_params.as_ref().map(|params| params.program.clone()),
|
||||
args: shell_params.as_ref().and_then(|params| params.args.clone()),
|
||||
title_override: terminal_title_override,
|
||||
source: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let pty_info = PtyProcessInfo::new(&pty);
|
||||
|
||||
//And connect them together
|
||||
let event_loop = EventLoop::new(
|
||||
term.clone(),
|
||||
ZedListener(events_tx),
|
||||
pty,
|
||||
pty_options.drain_on_exit,
|
||||
false,
|
||||
)
|
||||
.context("failed to create event loop")?;
|
||||
|
||||
//Kick things off
|
||||
let pty_tx = event_loop.channel();
|
||||
let _io_thread = event_loop.spawn(); // DANGER
|
||||
|
||||
let no_task = task.is_none();
|
||||
|
||||
let terminal = Terminal {
|
||||
task,
|
||||
terminal_type: TerminalType::Pty {
|
||||
pty_tx: Notifier(pty_tx),
|
||||
info: pty_info,
|
||||
},
|
||||
completion_tx,
|
||||
term,
|
||||
term_config: config,
|
||||
title_override: terminal_title_override,
|
||||
events: VecDeque::with_capacity(10), //Should never get this high.
|
||||
last_content: Default::default(),
|
||||
last_mouse: None,
|
||||
matches: Vec::new(),
|
||||
selection_head: None,
|
||||
breadcrumb_text: String::new(),
|
||||
scroll_px: px(0.),
|
||||
next_link_id: 0,
|
||||
selection_phase: SelectionPhase::Ended,
|
||||
hyperlink_regex_searches: RegexSearches::new(),
|
||||
vi_mode_enabled: false,
|
||||
is_ssh_terminal,
|
||||
last_mouse_move_time: Instant::now(),
|
||||
last_hyperlink_search_position: None,
|
||||
#[cfg(windows)]
|
||||
shell_program,
|
||||
activation_script: activation_script.clone(),
|
||||
template: CopyTemplate {
|
||||
shell,
|
||||
env,
|
||||
cursor_shape,
|
||||
alternate_scroll,
|
||||
max_scroll_history_lines,
|
||||
window_id,
|
||||
},
|
||||
child_exited: None,
|
||||
};
|
||||
|
||||
if !activation_script.is_empty() && no_task {
|
||||
for activation_script in activation_script {
|
||||
terminal.write_to_pty(activation_script.into_bytes());
|
||||
// Simulate enter key press
|
||||
// NOTE(PowerShell): using `\r\n` will put PowerShell in a continuation mode (infamous >> character)
|
||||
// and generally mess up the rendering.
|
||||
terminal.write_to_pty(b"\x0d");
|
||||
}
|
||||
// In order to clear the screen at this point, we have two options:
|
||||
// 1. We can send a shell-specific command such as "clear" or "cls"
|
||||
// 2. We can "echo" a marker message that we will then catch when handling a Wakeup event
|
||||
// and clear the screen using `terminal.clear()` method
|
||||
// We cannot issue a `terminal.clear()` command at this point as alacritty is evented
|
||||
// and while we have sent the activation script to the pty, it will be executed asynchronously.
|
||||
// Therefore, we somehow need to wait for the activation script to finish executing before we
|
||||
// can proceed with clearing the screen.
|
||||
terminal.write_to_pty(shell_kind.clear_screen_command().as_bytes());
|
||||
// Simulate enter key press
|
||||
terminal.write_to_pty(b"\x0d");
|
||||
}
|
||||
|
||||
Ok(TerminalBuilder {
|
||||
terminal,
|
||||
events_rx,
|
||||
})
|
||||
Ok(TerminalBuilder {
|
||||
terminal,
|
||||
events_rx,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2154,7 +2153,7 @@ impl Terminal {
|
||||
self.vi_mode_enabled
|
||||
}
|
||||
|
||||
pub fn clone_builder(&self, cx: &App, cwd: Option<PathBuf>) -> Task<Result<TerminalBuilder>> {
|
||||
pub fn clone_builder(&self, cx: &App, cwd: Option<PathBuf>) -> Result<TerminalBuilder> {
|
||||
let working_directory = self.working_directory().or_else(|| cwd);
|
||||
TerminalBuilder::new(
|
||||
working_directory,
|
||||
@@ -2390,30 +2389,28 @@ mod tests {
|
||||
let (completion_tx, completion_rx) = smol::channel::unbounded();
|
||||
let (program, args) = ShellBuilder::new(&Shell::System, false)
|
||||
.build(Some("echo".to_owned()), &["hello".to_owned()]);
|
||||
let builder = cx
|
||||
.update(|cx| {
|
||||
TerminalBuilder::new(
|
||||
None,
|
||||
None,
|
||||
task::Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: None,
|
||||
},
|
||||
HashMap::default(),
|
||||
CursorShape::default(),
|
||||
AlternateScroll::On,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
vec![],
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let terminal = cx.new(|cx| builder.subscribe(cx));
|
||||
let terminal = cx.new(|cx| {
|
||||
TerminalBuilder::new(
|
||||
None,
|
||||
None,
|
||||
task::Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: None,
|
||||
},
|
||||
HashMap::default(),
|
||||
CursorShape::default(),
|
||||
AlternateScroll::On,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
vec![],
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
});
|
||||
assert_eq!(
|
||||
completion_rx.recv().await.unwrap(),
|
||||
Some(ExitStatus::default())
|
||||
@@ -2442,27 +2439,25 @@ mod tests {
|
||||
cx.executor().allow_parking();
|
||||
|
||||
let (completion_tx, completion_rx) = smol::channel::unbounded();
|
||||
let builder = cx
|
||||
.update(|cx| {
|
||||
TerminalBuilder::new(
|
||||
None,
|
||||
None,
|
||||
task::Shell::System,
|
||||
HashMap::default(),
|
||||
CursorShape::default(),
|
||||
AlternateScroll::On,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
Vec::new(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
// Build an empty command, which will result in a tty shell spawned.
|
||||
let terminal = cx.new(|cx| builder.subscribe(cx));
|
||||
let terminal = cx.new(|cx| {
|
||||
TerminalBuilder::new(
|
||||
None,
|
||||
None,
|
||||
task::Shell::System,
|
||||
HashMap::default(),
|
||||
CursorShape::default(),
|
||||
AlternateScroll::On,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
Vec::new(),
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
});
|
||||
|
||||
let (event_tx, event_rx) = smol::channel::unbounded::<Event>();
|
||||
cx.update(|cx| {
|
||||
@@ -2513,30 +2508,28 @@ mod tests {
|
||||
let (completion_tx, completion_rx) = smol::channel::unbounded();
|
||||
let (program, args) = ShellBuilder::new(&Shell::System, false)
|
||||
.build(Some("asdasdasdasd".to_owned()), &["@@@@@".to_owned()]);
|
||||
let builder = cx
|
||||
.update(|cx| {
|
||||
TerminalBuilder::new(
|
||||
None,
|
||||
None,
|
||||
task::Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: None,
|
||||
},
|
||||
HashMap::default(),
|
||||
CursorShape::default(),
|
||||
AlternateScroll::On,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
Vec::new(),
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let terminal = cx.new(|cx| builder.subscribe(cx));
|
||||
let terminal = cx.new(|cx| {
|
||||
TerminalBuilder::new(
|
||||
None,
|
||||
None,
|
||||
task::Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: None,
|
||||
},
|
||||
HashMap::default(),
|
||||
CursorShape::default(),
|
||||
AlternateScroll::On,
|
||||
None,
|
||||
false,
|
||||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
Vec::new(),
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
});
|
||||
|
||||
let (event_tx, event_rx) = smol::channel::unbounded::<Event>();
|
||||
cx.update(|cx| {
|
||||
|
||||
@@ -214,6 +214,14 @@ async fn deserialize_pane_group(
|
||||
}
|
||||
SerializedPaneGroup::Pane(serialized_pane) => {
|
||||
let active = serialized_pane.active;
|
||||
let new_items = deserialize_terminal_views(
|
||||
workspace_id,
|
||||
project.clone(),
|
||||
workspace.clone(),
|
||||
serialized_pane.children.as_slice(),
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
let pane = panel
|
||||
.update_in(cx, |terminal_panel, window, cx| {
|
||||
@@ -228,71 +236,56 @@ async fn deserialize_pane_group(
|
||||
.log_err()?;
|
||||
let active_item = serialized_pane.active_item;
|
||||
let pinned_count = serialized_pane.pinned_count;
|
||||
let new_items = deserialize_terminal_views(
|
||||
workspace_id,
|
||||
project.clone(),
|
||||
workspace.clone(),
|
||||
serialized_pane.children.as_slice(),
|
||||
cx,
|
||||
);
|
||||
cx.spawn({
|
||||
let pane = pane.downgrade();
|
||||
async move |cx| {
|
||||
let new_items = new_items.await;
|
||||
|
||||
let items = pane.update_in(cx, |pane, window, cx| {
|
||||
populate_pane_items(pane, new_items, active_item, window, cx);
|
||||
pane.set_pinned_count(pinned_count);
|
||||
pane.items_len()
|
||||
});
|
||||
let terminal = pane
|
||||
.update_in(cx, |pane, window, cx| {
|
||||
populate_pane_items(pane, new_items, active_item, window, cx);
|
||||
pane.set_pinned_count(pinned_count);
|
||||
// Avoid blank panes in splits
|
||||
if items.is_ok_and(|items| items == 0) {
|
||||
if pane.items_len() == 0 {
|
||||
let working_directory = workspace
|
||||
.update(cx, |workspace, cx| default_working_directory(workspace, cx))
|
||||
.ok()
|
||||
.flatten();
|
||||
let Some(terminal) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_shell(working_directory, cx)
|
||||
})
|
||||
.log_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let terminal = terminal.await.log_err();
|
||||
pane.update_in(cx, |pane, window, cx| {
|
||||
if let Some(terminal) = terminal {
|
||||
let terminal_view = Box::new(cx.new(|cx| {
|
||||
TerminalView::new(
|
||||
terminal,
|
||||
workspace.clone(),
|
||||
Some(workspace_id),
|
||||
project.downgrade(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
pane.add_item(terminal_view, true, false, None, window, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
let terminal = project.update(cx, |project, cx| {
|
||||
project.create_terminal_shell(working_directory, cx)
|
||||
});
|
||||
Some(Some(terminal))
|
||||
} else {
|
||||
Some(None)
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.ok()
|
||||
.flatten()?;
|
||||
if let Some(terminal) = terminal {
|
||||
let terminal = terminal.await.ok()?;
|
||||
pane.update_in(cx, |pane, window, cx| {
|
||||
let terminal_view = Box::new(cx.new(|cx| {
|
||||
TerminalView::new(
|
||||
terminal,
|
||||
workspace.clone(),
|
||||
Some(workspace_id),
|
||||
project.downgrade(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
pane.add_item(terminal_view, true, false, None, window, cx);
|
||||
})
|
||||
.ok()?;
|
||||
}
|
||||
Some((Member::Pane(pane.clone()), active.then_some(pane)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_terminal_views(
|
||||
async fn deserialize_terminal_views(
|
||||
workspace_id: WorkspaceId,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
item_ids: &[u64],
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> impl Future<Output = Vec<Entity<TerminalView>>> + use<> {
|
||||
) -> Vec<Entity<TerminalView>> {
|
||||
let mut items = Vec::with_capacity(item_ids.len());
|
||||
let mut deserialized_items = item_ids
|
||||
.iter()
|
||||
.map(|item_id| {
|
||||
@@ -309,15 +302,12 @@ fn deserialize_terminal_views(
|
||||
.unwrap_or_else(|e| Task::ready(Err(e.context("no window present"))))
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
async move {
|
||||
let mut items = Vec::with_capacity(deserialized_items.len());
|
||||
while let Some(item) = deserialized_items.next().await {
|
||||
if let Some(item) = item.log_err() {
|
||||
items.push(item);
|
||||
}
|
||||
while let Some(item) = deserialized_items.next().await {
|
||||
if let Some(item) = item.log_err() {
|
||||
items.push(item);
|
||||
}
|
||||
items
|
||||
}
|
||||
items
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -462,11 +462,11 @@ impl TerminalPanel {
|
||||
cx.spawn_in(window, async move |panel, cx| {
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| match terminal_view {
|
||||
Some(view) => project.clone_terminal(
|
||||
Some(view) => Task::ready(project.clone_terminal(
|
||||
&view.read(cx).terminal.clone(),
|
||||
cx,
|
||||
working_directory,
|
||||
),
|
||||
)),
|
||||
None => project.create_terminal_shell(working_directory, cx),
|
||||
})
|
||||
.ok()?
|
||||
|
||||
@@ -1220,31 +1220,28 @@ impl Item for TerminalView {
|
||||
workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>> {
|
||||
let Ok(terminal) = self.project.update(cx, |project, cx| {
|
||||
let cwd = project
|
||||
.active_project_directory(cx)
|
||||
.map(|it| it.to_path_buf());
|
||||
project.clone_terminal(self.terminal(), cx, cwd)
|
||||
}) else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let terminal = terminal.await.log_err()?;
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
cx.new(|cx| {
|
||||
TerminalView::new(
|
||||
terminal,
|
||||
this.workspace.clone(),
|
||||
workspace_id,
|
||||
this.project.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
) -> Option<Entity<Self>> {
|
||||
let terminal = self
|
||||
.project
|
||||
.update(cx, |project, cx| {
|
||||
let cwd = project
|
||||
.active_project_directory(cx)
|
||||
.map(|it| it.to_path_buf());
|
||||
project.clone_terminal(self.terminal(), cx, cwd)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.ok()?
|
||||
.log_err()?;
|
||||
|
||||
Some(cx.new(|cx| {
|
||||
TerminalView::new(
|
||||
terminal,
|
||||
self.workspace.clone(),
|
||||
workspace_id,
|
||||
self.project.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &gpui::App) -> bool {
|
||||
|
||||
@@ -11,9 +11,8 @@ use anyhow::Result;
|
||||
use client::{Client, proto};
|
||||
use futures::{StreamExt, channel::mpsc};
|
||||
use gpui::{
|
||||
Action, AnyElement, AnyView, App, AppContext, Context, Entity, EntityId, EventEmitter,
|
||||
FocusHandle, Focusable, Font, HighlightStyle, Pixels, Point, Render, SharedString, Task,
|
||||
WeakEntity, Window,
|
||||
Action, AnyElement, AnyView, App, Context, Entity, EntityId, EventEmitter, FocusHandle,
|
||||
Focusable, Font, HighlightStyle, Pixels, Point, Render, SharedString, Task, WeakEntity, Window,
|
||||
};
|
||||
use project::{Project, ProjectEntryId, ProjectPath};
|
||||
pub use settings::{
|
||||
@@ -218,11 +217,11 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
_window: &mut Window,
|
||||
_: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(None)
|
||||
None
|
||||
}
|
||||
fn is_dirty(&self, _: &App) -> bool {
|
||||
false
|
||||
@@ -423,7 +422,7 @@ pub trait ItemHandle: 'static + Send {
|
||||
workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Option<Box<dyn ItemHandle>>>;
|
||||
) -> Option<Box<dyn ItemHandle>>;
|
||||
fn added_to_pane(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
@@ -636,12 +635,9 @@ impl<T: Item> ItemHandle for Entity<T> {
|
||||
workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Option<Box<dyn ItemHandle>>> {
|
||||
let task = self.update(cx, |item, cx| item.clone_on_split(workspace_id, window, cx));
|
||||
cx.background_spawn(async move {
|
||||
task.await
|
||||
.map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
|
||||
})
|
||||
) -> Option<Box<dyn ItemHandle>> {
|
||||
self.update(cx, |item, cx| item.clone_on_split(workspace_id, window, cx))
|
||||
.map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
|
||||
}
|
||||
|
||||
fn added_to_pane(
|
||||
@@ -1508,11 +1504,11 @@ pub mod test {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| Self {
|
||||
Some(cx.new(|cx| Self {
|
||||
state: self.state.clone(),
|
||||
label: self.label.clone(),
|
||||
save_count: self.save_count,
|
||||
@@ -1529,7 +1525,7 @@ pub mod test {
|
||||
workspace_id: self.workspace_id,
|
||||
focus_handle: cx.focus_handle(),
|
||||
serialize: None,
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
fn is_dirty(&self, _: &App) -> bool {
|
||||
|
||||
@@ -3295,18 +3295,11 @@ impl Pane {
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let task = item.clone_on_split(database_id, window, cx);
|
||||
let to_pane = to_pane.downgrade();
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
if let Some(item) = task.await {
|
||||
to_pane
|
||||
.update_in(cx, |pane, window, cx| {
|
||||
pane.add_item(item, true, true, None, window, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
if let Some(item) = item.clone_on_split(database_id, window, cx) {
|
||||
to_pane.update(cx, |pane, cx| {
|
||||
pane.add_item(item, true, true, None, window, cx);
|
||||
})
|
||||
}
|
||||
} else {
|
||||
move_item(&from_pane, &to_pane, item_id, ix, true, window, cx);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use call::{RemoteVideoTrack, RemoteVideoTrackView, Room};
|
||||
use client::{User, proto::PeerId};
|
||||
use gpui::{
|
||||
AppContext as _, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
|
||||
ParentElement, Render, SharedString, Styled, Task, div,
|
||||
ParentElement, Render, SharedString, Styled, div,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use ui::{Icon, IconName, prelude::*};
|
||||
@@ -114,14 +114,14 @@ impl Item for SharedScreen {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>> {
|
||||
Task::ready(Some(cx.new(|cx| Self {
|
||||
) -> Option<Entity<Self>> {
|
||||
Some(cx.new(|cx| Self {
|
||||
view: self.view.update(cx, |view, cx| view.clone(window, cx)),
|
||||
peer_id: self.peer_id,
|
||||
user: self.user.clone(),
|
||||
nav_history: Default::default(),
|
||||
focus: cx.focus_handle(),
|
||||
})))
|
||||
}))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#![allow(unused, dead_code)]
|
||||
use gpui::{
|
||||
AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, Hsla, Task, actions, hsla,
|
||||
};
|
||||
use gpui::{AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, Hsla, actions, hsla};
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::all_theme_colors;
|
||||
use ui::{
|
||||
@@ -102,11 +100,11 @@ impl Item for ThemePreview {
|
||||
_workspace_id: Option<crate::WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Self>>>
|
||||
) -> Option<Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Task::ready(Some(cx.new(|cx| Self::new(window, cx))))
|
||||
Some(cx.new(|cx| Self::new(window, cx)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3627,8 +3627,7 @@ impl Workspace {
|
||||
if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
|
||||
window.focus(&pane.focus_handle(cx));
|
||||
} else {
|
||||
self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, window, cx)
|
||||
.detach();
|
||||
self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3995,8 +3994,7 @@ impl Workspace {
|
||||
clone_active_item,
|
||||
} => {
|
||||
if *clone_active_item {
|
||||
self.split_and_clone(pane.clone(), *direction, window, cx)
|
||||
.detach();
|
||||
self.split_and_clone(pane.clone(), *direction, window, cx);
|
||||
} else {
|
||||
self.split_and_move(pane.clone(), *direction, window, cx);
|
||||
}
|
||||
@@ -4137,27 +4135,21 @@ impl Workspace {
|
||||
direction: SplitDirection,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<Entity<Pane>>> {
|
||||
let Some(item) = pane.read(cx).active_item() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let task = item.clone_on_split(self.database_id(), window, cx);
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if let Some(clone) = task.await {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let new_pane = this.add_pane(window, cx);
|
||||
new_pane.update(cx, |pane, cx| {
|
||||
pane.add_item(clone, true, true, None, window, cx)
|
||||
});
|
||||
this.center.split(&pane, &new_pane, direction).unwrap();
|
||||
cx.notify();
|
||||
new_pane
|
||||
})
|
||||
.ok()
|
||||
) -> Option<Entity<Pane>> {
|
||||
let item = pane.read(cx).active_item()?;
|
||||
let maybe_pane_handle =
|
||||
if let Some(clone) = item.clone_on_split(self.database_id(), window, cx) {
|
||||
let new_pane = self.add_pane(window, cx);
|
||||
new_pane.update(cx, |pane, cx| {
|
||||
pane.add_item(clone, true, true, None, window, cx)
|
||||
});
|
||||
self.center.split(&pane, &new_pane, direction).unwrap();
|
||||
cx.notify();
|
||||
Some(new_pane)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
};
|
||||
maybe_pane_handle
|
||||
}
|
||||
|
||||
pub fn join_all_panes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -8191,27 +8183,19 @@ pub fn clone_active_item(
|
||||
let Some(active_item) = source.read(cx).active_item() else {
|
||||
return;
|
||||
};
|
||||
let destination = destination.downgrade();
|
||||
let task = active_item.clone_on_split(workspace_id, window, cx);
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let Some(clone) = task.await else {
|
||||
return;
|
||||
};
|
||||
destination
|
||||
.update_in(cx, |target_pane, window, cx| {
|
||||
target_pane.add_item(
|
||||
clone,
|
||||
focus_destination,
|
||||
focus_destination,
|
||||
Some(target_pane.items_len()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
.detach();
|
||||
destination.update(cx, |target_pane, cx| {
|
||||
let Some(clone) = active_item.clone_on_split(workspace_id, window, cx) else {
|
||||
return;
|
||||
};
|
||||
target_pane.add_item(
|
||||
clone,
|
||||
focus_destination,
|
||||
focus_destination,
|
||||
Some(target_pane.items_len()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -8718,24 +8702,25 @@ mod tests {
|
||||
cx,
|
||||
);
|
||||
|
||||
let right_pane =
|
||||
workspace.split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx);
|
||||
let right_pane = workspace
|
||||
.split_and_clone(left_pane.clone(), SplitDirection::Right, window, cx)
|
||||
.unwrap();
|
||||
|
||||
let boxed_clone = single_entry_items[1].boxed_clone();
|
||||
let right_pane = window.spawn(cx, async move |cx| {
|
||||
right_pane.await.inspect(|right_pane| {
|
||||
right_pane
|
||||
.update_in(cx, |pane, window, cx| {
|
||||
pane.add_item(boxed_clone, true, true, None, window, cx);
|
||||
pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
|
||||
})
|
||||
.unwrap();
|
||||
})
|
||||
right_pane.update(cx, |pane, cx| {
|
||||
pane.add_item(
|
||||
single_entry_items[1].boxed_clone(),
|
||||
true,
|
||||
true,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
pane.add_item(Box::new(item_3_4.clone()), true, true, None, window, cx);
|
||||
});
|
||||
|
||||
(left_pane, right_pane)
|
||||
});
|
||||
let right_pane = right_pane.await.unwrap();
|
||||
|
||||
cx.focus(&right_pane);
|
||||
|
||||
let mut close = right_pane.update_in(cx, |pane, window, cx| {
|
||||
@@ -10552,10 +10537,7 @@ mod tests {
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert_eq!(workspace.panes.len(), 3, "Two new panes were created");
|
||||
for pane in workspace.panes() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition.workspace = true
|
||||
name = "zed"
|
||||
version = "0.210.0"
|
||||
version = "0.210.4"
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
preview
|
||||
@@ -2839,16 +2839,14 @@ mod tests {
|
||||
});
|
||||
|
||||
// Split the pane with the first entry, then open the second entry again.
|
||||
let (task1, task2) = window
|
||||
window
|
||||
.update(cx, |w, window, cx| {
|
||||
(
|
||||
w.split_and_clone(w.active_pane().clone(), SplitDirection::Right, window, cx),
|
||||
w.open_path(file2.clone(), None, true, window, cx),
|
||||
)
|
||||
w.split_and_clone(w.active_pane().clone(), SplitDirection::Right, window, cx);
|
||||
w.open_path(file2.clone(), None, true, window, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
task1.await.unwrap();
|
||||
task2.await.unwrap();
|
||||
|
||||
window
|
||||
.read_with(cx, |w, cx| {
|
||||
@@ -3471,13 +3469,7 @@ mod tests {
|
||||
SplitDirection::Right,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
window
|
||||
.update(cx, |workspace, window, cx| {
|
||||
);
|
||||
workspace.open_path(
|
||||
(worktree.read(cx).id(), rel_path("the-new-name.rs")),
|
||||
None,
|
||||
|
||||
@@ -720,7 +720,7 @@ impl Item for ComponentPreview {
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<gpui::Entity<Self>>>
|
||||
) -> Option<gpui::Entity<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -742,13 +742,13 @@ impl Item for ComponentPreview {
|
||||
cx,
|
||||
);
|
||||
|
||||
Task::ready(match self_result {
|
||||
match self_result {
|
||||
Ok(preview) => Some(cx.new(|_cx| preview)),
|
||||
Err(e) => {
|
||||
log::error!("Failed to clone component preview: {}", e);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||
|
||||
Reference in New Issue
Block a user