Compare commits
8 Commits
v0.212.7
...
v0.210.1-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18f31e8bf9 | ||
|
|
780d60bc9d | ||
|
|
08ba1939c6 | ||
|
|
c4858f8ae6 | ||
|
|
f3768cfc40 | ||
|
|
80d1551671 | ||
|
|
00a14349de | ||
|
|
aa8128e2ff |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -20918,7 +20918,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.210.0"
|
||||
version = "0.210.1"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
|
||||
@@ -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."],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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.1"
|
||||
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