Compare commits

...
Sign in to create a new pull request.

13 commits

Author SHA1 Message Date
Max Brunsfeld
7b721fd6df For now, always reinstall cargo bundle 0.5 when bundling 2023-06-15 17:18:27 -07:00
Max Brunsfeld
624e62b8d6 zed 0.90.2 2023-06-15 15:33:27 -07:00
Max Brunsfeld
8910f7970b Fix failure to upload panics when multiple panics happen at the same time (#2616)
When multiple panics occur at the same time (usually because one thread
panics, and another thread joins it), multiple panic JSON objects can
get written to the same panic file. The resulting file won't be valid
JSON.

This PR addresses that problem via two changes:
* Format panic files as single-line JSON objects
* When a panic file  isn't valid JSON, try taking the first line

In the future, we could try combining all of the backtraces, but for
now, I just want to avoid a problem of not reporting a panic at all.

Release Notes:

- Fixed a problem with Zed's internal crash reporting.
2023-06-15 15:32:51 -07:00
Max Brunsfeld
16012fd800 Don't rely on debug symbols for panic reporting (#2615)
This fixes a regression introduced in
https://github.com/zed-industries/zed/pull/2560, where panic reports did
not include backtraces. The problem was that in that PR, I assumed we
could retrieve file paths for symbols in our backtraces. But actually,
that functionality only works when the app is built locally, and a
`.dSYM` file can be magically found by the OS. We don't ship those dSYM
files with Zed, so panic symbols do not have file paths available.

Panic backtraces will still be more useful and less noisy than before
though: we will strip out frames for which we don't have symbol names,
and remove leading panic-handling stack frames from the backtraces.

Release Notes:

- N/A
2023-06-15 14:10:07 -07:00
Piotr Osiewicz
0b095f28ef editor: Add language setting for comment continuations (#2605)
Per @JosephTLyons request I've added a language setting for comment
continuations.

Release Notes:

- Added a language setting for comment continuations.
2023-06-14 15:12:00 -04:00
Joseph Lyons
23a81e3d93 v0.90.x stable 2023-06-14 13:27:18 -04:00
Antonio Scandurra
d238e1867e Overhaul assistant panel (#2610)
Closes
https://linear.app/zed-industries/issue/Z-2368/use-a-different-icon-for-the-assistant-panel
Closes
https://linear.app/zed-industries/issue/Z-2363/ship-the-assistant-only-on-preview
Closes
https://linear.app/zed-industries/issue/Z-2331/scrolling-makes-it-hard-to-read
Closes
https://linear.app/zed-industries/issue/Z-2306/allow-undo-and-collaboration-in-assistant

This pull request is a significant overhaul of the assistant panel,
which now uses a simple `Buffer` as opposed to a `MultiBuffer` to show
messages. Specifically, we track the start of each message with an
anchor located right after the newline (or `Anchor::MIN` for the first
message). When the anchor becomes invalid (that is, the newline is
deleted), we merge the message with the preceding ones. Crucially,
messages don't actually get deleted so that, if the newline anchor
becomes valid again (such as when undoing/redoing), we can restore the
messages as well.

As part of this overhaul, we are also improving the scrolling behavior
to maintain the viewport stable only when editing or moving the cursor,
but otherwise leave the scroll position unchanged when manually
scrolling up or down.

Note that with these changes, we are limiting access to the assistant to
users on preview (and dev), as we want to polish the behavior a little
more before shipping to the general public. Users on stable will still
be able to see the default settings/keybindings of the assistant, but I
think that's okay, as they won't be able to do anything with them.

Release Notes:

- Added support for undo/redo in the assistant (preview-only)
- Improved the scrolling behavior of the assistant when it was
generating responses. Now Zed will keep the viewport stable only when
editing or moving the cursor, but otherwise leave the scroll position
unchanged when manually scrolling up or down (preview-only)
- Changed the icon of the assistant panel (preview-only)

**Note for @JosephTLyons: given that we're feature flagging this, let's
make sure things on stable look reasonable and work correctly. Things to
look out for: ensure a stock installation works, changing the settings
on stable works, changing the keybinding on stable works.**
2023-06-14 14:11:02 +02:00
Joseph T. Lyons
40b6c657ef Fix comments in settings files (#2604)
I accidentally added the information about folder-specific settings to
the user settings default file - just fixing that.

Release Notes:

- N/A
2023-06-13 04:07:41 -04:00
Max Brunsfeld
9d32e933e8 Bump protocol version 2023-06-09 11:51:09 -07:00
Max Brunsfeld
ca356108f0 zed 0.90.1 2023-06-09 11:06:07 -07:00
Piotr Osiewicz
1c820ea3c6 Z-2303 editor: fix comment extension on the start of line (#2595)
Release Notes:

- Improved comment extension for cases where cursor was placed before
start of comment marker
2023-06-09 11:05:23 -07:00
Mikayla Maki
0824bc5700 Switch to attaching git statuses to their associated entries (#2571)
This rewrites and simplifies the git status system by attaching the git
status to each individual entry. This also improves the git testing
infrastructure to cover more cases and be more accurate to how file
events actually occur. This also fixes several other bugs in the worktree and the buffer, and stops any randomly generated actions from happening inside a `.git` folder. Hopefully, we can undo this change soon once our randomized testing is more robust.

Release Notes:

- Will require a DB migration

TODO:
- [x] Pass randomized tests
- [x] Get ready for merging
2023-06-09 11:05:12 -07:00
Joseph Lyons
89d9d2d2b9 v0.90.x preview 2023-06-07 11:57:20 -04:00
36 changed files with 1571 additions and 1303 deletions

3
Cargo.lock generated
View file

@ -2440,6 +2440,7 @@ dependencies = [
"parking_lot 0.11.2",
"regex",
"rope",
"rpc",
"serde",
"serde_derive",
"serde_json",
@ -8782,7 +8783,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.90.0"
version = "0.90.2"
dependencies = [
"activity_indicator",
"ai",

View file

@ -0,0 +1,4 @@
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 4C2.5 2.89531 3.39688 2 4.5 2H9.5C10.6031 2 11.5 2.89531 11.5 4V8C11.5 9.10312 10.6031 10 9.5 10H4.5C3.39688 10 2.5 9.10312 2.5 8V4ZM5 4C4.44687 4 4 4.44687 4 5C4 5.55313 4.44687 6 5 6C5.55313 6 6 5.55313 6 5C6 4.44687 5.55313 4 5 4ZM9 6C9.55313 6 10 5.55313 10 5C10 4.44687 9.55313 4 9 4C8.44687 4 8 4.44687 8 5C8 5.55313 8.44687 6 9 6ZM5 8.5C5.275 8.5 5.5 8.275 5.5 8C5.5 7.725 5.275 7.5 5 7.5C4.725 7.5 4.5 7.725 4.5 8C4.5 8.275 4.725 8.5 5 8.5ZM7 7.5C6.725 7.5 6.5 7.725 6.5 8C6.5 8.275 6.725 8.5 7 8.5C7.275 8.5 7.5 8.275 7.5 8C7.5 7.725 7.275 7.5 7 7.5ZM9 8.5C9.275 8.5 9.5 8.275 9.5 8C9.5 7.725 9.275 7.5 9 7.5C8.725 7.5 8.5 7.725 8.5 8C8.5 8.275 8.725 8.5 9 8.5ZM0 14C0 12.3156 1.34312 11 3 11H11C12.6562 11 14 12.3156 14 14V15C14 15.5531 13.5531 16 13 16H11V14C11 13.4469 10.5531 13 10 13H4C3.44687 13 3 13.4469 3 14V16H1C0.447812 16 0 15.5531 0 15V14Z" fill="#808080"/>
<path d="M7.5 2H6.5V0.5C6.5 0.22375 6.725 0 7 0C7.275 0 7.5 0.22375 7.5 0.5V2ZM1.5 4.5V7.5C1.5 7.775 1.27625 8 1 8C0.72375 8 0.5 7.775 0.5 7.5V4.5C0.5 4.225 0.72375 4 1 4C1.27625 4 1.5 4.225 1.5 4.5ZM5.5 16H4.5V14.5C4.5 14.225 4.725 14 5 14C5.275 14 5.5 14.225 5.5 14.5V16ZM7.5 16H6.5V14.5C6.5 14.225 6.725 14 7 14C7.275 14 7.5 14.225 7.5 14.5V16ZM9 14C9.275 14 9.5 14.225 9.5 14.5V16H8.5V14.5C8.5 14.225 8.725 14 9 14ZM13.5 7.5C13.5 7.775 13.275 8 13 8C12.725 8 12.5 7.775 12.5 7.5V4.5C12.5 4.225 12.725 4 13 4C13.275 4 13.5 4.225 13.5 4.5V7.5Z" fill="#808080"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -108,6 +108,8 @@
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
"remove_trailing_whitespace_on_save": true,
// Whether to start a new line with a comment when a previous line is a comment as well.
"extend_comment_on_newline": true,
// Whether or not to ensure there's a single newline at the end of a buffer
// when saving it.
"ensure_final_newline_on_save": true,

View file

@ -1,11 +1,5 @@
// Folder-specific Zed settings
// Folder-specific settings
//
// A subset of Zed's settings can be configured on a per-folder basis.
//
// For information on how to configure Zed, see the Zed
// documentation: https://zed.dev/docs/configuring-zed
//
// To see all of Zed's default settings without changing your
// custom settings, run the `open default settings` command
// from the command palette or from `Zed` application menu.
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://docs.zed.dev/configuration/configuring-zed#folder-specific-settings
{}

View file

@ -1,7 +1,7 @@
// Folder-specific settings
// Zed settings
//
// For a full list of overridable settings, and general information on folder-specific settings, see the documentation:
// https://docs.zed.dev/configuration/configuring-zed#folder-specific-settings
// For information on how to configure Zed, see the Zed
// documentation: https://zed.dev/docs/configuring-zed
//
// To see all of Zed's default settings without changing your
// custom settings, run the `open default settings` command

File diff suppressed because it is too large Load diff

View file

@ -66,6 +66,7 @@ impl<'a> AddAssign<&'a Local> for Local {
}
}
/// A vector clock
#[derive(Clone, Default, Hash, Eq, PartialEq)]
pub struct Global(SmallVec<[u32; 8]>);

View file

@ -76,6 +76,7 @@ CREATE TABLE "worktree_entries" (
"is_symlink" BOOL NOT NULL,
"is_ignored" BOOL NOT NULL,
"is_deleted" BOOL NOT NULL,
"git_status" INTEGER,
PRIMARY KEY(project_id, worktree_id, id),
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
);
@ -96,22 +97,6 @@ CREATE TABLE "worktree_repositories" (
CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id");
CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id");
CREATE TABLE "worktree_repository_statuses" (
"project_id" INTEGER NOT NULL,
"worktree_id" INTEGER NOT NULL,
"work_directory_id" INTEGER NOT NULL,
"repo_path" VARCHAR NOT NULL,
"status" INTEGER NOT NULL,
"scan_id" INTEGER NOT NULL,
"is_deleted" BOOL NOT NULL,
PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path),
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
);
CREATE INDEX "index_worktree_repository_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id");
CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id_and_work_directory_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
CREATE TABLE "worktree_settings_files" (
"project_id" INTEGER NOT NULL,
"worktree_id" INTEGER NOT NULL,

View file

@ -0,0 +1,4 @@
DROP TABLE "worktree_repository_statuses";
ALTER TABLE "worktree_entries"
ADD "git_status" INT8;

View file

@ -1539,6 +1539,7 @@ impl Database {
}),
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
git_status: db_entry.git_status.map(|status| status as i32),
});
}
}
@ -1573,54 +1574,6 @@ impl Database {
worktree.updated_repositories.push(proto::RepositoryEntry {
work_directory_id: db_repository.work_directory_id as u64,
branch: db_repository.branch,
removed_repo_paths: Default::default(),
updated_statuses: Default::default(),
});
}
}
}
// Repository Status Entries
for repository in worktree.updated_repositories.iter_mut() {
let repository_status_entry_filter =
if let Some(rejoined_worktree) = rejoined_worktree {
worktree_repository_statuses::Column::ScanId
.gt(rejoined_worktree.scan_id)
} else {
worktree_repository_statuses::Column::IsDeleted.eq(false)
};
let mut db_repository_statuses =
worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(
worktree_repository_statuses::Column::ProjectId
.eq(project.id),
)
.add(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree.id),
)
.add(
worktree_repository_statuses::Column::WorkDirectoryId
.eq(repository.work_directory_id),
)
.add(repository_status_entry_filter),
)
.stream(&*tx)
.await?;
while let Some(db_status_entry) = db_repository_statuses.next().await {
let db_status_entry = db_status_entry?;
if db_status_entry.is_deleted {
repository
.removed_repo_paths
.push(db_status_entry.repo_path);
} else {
repository.updated_statuses.push(proto::StatusEntry {
repo_path: db_status_entry.repo_path,
status: db_status_entry.status as i32,
});
}
}
@ -2396,6 +2349,7 @@ impl Database {
mtime_nanos: ActiveValue::set(mtime.nanos as i32),
is_symlink: ActiveValue::set(entry.is_symlink),
is_ignored: ActiveValue::set(entry.is_ignored),
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
is_deleted: ActiveValue::set(false),
scan_id: ActiveValue::set(update.scan_id as i64),
}
@ -2414,6 +2368,7 @@ impl Database {
worktree_entry::Column::MtimeNanos,
worktree_entry::Column::IsSymlink,
worktree_entry::Column::IsIgnored,
worktree_entry::Column::GitStatus,
worktree_entry::Column::ScanId,
])
.to_owned(),
@ -2467,68 +2422,6 @@ impl Database {
)
.exec(&*tx)
.await?;
for repository in update.updated_repositories.iter() {
if !repository.updated_statuses.is_empty() {
worktree_repository_statuses::Entity::insert_many(
repository.updated_statuses.iter().map(|status_entry| {
worktree_repository_statuses::ActiveModel {
project_id: ActiveValue::set(project_id),
worktree_id: ActiveValue::set(worktree_id),
work_directory_id: ActiveValue::set(
repository.work_directory_id as i64,
),
repo_path: ActiveValue::set(status_entry.repo_path.clone()),
status: ActiveValue::set(status_entry.status as i64),
scan_id: ActiveValue::set(update.scan_id as i64),
is_deleted: ActiveValue::set(false),
}
}),
)
.on_conflict(
OnConflict::columns([
worktree_repository_statuses::Column::ProjectId,
worktree_repository_statuses::Column::WorktreeId,
worktree_repository_statuses::Column::WorkDirectoryId,
worktree_repository_statuses::Column::RepoPath,
])
.update_columns([
worktree_repository_statuses::Column::ScanId,
worktree_repository_statuses::Column::Status,
worktree_repository_statuses::Column::IsDeleted,
])
.to_owned(),
)
.exec(&*tx)
.await?;
}
if !repository.removed_repo_paths.is_empty() {
worktree_repository_statuses::Entity::update_many()
.filter(
worktree_repository_statuses::Column::ProjectId
.eq(project_id)
.and(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree_id),
)
.and(
worktree_repository_statuses::Column::WorkDirectoryId
.eq(repository.work_directory_id as i64),
)
.and(worktree_repository_statuses::Column::RepoPath.is_in(
repository.removed_repo_paths.iter().map(String::as_str),
)),
)
.set(worktree_repository_statuses::ActiveModel {
is_deleted: ActiveValue::Set(true),
scan_id: ActiveValue::Set(update.scan_id as i64),
..Default::default()
})
.exec(&*tx)
.await?;
}
}
}
if !update.removed_repositories.is_empty() {
@ -2812,6 +2705,7 @@ impl Database {
}),
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
git_status: db_entry.git_status.map(|status| status as i32),
});
}
}
@ -2837,41 +2731,12 @@ impl Database {
proto::RepositoryEntry {
work_directory_id: db_repository_entry.work_directory_id as u64,
branch: db_repository_entry.branch,
removed_repo_paths: Default::default(),
updated_statuses: Default::default(),
},
);
}
}
}
{
let mut db_status_entries = worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(worktree_repository_statuses::Column::ProjectId.eq(project_id))
.add(worktree_repository_statuses::Column::IsDeleted.eq(false)),
)
.stream(&*tx)
.await?;
while let Some(db_status_entry) = db_status_entries.next().await {
let db_status_entry = db_status_entry?;
if let Some(worktree) = worktrees.get_mut(&(db_status_entry.worktree_id as u64))
{
if let Some(repository_entry) = worktree
.repository_entries
.get_mut(&(db_status_entry.work_directory_id as u64))
{
repository_entry.updated_statuses.push(proto::StatusEntry {
repo_path: db_status_entry.repo_path,
status: db_status_entry.status as i32,
});
}
}
}
}
// Populate worktree diagnostic summaries.
{
let mut db_summaries = worktree_diagnostic_summary::Entity::find()

View file

@ -15,6 +15,7 @@ pub struct Model {
pub inode: i64,
pub mtime_seconds: i64,
pub mtime_nanos: i32,
pub git_status: Option<i64>,
pub is_symlink: bool,
pub is_ignored: bool,
pub is_deleted: bool,

View file

@ -2415,14 +2415,10 @@ async fn test_git_diff_base_change(
"
.unindent();
client_a
.fs
.as_fake()
.set_index_for_repo(
Path::new("/dir/.git"),
&[(Path::new("a.txt"), diff_base.clone())],
)
.await;
client_a.fs.as_fake().set_index_for_repo(
Path::new("/dir/.git"),
&[(Path::new("a.txt"), diff_base.clone())],
);
// Create the buffer
let buffer_local_a = project_local
@ -2464,14 +2460,10 @@ async fn test_git_diff_base_change(
);
});
client_a
.fs
.as_fake()
.set_index_for_repo(
Path::new("/dir/.git"),
&[(Path::new("a.txt"), new_diff_base.clone())],
)
.await;
client_a.fs.as_fake().set_index_for_repo(
Path::new("/dir/.git"),
&[(Path::new("a.txt"), new_diff_base.clone())],
);
// Wait for buffer_local_a to receive it
deterministic.run_until_parked();
@ -2513,14 +2505,10 @@ async fn test_git_diff_base_change(
"
.unindent();
client_a
.fs
.as_fake()
.set_index_for_repo(
Path::new("/dir/sub/.git"),
&[(Path::new("b.txt"), diff_base.clone())],
)
.await;
client_a.fs.as_fake().set_index_for_repo(
Path::new("/dir/sub/.git"),
&[(Path::new("b.txt"), diff_base.clone())],
);
// Create the buffer
let buffer_local_b = project_local
@ -2562,14 +2550,10 @@ async fn test_git_diff_base_change(
);
});
client_a
.fs
.as_fake()
.set_index_for_repo(
Path::new("/dir/sub/.git"),
&[(Path::new("b.txt"), new_diff_base.clone())],
)
.await;
client_a.fs.as_fake().set_index_for_repo(
Path::new("/dir/sub/.git"),
&[(Path::new("b.txt"), new_diff_base.clone())],
);
// Wait for buffer_local_b to receive it
deterministic.run_until_parked();
@ -2646,8 +2630,7 @@ async fn test_git_branch_name(
client_a
.fs
.as_fake()
.set_branch_name(Path::new("/dir/.git"), Some("branch-1"))
.await;
.set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
// Wait for it to catch up to the new branch
deterministic.run_until_parked();
@ -2673,8 +2656,7 @@ async fn test_git_branch_name(
client_a
.fs
.as_fake()
.set_branch_name(Path::new("/dir/.git"), Some("branch-2"))
.await;
.set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
// Wait for buffer_local_a to receive it
deterministic.run_until_parked();
@ -2726,17 +2708,13 @@ async fn test_git_status_sync(
const A_TXT: &'static str = "a.txt";
const B_TXT: &'static str = "b.txt";
client_a
.fs
.as_fake()
.set_status_for_repo(
Path::new("/dir/.git"),
&[
(&Path::new(A_TXT), GitFileStatus::Added),
(&Path::new(B_TXT), GitFileStatus::Added),
],
)
.await;
client_a.fs.as_fake().set_status_for_repo_via_git_operation(
Path::new("/dir/.git"),
&[
(&Path::new(A_TXT), GitFileStatus::Added),
(&Path::new(B_TXT), GitFileStatus::Added),
],
);
let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
let project_id = active_call_a
@ -2763,8 +2741,7 @@ async fn test_git_status_sync(
assert_eq!(worktrees.len(), 1);
let worktree = worktrees[0].clone();
let snapshot = worktree.read(cx).snapshot();
let root_entry = snapshot.root_git_entry().unwrap();
assert_eq!(root_entry.status_for_file(&snapshot, file), status);
assert_eq!(snapshot.status_for_file(file), status);
}
// Smoke test status reading
@ -2780,14 +2757,13 @@ async fn test_git_status_sync(
client_a
.fs
.as_fake()
.set_status_for_repo(
.set_status_for_repo_via_working_copy_change(
Path::new("/dir/.git"),
&[
(&Path::new(A_TXT), GitFileStatus::Modified),
(&Path::new(B_TXT), GitFileStatus::Modified),
],
)
.await;
);
// Wait for buffer_local_a to receive it
deterministic.run_until_parked();

View file

@ -422,7 +422,7 @@ async fn apply_client_operation(
);
ensure_project_shared(&project, client, cx).await;
if !client.fs.paths().contains(&new_root_path) {
if !client.fs.paths(false).contains(&new_root_path) {
client.fs.create_dir(&new_root_path).await.unwrap();
}
project
@ -628,12 +628,13 @@ async fn apply_client_operation(
ensure_project_shared(&project, client, cx).await;
let requested_version = buffer.read_with(cx, |buffer, _| buffer.version());
let save = project.update(cx, |project, cx| project.save_buffer(buffer, cx));
let save = cx.background().spawn(async move {
let (saved_version, _, _) = save
.await
let save = project.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx));
let save = cx.spawn(|cx| async move {
save.await
.map_err(|err| anyhow!("save request failed: {:?}", err))?;
assert!(saved_version.observed_all(&requested_version));
assert!(buffer
.read_with(&cx, |buffer, _| { buffer.saved_version().to_owned() })
.observed_all(&requested_version));
anyhow::Ok(())
});
if detach {
@ -743,7 +744,7 @@ async fn apply_client_operation(
} => {
if !client
.fs
.directories()
.directories(false)
.contains(&path.parent().unwrap().to_owned())
{
return Err(TestError::Inapplicable);
@ -770,10 +771,16 @@ async fn apply_client_operation(
repo_path,
contents,
} => {
if !client.fs.directories().contains(&repo_path) {
if !client.fs.directories(false).contains(&repo_path) {
return Err(TestError::Inapplicable);
}
for (path, _) in contents.iter() {
if !client.fs.files().contains(&repo_path.join(path)) {
return Err(TestError::Inapplicable);
}
}
log::info!(
"{}: writing git index for repo {:?}: {:?}",
client.username,
@ -789,13 +796,13 @@ async fn apply_client_operation(
if client.fs.metadata(&dot_git_dir).await?.is_none() {
client.fs.create_dir(&dot_git_dir).await?;
}
client.fs.set_index_for_repo(&dot_git_dir, &contents).await;
client.fs.set_index_for_repo(&dot_git_dir, &contents);
}
GitOperation::WriteGitBranch {
repo_path,
new_branch,
} => {
if !client.fs.directories().contains(&repo_path) {
if !client.fs.directories(false).contains(&repo_path) {
return Err(TestError::Inapplicable);
}
@ -810,15 +817,21 @@ async fn apply_client_operation(
if client.fs.metadata(&dot_git_dir).await?.is_none() {
client.fs.create_dir(&dot_git_dir).await?;
}
client.fs.set_branch_name(&dot_git_dir, new_branch).await;
client.fs.set_branch_name(&dot_git_dir, new_branch);
}
GitOperation::WriteGitStatuses {
repo_path,
statuses,
git_operation,
} => {
if !client.fs.directories().contains(&repo_path) {
if !client.fs.directories(false).contains(&repo_path) {
return Err(TestError::Inapplicable);
}
for (path, _) in statuses.iter() {
if !client.fs.files().contains(&repo_path.join(path)) {
return Err(TestError::Inapplicable);
}
}
log::info!(
"{}: writing git statuses for repo {:?}: {:?}",
@ -838,10 +851,16 @@ async fn apply_client_operation(
client.fs.create_dir(&dot_git_dir).await?;
}
client
.fs
.set_status_for_repo(&dot_git_dir, statuses.as_slice())
.await;
if git_operation {
client
.fs
.set_status_for_repo_via_git_operation(&dot_git_dir, statuses.as_slice());
} else {
client.fs.set_status_for_repo_via_working_copy_change(
&dot_git_dir,
statuses.as_slice(),
);
}
}
},
}
@ -913,9 +932,10 @@ fn check_consistency_between_clients(clients: &[(Rc<TestClient>, TestAppContext)
assert_eq!(
guest_snapshot.entries(false).collect::<Vec<_>>(),
host_snapshot.entries(false).collect::<Vec<_>>(),
"{} has different snapshot than the host for worktree {:?} and project {:?}",
"{} has different snapshot than the host for worktree {:?} ({:?}) and project {:?}",
client.username,
host_snapshot.abs_path(),
id,
guest_project.remote_id(),
);
assert_eq!(guest_snapshot.repositories().collect::<Vec<_>>(), host_snapshot.repositories().collect::<Vec<_>>(),
@ -1230,6 +1250,7 @@ enum GitOperation {
WriteGitStatuses {
repo_path: PathBuf,
statuses: Vec<(PathBuf, GitFileStatus)>,
git_operation: bool,
},
}
@ -1575,7 +1596,7 @@ impl TestPlan {
.choose(&mut self.rng)
.cloned() else { continue };
let project_root_name = root_name_for_project(&project, cx);
let mut paths = client.fs.paths();
let mut paths = client.fs.paths(false);
paths.remove(0);
let new_root_path = if paths.is_empty() || self.rng.gen() {
Path::new("/").join(&self.next_root_dir_name(user_id))
@ -1755,7 +1776,7 @@ impl TestPlan {
let is_dir = self.rng.gen::<bool>();
let content;
let mut path;
let dir_paths = client.fs.directories();
let dir_paths = client.fs.directories(false);
if is_dir {
content = String::new();
@ -1809,7 +1830,7 @@ impl TestPlan {
let repo_path = client
.fs
.directories()
.directories(false)
.choose(&mut self.rng)
.unwrap()
.clone();
@ -1855,9 +1876,12 @@ impl TestPlan {
})
.collect::<Vec<_>>();
let git_operation = self.rng.gen::<bool>();
GitOperation::WriteGitStatuses {
repo_path,
statuses,
git_operation,
}
}
_ => unreachable!(),

View file

@ -430,7 +430,7 @@ impl ProjectDiagnosticsEditor {
});
self.editor.update(cx, |editor, cx| {
editor.remove_blocks(blocks_to_remove, cx);
editor.remove_blocks(blocks_to_remove, None, cx);
let block_ids = editor.insert_blocks(
blocks_to_add.into_iter().map(|block| {
let (excerpt_id, text_anchor) = block.position;
@ -442,6 +442,7 @@ impl ProjectDiagnosticsEditor {
disposition: block.disposition,
}
}),
Some(Autoscroll::fit()),
cx,
);

View file

@ -31,13 +31,11 @@ use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
pub use editor_settings::EditorSettings;
pub use element::RenderExcerptHeaderParams;
pub use element::{
Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles,
};
use futures::FutureExt;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::LayoutContext;
use gpui::{
actions,
color::Color,
@ -511,7 +509,6 @@ pub struct Editor {
mode: EditorMode,
show_gutter: bool,
placeholder_text: Option<Arc<str>>,
render_excerpt_header: Option<element::RenderExcerptHeader>,
highlighted_rows: Option<Range<u32>>,
#[allow(clippy::type_complexity)]
background_highlights: BTreeMap<TypeId, (fn(&Theme) -> Color, Vec<Range<Anchor>>)>,
@ -1317,7 +1314,6 @@ impl Editor {
mode,
show_gutter: mode == EditorMode::Full,
placeholder_text: None,
render_excerpt_header: None,
highlighted_rows: None,
background_highlights: Default::default(),
nav_history: None,
@ -2169,8 +2165,8 @@ impl Editor {
self.transact(cx, |this, cx| {
let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
let selections = this.selections.all::<usize>(cx);
let buffer = this.buffer.read(cx).snapshot(cx);
let multi_buffer = this.buffer.read(cx);
let buffer = multi_buffer.snapshot(cx);
selections
.iter()
.map(|selection| {
@ -2181,58 +2177,74 @@ impl Editor {
let end = selection.end;
let is_cursor = start == end;
let language_scope = buffer.language_scope_at(start);
let (comment_delimiter, insert_extra_newline) =
if let Some(language) = &language_scope {
let leading_whitespace_len = buffer
.reversed_chars_at(start)
.take_while(|c| c.is_whitespace() && *c != '\n')
.map(|c| c.len_utf8())
.sum::<usize>();
let (comment_delimiter, insert_extra_newline) = if let Some(language) =
&language_scope
{
let leading_whitespace_len = buffer
.reversed_chars_at(start)
.take_while(|c| c.is_whitespace() && *c != '\n')
.map(|c| c.len_utf8())
.sum::<usize>();
let trailing_whitespace_len = buffer
.chars_at(end)
.take_while(|c| c.is_whitespace() && *c != '\n')
.map(|c| c.len_utf8())
.sum::<usize>();
let trailing_whitespace_len = buffer
.chars_at(end)
.take_while(|c| c.is_whitespace() && *c != '\n')
.map(|c| c.len_utf8())
.sum::<usize>();
let insert_extra_newline =
language.brackets().any(|(pair, enabled)| {
let pair_start = pair.start.trim_end();
let pair_end = pair.end.trim_start();
let insert_extra_newline =
language.brackets().any(|(pair, enabled)| {
let pair_start = pair.start.trim_end();
let pair_end = pair.end.trim_start();
enabled
&& pair.newline
&& buffer.contains_str_at(
end + trailing_whitespace_len,
pair_end,
)
&& buffer.contains_str_at(
(start - leading_whitespace_len)
.saturating_sub(pair_start.len()),
pair_start,
)
});
// Comment extension on newline is allowed only for cursor selections
let comment_delimiter =
language.line_comment_prefix().filter(|_| is_cursor);
let comment_delimiter = if let Some(delimiter) = comment_delimiter {
buffer
.buffer_line_for_row(start_point.row)
.is_some_and(|(snapshot, range)| {
snapshot
.chars_for_range(range)
.skip_while(|c| c.is_whitespace())
.take(delimiter.len())
.eq(delimiter.chars())
})
.then(|| delimiter.clone())
} else {
None
};
(comment_delimiter, insert_extra_newline)
enabled
&& pair.newline
&& buffer.contains_str_at(
end + trailing_whitespace_len,
pair_end,
)
&& buffer.contains_str_at(
(start - leading_whitespace_len)
.saturating_sub(pair_start.len()),
pair_start,
)
});
// Comment extension on newline is allowed only for cursor selections
let comment_delimiter = language.line_comment_prefix().filter(|_| {
let is_comment_extension_enabled =
multi_buffer.settings_at(0, cx).extend_comment_on_newline;
is_cursor && is_comment_extension_enabled
});
let comment_delimiter = if let Some(delimiter) = comment_delimiter {
buffer
.buffer_line_for_row(start_point.row)
.is_some_and(|(snapshot, range)| {
let mut index_of_first_non_whitespace = 0;
let line_starts_with_comment = snapshot
.chars_for_range(range)
.skip_while(|c| {
let should_skip = c.is_whitespace();
if should_skip {
index_of_first_non_whitespace += 1;
}
should_skip
})
.take(delimiter.len())
.eq(delimiter.chars());
let cursor_is_placed_after_comment_marker =
index_of_first_non_whitespace + delimiter.len()
<= start_point.column as usize;
line_starts_with_comment
&& cursor_is_placed_after_comment_marker
})
.then(|| delimiter.clone())
} else {
(None, false)
None
};
(comment_delimiter, insert_extra_newline)
} else {
(None, false)
};
let capacity_for_delimiter = comment_delimiter
.as_deref()
@ -6256,6 +6268,7 @@ impl Editor {
}),
disposition: BlockDisposition::Below,
}],
Some(Autoscroll::fit()),
cx,
)[0];
this.pending_rename = Some(RenameState {
@ -6322,7 +6335,11 @@ impl Editor {
cx: &mut ViewContext<Self>,
) -> Option<RenameState> {
let rename = self.pending_rename.take()?;
self.remove_blocks([rename.block_id].into_iter().collect(), cx);
self.remove_blocks(
[rename.block_id].into_iter().collect(),
Some(Autoscroll::fit()),
cx,
);
self.clear_text_highlights::<Rename>(cx);
self.show_local_selections = true;
@ -6708,29 +6725,43 @@ impl Editor {
pub fn insert_blocks(
&mut self,
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
autoscroll: Option<Autoscroll>,
cx: &mut ViewContext<Self>,
) -> Vec<BlockId> {
let blocks = self
.display_map
.update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
self.request_autoscroll(Autoscroll::fit(), cx);
if let Some(autoscroll) = autoscroll {
self.request_autoscroll(autoscroll, cx);
}
blocks
}
pub fn replace_blocks(
&mut self,
blocks: HashMap<BlockId, RenderBlock>,
autoscroll: Option<Autoscroll>,
cx: &mut ViewContext<Self>,
) {
self.display_map
.update(cx, |display_map, _| display_map.replace_blocks(blocks));
self.request_autoscroll(Autoscroll::fit(), cx);
if let Some(autoscroll) = autoscroll {
self.request_autoscroll(autoscroll, cx);
}
}
pub fn remove_blocks(&mut self, block_ids: HashSet<BlockId>, cx: &mut ViewContext<Self>) {
pub fn remove_blocks(
&mut self,
block_ids: HashSet<BlockId>,
autoscroll: Option<Autoscroll>,
cx: &mut ViewContext<Self>,
) {
self.display_map.update(cx, |display_map, cx| {
display_map.remove_blocks(block_ids, cx)
});
if let Some(autoscroll) = autoscroll {
self.request_autoscroll(autoscroll, cx);
}
}
pub fn longest_row(&self, cx: &mut AppContext) -> u32 {
@ -6811,20 +6842,6 @@ impl Editor {
cx.notify();
}
pub fn set_render_excerpt_header(
&mut self,
render_excerpt_header: impl 'static
+ Fn(
&mut Editor,
RenderExcerptHeaderParams,
&mut LayoutContext<Editor>,
) -> AnyElement<Editor>,
cx: &mut ViewContext<Self>,
) {
self.render_excerpt_header = Some(Arc::new(render_excerpt_header));
cx.notify();
}
pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
@ -7432,6 +7449,7 @@ pub enum Event {
},
ScrollPositionChanged {
local: bool,
autoscroll: bool,
},
Closed,
}
@ -7463,12 +7481,8 @@ impl View for Editor {
});
}
let mut editor = EditorElement::new(style.clone());
if let Some(render_excerpt_header) = self.render_excerpt_header.clone() {
editor = editor.with_render_excerpt_header(render_excerpt_header);
}
Stack::new()
.with_child(editor)
.with_child(EditorElement::new(style.clone()))
.with_child(ChildView::new(&self.mouse_context_menu, cx))
.into_any()
}

View file

@ -1732,17 +1732,40 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
},
None,
));
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
{
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
// Fooˇ
"});
cx.update_editor(|e, cx| e.newline(&Newline, cx));
cx.assert_editor_state(indoc! {"
// Foo
//ˇ
"});
// Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
cx.set_state(indoc! {"
ˇ// Foo
"});
cx.update_editor(|e, cx| e.newline(&Newline, cx));
cx.assert_editor_state(indoc! {"
ˇ// Foo
"});
}
// Ensure that comment continuations can be disabled.
update_test_settings(cx, |settings| {
settings.defaults.extend_comment_on_newline = Some(false);
});
let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {"
// Fooˇ
"});
cx.update_editor(|e, cx| e.newline(&Newline, cx));
cx.assert_editor_state(indoc! {"
// Foo
//ˇ
ˇ
"});
}
@ -2472,6 +2495,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
height: 1,
render: Arc::new(|_| Empty::new().into_any()),
}],
Some(Autoscroll::fit()),
cx,
);
editor.change_selections(None, cx, |s| {

View file

@ -91,41 +91,17 @@ impl SelectionLayout {
}
}
pub struct RenderExcerptHeaderParams<'a> {
pub id: crate::ExcerptId,
pub buffer: &'a language::BufferSnapshot,
pub range: &'a crate::ExcerptRange<text::Anchor>,
pub starts_new_buffer: bool,
pub gutter_padding: f32,
pub editor_style: &'a EditorStyle,
}
pub type RenderExcerptHeader = Arc<
dyn Fn(
&mut Editor,
RenderExcerptHeaderParams,
&mut LayoutContext<Editor>,
) -> AnyElement<Editor>,
>;
pub struct EditorElement {
style: Arc<EditorStyle>,
render_excerpt_header: RenderExcerptHeader,
}
impl EditorElement {
pub fn new(style: EditorStyle) -> Self {
Self {
style: Arc::new(style),
render_excerpt_header: Arc::new(render_excerpt_header),
}
}
pub fn with_render_excerpt_header(mut self, render: RenderExcerptHeader) -> Self {
self.render_excerpt_header = render;
self
}
fn attach_mouse_handlers(
scene: &mut SceneBuilder,
position_map: &Arc<PositionMap>,
@ -1531,18 +1507,117 @@ impl EditorElement {
range,
starts_new_buffer,
..
} => (self.render_excerpt_header)(
editor,
RenderExcerptHeaderParams {
id: *id,
buffer,
range,
starts_new_buffer: *starts_new_buffer,
gutter_padding,
editor_style: style,
},
cx,
),
} => {
let tooltip_style = theme::current(cx).tooltip.clone();
let include_root = editor
.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default();
let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
let jump_path = ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
};
let jump_anchor = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
enum JumpIcon {}
MouseEventHandler::<JumpIcon, _>::new((*id).into(), cx, |state, _| {
let style = style.jump_icon.style_for(state, false);
Svg::new("icons/arrow_up_right_8.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.contained()
.with_style(style.container)
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, editor, cx| {
if let Some(workspace) = editor
.workspace
.as_ref()
.and_then(|(workspace, _)| workspace.upgrade(cx))
{
workspace.update(cx, |workspace, cx| {
Editor::jump(
workspace,
jump_path.clone(),
jump_position,
jump_anchor,
cx,
);
});
}
})
.with_tooltip::<JumpIcon>(
(*id).into(),
"Jump to Buffer".to_string(),
Some(Box::new(crate::OpenExcerpts)),
tooltip_style.clone(),
cx,
)
.aligned()
.flex_float()
});
if *starts_new_buffer {
let editor_font_size = style.text.font_size;
let style = &style.diagnostic_path_header;
let font_size = (style.text_scale_factor * editor_font_size).round();
let path = buffer.resolve_file_path(cx, include_root);
let mut filename = None;
let mut parent_path = None;
// Can't use .and_then() because `.file_name()` and `.parent()` return references :(
if let Some(path) = path {
filename = path.file_name().map(|f| f.to_string_lossy().to_string());
parent_path =
path.parent().map(|p| p.to_string_lossy().to_string() + "/");
}
Flex::row()
.with_child(
Label::new(
filename.unwrap_or_else(|| "untitled".to_string()),
style.filename.text.clone().with_font_size(font_size),
)
.contained()
.with_style(style.filename.container)
.aligned(),
)
.with_children(parent_path.map(|path| {
Label::new(path, style.path.text.clone().with_font_size(font_size))
.contained()
.with_style(style.path.container)
.aligned()
}))
.with_children(jump_icon)
.contained()
.with_style(style.container)
.with_padding_left(gutter_padding)
.with_padding_right(gutter_padding)
.expanded()
.into_any_named("path header block")
} else {
let text_style = style.text.clone();
Flex::row()
.with_child(Label::new("", text_style))
.with_children(jump_icon)
.contained()
.with_padding_left(gutter_padding)
.with_padding_right(gutter_padding)
.expanded()
.into_any_named("collapsed context")
}
}
};
element.layout(
@ -2679,121 +2754,6 @@ impl HighlightedRange {
}
}
fn render_excerpt_header(
editor: &mut Editor,
RenderExcerptHeaderParams {
id,
buffer,
range,
starts_new_buffer,
gutter_padding,
editor_style,
}: RenderExcerptHeaderParams,
cx: &mut LayoutContext<Editor>,
) -> AnyElement<Editor> {
let tooltip_style = theme::current(cx).tooltip.clone();
let include_root = editor
.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default();
let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
let jump_path = ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
};
let jump_anchor = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
enum JumpIcon {}
MouseEventHandler::<JumpIcon, _>::new(id.into(), cx, |state, _| {
let style = editor_style.jump_icon.style_for(state, false);
Svg::new("icons/arrow_up_right_8.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.contained()
.with_style(style.container)
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, editor, cx| {
if let Some(workspace) = editor
.workspace
.as_ref()
.and_then(|(workspace, _)| workspace.upgrade(cx))
{
workspace.update(cx, |workspace, cx| {
Editor::jump(workspace, jump_path.clone(), jump_position, jump_anchor, cx);
});
}
})
.with_tooltip::<JumpIcon>(
id.into(),
"Jump to Buffer".to_string(),
Some(Box::new(crate::OpenExcerpts)),
tooltip_style.clone(),
cx,
)
.aligned()
.flex_float()
});
if starts_new_buffer {
let style = &editor_style.diagnostic_path_header;
let font_size = (style.text_scale_factor * editor_style.text.font_size).round();
let path = buffer.resolve_file_path(cx, include_root);
let mut filename = None;
let mut parent_path = None;
// Can't use .and_then() because `.file_name()` and `.parent()` return references :(
if let Some(path) = path {
filename = path.file_name().map(|f| f.to_string_lossy().to_string());
parent_path = path.parent().map(|p| p.to_string_lossy().to_string() + "/");
}
Flex::row()
.with_child(
Label::new(
filename.unwrap_or_else(|| "untitled".to_string()),
style.filename.text.clone().with_font_size(font_size),
)
.contained()
.with_style(style.filename.container)
.aligned(),
)
.with_children(parent_path.map(|path| {
Label::new(path, style.path.text.clone().with_font_size(font_size))
.contained()
.with_style(style.path.container)
.aligned()
}))
.with_children(jump_icon)
.contained()
.with_style(style.container)
.with_padding_left(gutter_padding)
.with_padding_right(gutter_padding)
.expanded()
.into_any_named("path header block")
} else {
let text_style = editor_style.text.clone();
Flex::row()
.with_child(Label::new("", text_style))
.with_children(jump_icon)
.contained()
.with_padding_left(gutter_padding)
.with_padding_right(gutter_padding)
.expanded()
.into_any_named("collapsed context")
}
}
fn position_to_display_point(
position: Vector2F,
text_bounds: RectF,
@ -2923,6 +2883,7 @@ mod tests {
position: Anchor::min(),
render: Arc::new(|_| Empty::new().into_any()),
}],
None,
cx,
);

View file

@ -294,7 +294,7 @@ impl FollowableItem for Editor {
match event {
Event::Edited => true,
Event::SelectionsChanged { local } => *local,
Event::ScrollPositionChanged { local } => *local,
Event::ScrollPositionChanged { local, .. } => *local,
_ => false,
}
}

View file

@ -173,6 +173,7 @@ impl ScrollManager {
scroll_position: Vector2F,
map: &DisplaySnapshot,
local: bool,
autoscroll: bool,
workspace_id: Option<i64>,
cx: &mut ViewContext<Editor>,
) {
@ -203,7 +204,7 @@ impl ScrollManager {
)
};
self.set_anchor(new_anchor, top_row, local, workspace_id, cx);
self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx);
}
fn set_anchor(
@ -211,11 +212,12 @@ impl ScrollManager {
anchor: ScrollAnchor,
top_row: u32,
local: bool,
autoscroll: bool,
workspace_id: Option<i64>,
cx: &mut ViewContext<Editor>,
) {
self.anchor = anchor;
cx.emit(Event::ScrollPositionChanged { local });
cx.emit(Event::ScrollPositionChanged { local, autoscroll });
self.show_scrollbar(cx);
self.autoscroll_request.take();
if let Some(workspace_id) = workspace_id {
@ -296,21 +298,28 @@ impl Editor {
}
pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
self.set_scroll_position_internal(scroll_position, true, cx);
self.set_scroll_position_internal(scroll_position, true, false, cx);
}
pub(crate) fn set_scroll_position_internal(
&mut self,
scroll_position: Vector2F,
local: bool,
autoscroll: bool,
cx: &mut ViewContext<Self>,
) {
let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
hide_hover(self, cx);
let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
self.scroll_manager
.set_scroll_position(scroll_position, &map, local, workspace_id, cx);
self.scroll_manager.set_scroll_position(
scroll_position,
&map,
local,
autoscroll,
workspace_id,
cx,
);
}
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
@ -326,7 +335,7 @@ impl Editor {
.to_point(&self.buffer().read(cx).snapshot(cx))
.row;
self.scroll_manager
.set_anchor(scroll_anchor, top_row, true, workspace_id, cx);
.set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx);
}
pub(crate) fn set_scroll_anchor_remote(
@ -341,7 +350,7 @@ impl Editor {
.to_point(&self.buffer().read(cx).snapshot(cx))
.row;
self.scroll_manager
.set_anchor(scroll_anchor, top_row, false, workspace_id, cx);
.set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
}
pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {

View file

@ -136,23 +136,23 @@ impl Editor {
if target_top < start_row {
scroll_position.set_y(target_top);
self.set_scroll_position_internal(scroll_position, local, cx);
self.set_scroll_position_internal(scroll_position, local, true, cx);
} else if target_bottom >= end_row {
scroll_position.set_y(target_bottom - visible_lines);
self.set_scroll_position_internal(scroll_position, local, cx);
self.set_scroll_position_internal(scroll_position, local, true, cx);
}
}
AutoscrollStrategy::Center => {
scroll_position.set_y((first_cursor_top - margin).max(0.0));
self.set_scroll_position_internal(scroll_position, local, cx);
self.set_scroll_position_internal(scroll_position, local, true, cx);
}
AutoscrollStrategy::Top => {
scroll_position.set_y((first_cursor_top).max(0.0));
self.set_scroll_position_internal(scroll_position, local, cx);
self.set_scroll_position_internal(scroll_position, local, true, cx);
}
AutoscrollStrategy::Bottom => {
scroll_position.set_y((last_cursor_bottom - visible_lines).max(0.0));
self.set_scroll_position_internal(scroll_position, local, cx);
self.set_scroll_position_internal(scroll_position, local, true, cx);
}
}

View file

@ -14,6 +14,8 @@ lsp = { path = "../lsp" }
rope = { path = "../rope" }
util = { path = "../util" }
sum_tree = { path = "../sum_tree" }
rpc = { path = "../rpc" }
anyhow.workspace = true
async-trait.workspace = true
futures.workspace = true

View file

@ -29,6 +29,8 @@ use collections::{btree_map, BTreeMap};
#[cfg(any(test, feature = "test-support"))]
use repository::{FakeGitRepositoryState, GitFileStatus};
#[cfg(any(test, feature = "test-support"))]
use std::ffi::OsStr;
#[cfg(any(test, feature = "test-support"))]
use std::sync::Weak;
lazy_static! {
@ -501,6 +503,11 @@ impl FakeFsState {
}
}
#[cfg(any(test, feature = "test-support"))]
lazy_static! {
pub static ref FS_DOT_GIT: &'static OsStr = OsStr::new(".git");
}
#[cfg(any(test, feature = "test-support"))]
impl FakeFs {
pub fn new(executor: Arc<gpui::executor::Background>) -> Arc<Self> {
@ -619,7 +626,7 @@ impl FakeFs {
.boxed()
}
pub fn with_git_state<F>(&self, dot_git: &Path, f: F)
pub fn with_git_state<F>(&self, dot_git: &Path, emit_git_event: bool, f: F)
where
F: FnOnce(&mut FakeGitRepositoryState),
{
@ -633,18 +640,22 @@ impl FakeFs {
f(&mut repo_state);
state.emit_event([dot_git]);
if emit_git_event {
state.emit_event([dot_git]);
}
} else {
panic!("not a directory");
}
}
pub async fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
self.with_git_state(dot_git, |state| state.branch_name = branch.map(Into::into))
pub fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
self.with_git_state(dot_git, true, |state| {
state.branch_name = branch.map(Into::into)
})
}
pub async fn set_index_for_repo(&self, dot_git: &Path, head_state: &[(&Path, String)]) {
self.with_git_state(dot_git, |state| {
pub fn set_index_for_repo(&self, dot_git: &Path, head_state: &[(&Path, String)]) {
self.with_git_state(dot_git, true, |state| {
state.index_contents.clear();
state.index_contents.extend(
head_state
@ -654,8 +665,32 @@ impl FakeFs {
});
}
pub async fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, GitFileStatus)]) {
self.with_git_state(dot_git, |state| {
pub fn set_status_for_repo_via_working_copy_change(
&self,
dot_git: &Path,
statuses: &[(&Path, GitFileStatus)],
) {
self.with_git_state(dot_git, false, |state| {
state.worktree_statuses.clear();
state.worktree_statuses.extend(
statuses
.iter()
.map(|(path, content)| ((**path).into(), content.clone())),
);
});
self.state.lock().emit_event(
statuses
.iter()
.map(|(path, _)| dot_git.parent().unwrap().join(path)),
);
}
pub fn set_status_for_repo_via_git_operation(
&self,
dot_git: &Path,
statuses: &[(&Path, GitFileStatus)],
) {
self.with_git_state(dot_git, true, |state| {
state.worktree_statuses.clear();
state.worktree_statuses.extend(
statuses
@ -665,7 +700,7 @@ impl FakeFs {
});
}
pub fn paths(&self) -> Vec<PathBuf> {
pub fn paths(&self, include_dot_git: bool) -> Vec<PathBuf> {
let mut result = Vec::new();
let mut queue = collections::VecDeque::new();
queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
@ -675,12 +710,18 @@ impl FakeFs {
queue.push_back((path.join(name), entry.clone()));
}
}
result.push(path);
if include_dot_git
|| !path
.components()
.any(|component| component.as_os_str() == *FS_DOT_GIT)
{
result.push(path);
}
}
result
}
pub fn directories(&self) -> Vec<PathBuf> {
pub fn directories(&self, include_dot_git: bool) -> Vec<PathBuf> {
let mut result = Vec::new();
let mut queue = collections::VecDeque::new();
queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
@ -689,7 +730,13 @@ impl FakeFs {
for (name, entry) in entries {
queue.push_back((path.join(name), entry.clone()));
}
result.push(path);
if include_dot_git
|| !path
.components()
.any(|component| component.as_os_str() == *FS_DOT_GIT)
{
result.push(path);
}
}
}
result

View file

@ -1,6 +1,8 @@
use anyhow::Result;
use collections::HashMap;
use git2::ErrorCode;
use parking_lot::Mutex;
use rpc::proto;
use serde_derive::{Deserialize, Serialize};
use std::{
cmp::Ordering,
@ -24,7 +26,7 @@ pub trait GitRepository: Send {
fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>>;
fn status(&self, path: &RepoPath) -> Option<GitFileStatus>;
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>>;
}
impl std::fmt::Debug for dyn GitRepository {
@ -91,9 +93,18 @@ impl GitRepository for LibGitRepository {
Some(map)
}
fn status(&self, path: &RepoPath) -> Option<GitFileStatus> {
let status = self.status_file(path).log_err()?;
read_status(status)
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>> {
let status = self.status_file(path);
match status {
Ok(status) => Ok(read_status(status)),
Err(e) => {
if e.code() == ErrorCode::NotFound {
Ok(None)
} else {
Err(e.into())
}
}
}
}
}
@ -155,9 +166,9 @@ impl GitRepository for FakeGitRepository {
Some(map)
}
fn status(&self, path: &RepoPath) -> Option<GitFileStatus> {
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>> {
let state = self.state.lock();
state.worktree_statuses.get(path).cloned()
Ok(state.worktree_statuses.get(path).cloned())
}
}
@ -197,8 +208,51 @@ pub enum GitFileStatus {
Conflict,
}
impl GitFileStatus {
pub fn merge(
this: Option<GitFileStatus>,
other: Option<GitFileStatus>,
prefer_other: bool,
) -> Option<GitFileStatus> {
if prefer_other {
return other;
} else {
match (this, other) {
(Some(GitFileStatus::Conflict), _) | (_, Some(GitFileStatus::Conflict)) => {
Some(GitFileStatus::Conflict)
}
(Some(GitFileStatus::Modified), _) | (_, Some(GitFileStatus::Modified)) => {
Some(GitFileStatus::Modified)
}
(Some(GitFileStatus::Added), _) | (_, Some(GitFileStatus::Added)) => {
Some(GitFileStatus::Added)
}
_ => None,
}
}
}
pub fn from_proto(git_status: Option<i32>) -> Option<GitFileStatus> {
git_status.and_then(|status| {
proto::GitStatus::from_i32(status).map(|status| match status {
proto::GitStatus::Added => GitFileStatus::Added,
proto::GitStatus::Modified => GitFileStatus::Modified,
proto::GitStatus::Conflict => GitFileStatus::Conflict,
})
})
}
pub fn to_proto(self) -> i32 {
match self {
GitFileStatus::Added => proto::GitStatus::Added as i32,
GitFileStatus::Modified => proto::GitStatus::Modified as i32,
GitFileStatus::Conflict => proto::GitStatus::Conflict as i32,
}
}
}
#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)]
pub struct RepoPath(PathBuf);
pub struct RepoPath(pub PathBuf);
impl RepoPath {
pub fn new(path: PathBuf) -> Self {

View file

@ -51,6 +51,7 @@ pub struct LanguageSettings {
pub enable_language_server: bool,
pub show_copilot_suggestions: bool,
pub show_whitespaces: ShowWhitespaceSetting,
pub extend_comment_on_newline: bool,
}
#[derive(Clone, Debug, Default)]
@ -95,6 +96,8 @@ pub struct LanguageSettingsContent {
pub show_copilot_suggestions: Option<bool>,
#[serde(default)]
pub show_whitespaces: Option<ShowWhitespaceSetting>,
#[serde(default)]
pub extend_comment_on_newline: Option<bool>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
@ -340,7 +343,10 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
src.show_copilot_suggestions,
);
merge(&mut settings.show_whitespaces, src.show_whitespaces);
merge(
&mut settings.extend_comment_on_newline,
src.extend_comment_on_newline,
);
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;

View file

@ -37,8 +37,8 @@ use language::{
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel,
Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, File as _,
Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, Operation, Patch,
PendingLanguageServer, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16,
Transaction, Unclipped,
PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
Unclipped,
};
use log::error;
use lsp::{
@ -69,7 +69,7 @@ use std::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
time::{Duration, Instant, SystemTime},
time::{Duration, Instant},
};
use terminals::Terminals;
use util::{
@ -1616,7 +1616,7 @@ impl Project {
&self,
buffer: ModelHandle<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Result<(clock::Global, RopeFingerprint, SystemTime)>> {
) -> Task<Result<()>> {
let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
return Task::ready(Err(anyhow!("buffer doesn't have a file")));
};
@ -5156,9 +5156,9 @@ impl Project {
return None;
}
let path = &project_path.path;
changed_repos.iter().find(|(work_dir, change)| {
path.starts_with(work_dir) && change.git_dir_changed
})?;
changed_repos
.iter()
.find(|(work_dir, _)| path.starts_with(work_dir))?;
let receiver = receiver.clone();
let path = path.clone();
Some(async move {
@ -5181,9 +5181,9 @@ impl Project {
return None;
}
let path = file.path();
changed_repos.iter().find(|(work_dir, change)| {
path.starts_with(work_dir) && change.git_dir_changed
})?;
changed_repos
.iter()
.find(|(work_dir, _)| path.starts_with(work_dir))?;
Some((buffer, path.clone()))
})
.collect::<Vec<_>>();
@ -5984,16 +5984,15 @@ impl Project {
.await?;
let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id());
let (saved_version, fingerprint, mtime) = this
.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
this.update(&mut cx, |this, cx| this.save_buffer(buffer.clone(), cx))
.await?;
Ok(proto::BufferSaved {
Ok(buffer.read_with(&cx, |buffer, _| proto::BufferSaved {
project_id,
buffer_id,
version: serialize_version(&saved_version),
mtime: Some(mtime.into()),
fingerprint: language::proto::serialize_fingerprint(fingerprint),
})
version: serialize_version(buffer.saved_version()),
mtime: Some(buffer.saved_mtime().into()),
fingerprint: language::proto::serialize_fingerprint(buffer.saved_version_fingerprint()),
}))
}
async fn handle_reload_buffers(

File diff suppressed because it is too large Load diff

View file

@ -1002,6 +1002,7 @@ impl ProjectPanel {
mtime: entry.mtime,
is_symlink: false,
is_ignored: false,
git_status: entry.git_status,
});
}
if expanded_dir_ids.binary_search(&entry.id).is_err()
@ -1011,6 +1012,9 @@ impl ProjectPanel {
}
entry_iter.advance();
}
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
visible_worktree_entries.sort_by(|entry_a, entry_b| {
let mut components_a = entry_a.path.components().peekable();
let mut components_b = entry_b.path.components().peekable();
@ -1108,14 +1112,8 @@ impl ProjectPanel {
.unwrap_or(&[]);
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
for (entry, repo) in
snapshot.entries_with_repositories(visible_worktree_entries[entry_range].iter())
{
let status = (git_status_setting
&& entry.path.parent().is_some()
&& !entry.is_ignored)
.then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path)))
.flatten();
for entry in visible_worktree_entries[entry_range].iter() {
let status = git_status_setting.then(|| entry.git_status).flatten();
let mut details = EntryDetails {
filename: entry

View file

@ -1005,13 +1005,12 @@ message Entry {
Timestamp mtime = 5;
bool is_symlink = 6;
bool is_ignored = 7;
optional GitStatus git_status = 8;
}
message RepositoryEntry {
uint64 work_directory_id = 1;
optional string branch = 2;
repeated string removed_repo_paths = 3;
repeated StatusEntry updated_statuses = 4;
}
message StatusEntry {

View file

@ -6,4 +6,4 @@ pub use conn::Connection;
pub use peer::*;
mod macros;
pub const PROTOCOL_VERSION: u32 = 57;
pub const PROTOCOL_VERSION: u32 = 58;

View file

@ -480,6 +480,11 @@ impl<T: Item> SumTree<T> {
} => child_trees.last().unwrap().rightmost_leaf(),
}
}
#[cfg(debug_assertions)]
pub fn _debug_entries(&self) -> Vec<&T> {
self.iter().collect::<Vec<_>>()
}
}
impl<T: Item + PartialEq> PartialEq for SumTree<T> {

View file

@ -57,7 +57,7 @@ pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T {
}
/// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and
/// enforcing a maximum length. Sort the items according to the given callback. Before calling this,
/// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this,
/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator.
pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F)
where

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.90.0"
version = "0.90.2"
publish = false
[lib]

View file

@ -1 +1 @@
dev
stable

View file

@ -31,7 +31,6 @@ use std::{
ffi::OsStr,
fs::OpenOptions,
io::Write as _,
ops::Not,
os::unix::prelude::OsStrExt,
panic,
path::{Path, PathBuf},
@ -373,7 +372,6 @@ struct Panic {
os_version: Option<String>,
architecture: String,
panicked_on: u128,
identifying_backtrace: Option<Vec<String>>,
}
#[derive(Serialize)]
@ -401,61 +399,18 @@ fn init_panic_hook(app: &App) {
.unwrap_or_else(|| "Box<Any>".to_string());
let backtrace = Backtrace::new();
let backtrace = backtrace
let mut backtrace = backtrace
.frames()
.iter()
.filter_map(|frame| {
let symbol = frame.symbols().first()?;
let path = symbol.filename()?;
Some((path, symbol.lineno(), format!("{:#}", symbol.name()?)))
})
.filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?)))
.collect::<Vec<_>>();
let this_file_path = Path::new(file!());
// Find the first frame in the backtrace for this panic hook itself. Exclude
// that frame and all frames before it.
let mut start_frame_ix = 0;
let mut codebase_root_path = None;
for (ix, (path, _, _)) in backtrace.iter().enumerate() {
if path.ends_with(this_file_path) {
start_frame_ix = ix + 1;
codebase_root_path = path.ancestors().nth(this_file_path.components().count());
break;
}
}
// Exclude any subsequent frames inside of rust's panic handling system.
while let Some((path, _, _)) = backtrace.get(start_frame_ix) {
if path.starts_with("/rustc") {
start_frame_ix += 1;
} else {
break;
}
}
// Build two backtraces:
// * one for display, which includes symbol names for all frames, and files
// and line numbers for symbols in this codebase
// * one for identification and de-duplication, which only includes symbol
// names for symbols in this codebase.
let mut display_backtrace = Vec::new();
let mut identifying_backtrace = Vec::new();
for (path, line, symbol) in &backtrace[start_frame_ix..] {
display_backtrace.push(symbol.clone());
if let Some(codebase_root_path) = &codebase_root_path {
if let Ok(suffix) = path.strip_prefix(&codebase_root_path) {
identifying_backtrace.push(symbol.clone());
let display_path = suffix.to_string_lossy();
if let Some(line) = line {
display_backtrace.push(format!(" {display_path}:{line}"));
} else {
display_backtrace.push(format!(" {display_path}"));
}
}
}
// Strip out leading stack frames for rust panic-handling.
if let Some(ix) = backtrace
.iter()
.position(|name| name == "rust_begin_unwind")
{
backtrace.drain(0..=ix);
}
let panic_data = Panic {
@ -477,29 +432,27 @@ fn init_panic_hook(app: &App) {
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis(),
backtrace: display_backtrace,
identifying_backtrace: identifying_backtrace
.is_empty()
.not()
.then_some(identifying_backtrace),
backtrace,
};
if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
if is_pty {
if is_pty {
if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
eprintln!("{}", panic_data_json);
return;
}
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
let panic_file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(&panic_file_path)
.log_err();
if let Some(mut panic_file) = panic_file {
write!(&mut panic_file, "{}", panic_data_json).log_err();
panic_file.flush().log_err();
} else {
if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
let panic_file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(&panic_file_path)
.log_err();
if let Some(mut panic_file) = panic_file {
writeln!(&mut panic_file, "{}", panic_data_json).log_err();
panic_file.flush().log_err();
}
}
}
}));
@ -531,23 +484,45 @@ fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
}
if telemetry_settings.diagnostics {
let panic_data_text = smol::fs::read_to_string(&child_path)
let panic_file_content = smol::fs::read_to_string(&child_path)
.await
.context("error reading panic file")?;
let body = serde_json::to_string(&PanicRequest {
panic: serde_json::from_str(&panic_data_text)?,
token: ZED_SECRET_CLIENT_TOKEN.into(),
})
.unwrap();
let panic = serde_json::from_str(&panic_file_content)
.ok()
.or_else(|| {
panic_file_content
.lines()
.next()
.and_then(|line| serde_json::from_str(line).ok())
})
.unwrap_or_else(|| {
log::error!(
"failed to deserialize panic file {:?}",
panic_file_content
);
None
});
let request = Request::post(&panic_report_url)
.redirect_policy(isahc::config::RedirectPolicy::Follow)
.header("Content-Type", "application/json")
.body(body.into())?;
let response = http.send(request).await.context("error sending panic")?;
if !response.status().is_success() {
log::error!("Error uploading panic to server: {}", response.status());
if let Some(panic) = panic {
let body = serde_json::to_string(&PanicRequest {
panic,
token: ZED_SECRET_CLIENT_TOKEN.into(),
})
.unwrap();
let request = Request::post(&panic_report_url)
.redirect_policy(isahc::config::RedirectPolicy::Follow)
.header("Content-Type", "application/json")
.body(body.into())?;
let response =
http.send(request).await.context("error sending panic")?;
if !response.status().is_success() {
log::error!(
"Error uploading panic to server: {}",
response.status()
);
}
}
}

View file

@ -254,13 +254,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
workspace.toggle_panel_focus::<TerminalPanel>(cx);
},
);
cx.add_action(
|workspace: &mut Workspace,
_: &ai::assistant::ToggleFocus,
cx: &mut ViewContext<Workspace>| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
},
);
cx.add_global_action({
let app_state = Arc::downgrade(&app_state);
move |_: &NewWindow, cx: &mut AppContext| {
@ -366,9 +359,12 @@ pub fn initialize_workspace(
let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
let (project_panel, terminal_panel, assistant_panel) =
futures::try_join!(project_panel, terminal_panel, assistant_panel)?;
let assistant_panel = if *util::channel::RELEASE_CHANNEL == ReleaseChannel::Stable {
None
} else {
Some(AssistantPanel::load(workspace_handle.clone(), cx.clone()).await?)
};
let (project_panel, terminal_panel) = futures::try_join!(project_panel, terminal_panel)?;
workspace_handle.update(&mut cx, |workspace, cx| {
let project_panel_position = project_panel.position(cx);
workspace.add_panel(project_panel, cx);
@ -387,7 +383,9 @@ pub fn initialize_workspace(
}
workspace.add_panel(terminal_panel, cx);
workspace.add_panel(assistant_panel, cx);
if let Some(assistant_panel) = assistant_panel {
workspace.add_panel(assistant_panel, cx);
}
})?;
Ok(())
})

View file

@ -23,7 +23,7 @@ done
export ZED_BUNDLE=true
export MACOSX_DEPLOYMENT_TARGET=10.15.7
which cargo-bundle > /dev/null || cargo install cargo-bundle --version 0.5.0
cargo install cargo-bundle --version 0.5.0
rustup target add wasm32-wasi
# Deal with versions of macOS that don't include libstdc++ headers