Compare commits

...

17 Commits

Author SHA1 Message Date
Joseph T. Lyons
857897ee10 zed 0.190.3 2025-06-07 13:58:18 -04:00
gcp-cherry-pick-bot[bot]
e6de7933bb Fix panic dragging tabs multiple positions to the right (cherry-pick #32305) (#32307)
Cherry-picked Fix panic dragging tabs multiple positions to the right
(#32305)

Closes https://github.com/zed-industries/zed/issues/32303

Release Notes:

- Fixed a panic that occurred when dragging tabs multiple positions to
the right (preview only)

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
2025-06-07 13:46:15 -04:00
Peter Tripp
fc077d194c zed 0.190.2 2025-06-06 18:59:21 -04:00
Michael Sloan
4cf76207aa Fix anchor biases for completion replacement ranges (esp slash commands) (cherry-pick #32262) (#32271)
Closes #32205

The issue was that in some places the end of the replacement range used
anchors with `Bias::Left` instead of `Bias::Right`. Before #31872
completions were recomputed on every change and so the anchor bias
didn't matter. After that change, the end anchor didn't move as the
user's typing. Changing it to `Bias::Right` to "stick" to the character
to the right of the cursor fixes this.

Also fixes slash command arguments to now have `is_incomplete: true`,
since some of the argument providers do limited fuzzy matching.

Release Notes:

- Fixed slash command completions in agent text threads to replace the
user's query text (Preview Only)
2025-06-06 15:57:54 -06:00
Joseph T. Lyons
1b45289efa Fix panic when dragging pinned item left in pinned region (#32263)
This was a regression with my recent fixes to pinned tabs. Dragging a
pinned tab left in the pinned region would still update the pinned tab
count, which would later cause an out-of-bounds later when it used that
value to index into a vec.

https://zed-industries.slack.com/archives/C04S6T1T7TQ/p1749220447796559

Release Notes:

- Fixed a panic caused by dragging a pinned item to the left in the
pinned region
2025-06-06 16:03:20 -04:00
Joseph T. Lyons
8eb12a3c3d Fixed more bugs around moving pinned tabs (#32228)
Closes https://github.com/zed-industries/zed/issues/32199
https://github.com/zed-industries/zed/issues/32229
https://github.com/zed-industries/zed/issues/32230
https://github.com/zed-industries/zed/issues/32232

Release Notes:

- Fixed a bug where if the last tab was a pinned tab and it was dragged
to the right, resulting in a no-op, it would become unpinned
- Fixed a bug where a pinned tab dragged just to the right of the end of
the pinned tab region would become unpinned
- Fixed a bug where dragging a pinned tab from one pane to another
pane's pinned region could result in an existing pinned tab becoming
unpinned when `max_tabs` was reached
- Fixed a bug where moving an unpinned tab to the left, just to the end
of the pinned region, would cause the pinned tabs to become unpinned.
2025-06-06 16:03:10 -04:00
Joseph T. Lyons
7b4f37353a Refactor some logic in handle_tab_drop (#32213)
Tiny little clean up PR after #32184 

Release Notes:

- N/A
2025-06-06 16:02:55 -04:00
Joseph T. Lyons
9b8035e8c2 Fix bugs around tab state loss when moving pinned tabs across panes (#32184)
Closes https://github.com/zed-industries/zed/issues/32181,
https://github.com/zed-industries/zed/issues/32179

In the screenshots: left = nightly, right = dev

Fix 1: A pinned tab dragged into a new split should remain pinned


https://github.com/user-attachments/assets/608a7e10-4ccb-4219-ba81-624298c960b0

Fix 2: Moving a pinned tab from one pane to another should not cause
other pinned tabs to be unpinned


https://github.com/user-attachments/assets/ccc05913-591d-4a43-85bb-3a7164a4d6a8

I also added tests for moving both pinned tabs and unpinned tabs into
existing panes, both into the "pinned" region and the "unpinned" region.

Release Notes:

- Fixed a bug where dragging a pinned tab into a new split would lose
its pinned tab state.
- Fixed a bug where pinned tabs in one pane could be lost when moving
one of the pinned tabs to another pane.
2025-06-06 16:02:42 -04:00
Peter Tripp
f6fdb7094b ci: Auto-release release prefix hotfixes again (#32265)
Undo:
- https://github.com/zed-industries/zed/pull/24894

CC: @JosephTLyons

Release Notes:

- N/A
2025-06-06 15:30:00 -04:00
Peter Tripp
0c90b19fa1 Build zed-remote-server on FreeBSD (#29561)
Builds freebsd-remote-server under qemu working on linux runners.

Release Notes:

- Initial support for ssh remotes running FreeBSD x86_64
2025-06-06 14:00:06 -04:00
Zed Bot
cb2885410d Bump to 0.190.1 for @ConradIrwin 2025-06-06 03:11:19 +00:00
gcp-cherry-pick-bot[bot]
6535a148b5 Fix innermost brackets panic (cherry-pick #32120) (#32189)
Cherry-picked Fix innermost brackets panic (#32120)

Release Notes:

- Fixed a few rare panics that could happen when a multibuffer excerpt
started with expanded deleted content.

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-06-05 16:00:22 -06:00
gcp-cherry-pick-bot[bot]
fc3a5a799e Fix a panic in merge conflict parsing (cherry-pick #32119) (#32123)
Cherry-picked Fix a panic in merge conflict parsing (#32119)

Release Notes:

- Fixed a panic that could occur when editing files containing merge
conflicts.

Co-authored-by: Cole Miller <cole@zed.dev>
2025-06-04 20:32:12 -04:00
Max Brunsfeld
393382c448 Bump tree-sitter-bash to 0.25 (#32091)
Closes https://github.com/zed-industries/zed/issues/23703

Release Notes:

- Fixed a crash that could occur when editing bash files
2025-06-04 13:23:22 -04:00
Max Brunsfeld
2ad24ef799 Bump Tree-sitter to 0.25.6 (#32090)
Fixes #31810 

Release Notes:

- Fixed a crash that could occur when editing YAML files.
2025-06-04 12:55:46 -04:00
Vitaly Slobodin
185e0696ae ruby: Add sorbet and steep to the list of available language servers (#32008)
Hi, this pull request adds `sorbet` and `steep` to the list of available
language servers for the Ruby language in order to prepare default Ruby
language settings for these LS. Both language servers are disabled by
default. We plan to add both in #104 and #102. Thanks!

Release Notes:

- ruby: Added `sorbet` and `steep` to the list of available language servers.
2025-06-04 10:20:51 -04:00
Joseph T. Lyons
750c6356e7 v0.190.x preview 2025-06-04 10:12:33 -04:00
17 changed files with 1095 additions and 62 deletions

View File

@@ -715,6 +715,64 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
freebsd:
timeout-minutes: 60
runs-on: github-8vcpu-ubuntu-2404
if: |
startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
needs: [linux_tests]
name: Build Zed on FreeBSD
# env:
# MYTOKEN : ${{ secrets.MYTOKEN }}
# MYTOKEN2: "value2"
steps:
- uses: actions/checkout@v4
- name: Build FreeBSD remote-server
id: freebsd-build
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
with:
# envs: "MYTOKEN MYTOKEN2"
usesh: true
release: 13.5
copyback: true
prepare: |
pkg install -y \
bash curl jq git \
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
run: |
freebsd-version
sysctl hw.model
sysctl hw.ncpu
sysctl hw.physmem
sysctl hw.usermem
git config --global --add safe.directory /home/runner/work/zed/zed
rustup-init --profile minimal --default-toolchain none -y
. "$HOME/.cargo/env"
./script/bundle-freebsd
mkdir -p out/
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
rm -rf target/
cargo clean
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-freebsd.gz
path: out/zed-remote-server-freebsd-x86_64.gz
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
out/zed-remote-server-freebsd-x86_64.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
nix-build:
name: Build with Nix
uses: ./.github/workflows/nix.yml
@@ -729,12 +787,12 @@ jobs:
if: |
startsWith(github.ref, 'refs/tags/v')
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64]
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, freebsd]
runs-on:
- self-hosted
- bundle
steps:
- name: gh release
run: gh release edit $GITHUB_REF_NAME --draft=true
run: gh release edit $GITHUB_REF_NAME --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -167,6 +167,62 @@ jobs:
- name: Upload Zed Nightly
run: script/upload-nightly linux-targz
freebsd:
timeout-minutes: 60
if: github.repository_owner == 'zed-industries'
runs-on: github-8vcpu-ubuntu-2404
needs: tests
name: Build Zed on FreeBSD
# env:
# MYTOKEN : ${{ secrets.MYTOKEN }}
# MYTOKEN2: "value2"
steps:
- uses: actions/checkout@v4
- name: Build FreeBSD remote-server
id: freebsd-build
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
with:
# envs: "MYTOKEN MYTOKEN2"
usesh: true
release: 13.5
copyback: true
prepare: |
pkg install -y \
bash curl jq git \
rustup-init cmake-core llvm-devel-lite pkgconf protobuf # ibx11 alsa-lib rust-bindgen-cli
run: |
freebsd-version
sysctl hw.model
sysctl hw.ncpu
sysctl hw.physmem
sysctl hw.usermem
git config --global --add safe.directory /home/runner/work/zed/zed
rustup-init --profile minimal --default-toolchain none -y
. "$HOME/.cargo/env"
./script/bundle-freebsd
mkdir -p out/
mv "target/zed-remote-server-freebsd-x86_64.gz" out/
rm -rf target/
cargo clean
- name: Upload Artifact to Workflow - zed-remote-server (run-bundling)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: zed-remote-server-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-freebsd.gz
path: out/zed-remote-server-freebsd-x86_64.gz
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: |
out/zed-remote-server-freebsd-x86_64.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bundle-nix:
name: Build and cache Nix package
needs: tests

10
Cargo.lock generated
View File

@@ -16509,9 +16509,9 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.25.5"
version = "0.25.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac5fff5c47490dfdf473b5228039bfacad9d765d9b6939d26bf7cc064c1c7822"
checksum = "a7cf18d43cbf0bfca51f657132cc616a5097edc4424d538bae6fa60142eaf9f0"
dependencies = [
"cc",
"regex",
@@ -16524,9 +16524,9 @@ dependencies = [
[[package]]
name = "tree-sitter-bash"
version = "0.23.3"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329a4d48623ac337d42b1df84e81a1c9dbb2946907c102ca72db158c1964a52e"
checksum = "871b0606e667e98a1237ebdc1b0d7056e0aebfdc3141d12b399865d4cb6ed8a6"
dependencies = [
"cc",
"tree-sitter-language",
@@ -19708,7 +19708,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.190.0"
version = "0.190.3"
dependencies = [
"activity_indicator",
"agent",

View File

@@ -574,8 +574,8 @@ tokio = { version = "1" }
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
toml = "0.8"
tower-http = "0.4.4"
tree-sitter = { version = "0.25.5", features = ["wasm"] }
tree-sitter-bash = "0.23"
tree-sitter = { version = "0.25.6", features = ["wasm"] }
tree-sitter-bash = "0.25.0"
tree-sitter-c = "0.23"
tree-sitter-cpp = "0.23"
tree-sitter-css = "0.23"

View File

@@ -1525,7 +1525,7 @@
"allow_rewrap": "anywhere"
},
"Ruby": {
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "..."]
"language_servers": ["solargraph", "!ruby-lsp", "!rubocop", "!sorbet", "!steep", "..."]
},
"SCSS": {
"prettier": {

View File

@@ -767,7 +767,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
let snapshot = buffer.read(cx).snapshot();
let source_range = snapshot.anchor_before(state.source_range.start)
..snapshot.anchor_before(state.source_range.end);
..snapshot.anchor_after(state.source_range.end);
let thread_store = self.thread_store.clone();
let text_thread_store = self.text_thread_store.clone();

View File

@@ -240,13 +240,14 @@ impl SlashCommandCompletionProvider {
Ok(vec![project::CompletionResponse {
completions,
is_incomplete: false,
// TODO: Could have slash commands indicate whether their completions are incomplete.
is_incomplete: true,
}])
})
} else {
Task::ready(Ok(vec![project::CompletionResponse {
completions: Vec::new(),
is_incomplete: false,
is_incomplete: true,
}]))
}
}
@@ -275,17 +276,17 @@ impl CompletionProvider for SlashCommandCompletionProvider {
position.row,
call.arguments.last().map_or(call.name.end, |arg| arg.end) as u32,
);
let command_range = buffer.anchor_after(command_range_start)
let command_range = buffer.anchor_before(command_range_start)
..buffer.anchor_after(command_range_end);
let name = line[call.name.clone()].to_string();
let (arguments, last_argument_range) = if let Some(argument) = call.arguments.last()
{
let last_arg_start =
buffer.anchor_after(Point::new(position.row, argument.start as u32));
buffer.anchor_before(Point::new(position.row, argument.start as u32));
let first_arg_start = call.arguments.first().expect("we have the last element");
let first_arg_start =
buffer.anchor_after(Point::new(position.row, first_arg_start.start as u32));
let first_arg_start = buffer
.anchor_before(Point::new(position.row, first_arg_start.start as u32));
let arguments = call
.arguments
.into_iter()
@@ -298,7 +299,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
)
} else {
let start =
buffer.anchor_after(Point::new(position.row, call.name.start as u32));
buffer.anchor_before(Point::new(position.row, call.name.start as u32));
(None, start..buffer_position)
};

View File

@@ -5046,7 +5046,13 @@ impl Editor {
return;
}
let position = self.selections.newest_anchor().head();
let multibuffer_snapshot = self.buffer.read(cx).read(cx);
let position = self
.selections
.newest_anchor()
.head()
.bias_right(&multibuffer_snapshot);
if position.diff_base_anchor.is_some() {
return;
}
@@ -5059,8 +5065,9 @@ impl Editor {
let buffer_snapshot = buffer.read(cx).snapshot();
let query: Option<Arc<String>> =
Self::completion_query(&self.buffer.read(cx).read(cx), position)
.map(|query| query.into());
Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
drop(multibuffer_snapshot);
let provider = match requested_source {
Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),

View File

@@ -726,7 +726,7 @@ fn completion_replace_range(snapshot: &BufferSnapshot, anchor: &Anchor) -> Optio
if end_in_line > start_in_line {
let replace_start = snapshot.anchor_before(line_start + start_in_line);
let replace_end = snapshot.anchor_before(line_start + end_in_line);
let replace_end = snapshot.anchor_after(line_start + end_in_line);
Some(replace_start..replace_end)
} else {
None

View File

@@ -6213,7 +6213,7 @@ impl MultiBufferSnapshot {
cursor.seek_to_start_of_current_excerpt();
let region = cursor.region()?;
let offset = region.range.start;
let buffer_offset = region.buffer_range.start;
let buffer_offset = start_excerpt.buffer_start_offset();
let excerpt_offset = cursor.excerpts.start().clone();
Some(MultiBufferExcerpt {
diff_transforms: cursor.diff_transforms,

View File

@@ -2842,6 +2842,22 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
.unwrap()
+ 1
);
let reference_ranges = cx.update(|cx| {
reference
.excerpts
.iter()
.map(|excerpt| {
(
excerpt.id,
excerpt.range.to_offset(&excerpt.buffer.read(cx).snapshot()),
)
})
.collect::<HashMap<_, _>>()
});
for i in 0..snapshot.len() {
let excerpt = snapshot.excerpt_containing(i..i).unwrap();
assert_eq!(excerpt.buffer_range(), reference_ranges[&excerpt.id()]);
}
assert_consistent_line_numbers(&snapshot);
assert_position_translation(&snapshot);

View File

@@ -171,7 +171,8 @@ impl ConflictSet {
let mut conflicts = Vec::new();
let mut line_pos = 0;
let mut lines = buffer.text_for_range(0..buffer.len()).lines();
let buffer_len = buffer.len();
let mut lines = buffer.text_for_range(0..buffer_len).lines();
let mut conflict_start: Option<usize> = None;
let mut ours_start: Option<usize> = None;
@@ -212,7 +213,7 @@ impl ConflictSet {
&& theirs_start.is_some()
{
let theirs_end = line_pos;
let conflict_end = line_end + 1;
let conflict_end = (line_end + 1).min(buffer_len);
let range = buffer.anchor_after(conflict_start.unwrap())
..buffer.anchor_before(conflict_end);
@@ -390,6 +391,22 @@ mod tests {
assert_eq!(their_text, "This is their version in a nested conflict\n");
}
#[test]
fn test_conflict_markers_at_eof() {
let test_content = r#"
<<<<<<< ours
=======
This is their version
>>>>>>> "#
.unindent();
let buffer_id = BufferId::new(1).unwrap();
let buffer = Buffer::new(0, buffer_id, test_content.to_string());
let snapshot = buffer.snapshot();
let conflict_snapshot = ConflictSet::parse(&snapshot);
assert_eq!(conflict_snapshot.conflicts.len(), 1);
}
#[test]
fn test_conflicts_in_range() {
// Create a buffer with conflict markers

View File

@@ -2174,10 +2174,6 @@ impl Pane {
self.pinned_tab_count > ix
}
fn has_pinned_tabs(&self) -> bool {
self.pinned_tab_count != 0
}
fn has_unpinned_tabs(&self) -> bool {
self.pinned_tab_count < self.items.len()
}
@@ -2893,6 +2889,7 @@ impl Pane {
|| cfg!(not(target_os = "macos")) && window.modifiers().control;
let from_pane = dragged_tab.pane.clone();
let from_ix = dragged_tab.ix;
self.workspace
.update(cx, |_, cx| {
cx.defer_in(window, move |workspace, window, cx| {
@@ -2900,8 +2897,11 @@ impl Pane {
to_pane = workspace.split_pane(to_pane, split_direction, window, cx);
}
let database_id = workspace.database_id();
let old_ix = from_pane.read(cx).index_for_item_id(item_id);
let old_len = to_pane.read(cx).items.len();
let was_pinned_in_from_pane = from_pane.read_with(cx, |pane, _| {
pane.index_for_item_id(item_id)
.is_some_and(|ix| pane.is_tab_pinned(ix))
});
let to_pane_old_length = to_pane.read(cx).items.len();
if is_clone {
let Some(item) = from_pane
.read(cx)
@@ -2919,38 +2919,36 @@ impl Pane {
} else {
move_item(&from_pane, &to_pane, item_id, ix, true, window, cx);
}
if to_pane == from_pane {
if let Some(old_index) = old_ix {
to_pane.update(cx, |this, _| {
if old_index < this.pinned_tab_count
&& (ix == this.items.len() || ix > this.pinned_tab_count)
{
this.pinned_tab_count -= 1;
} else if this.has_pinned_tabs()
&& old_index >= this.pinned_tab_count
&& ix < this.pinned_tab_count
{
this.pinned_tab_count += 1;
}
});
}
} else {
to_pane.update(cx, |this, _| {
if this.items.len() > old_len // Did we not deduplicate on drag?
&& this.has_pinned_tabs()
&& ix < this.pinned_tab_count
to_pane.update(cx, |this, _| {
if to_pane == from_pane {
let moved_right = ix > from_ix;
let ix = if moved_right { ix - 1 } else { ix };
let is_pinned_in_to_pane = this.is_tab_pinned(ix);
if (was_pinned_in_from_pane && is_pinned_in_to_pane)
|| (!was_pinned_in_from_pane && !is_pinned_in_to_pane)
{
return;
}
if is_pinned_in_to_pane {
this.pinned_tab_count += 1;
} else {
this.pinned_tab_count -= 1;
}
} else if this.items.len() >= to_pane_old_length {
let is_pinned_in_to_pane = this.is_tab_pinned(ix);
let item_created_pane = to_pane_old_length == 0;
let is_first_position = ix == 0;
let was_dropped_at_beginning = item_created_pane || is_first_position;
let should_remain_pinned = is_pinned_in_to_pane
|| (was_pinned_in_from_pane && was_dropped_at_beginning);
if should_remain_pinned {
this.pinned_tab_count += 1;
}
});
from_pane.update(cx, |this, _| {
if let Some(index) = old_ix {
if this.pinned_tab_count > index {
this.pinned_tab_count -= 1;
}
}
})
}
}
});
});
})
.log_err();
@@ -4325,6 +4323,725 @@ mod tests {
assert_item_labels(&pane, ["C", "A", "B*"], cx);
}
#[gpui::test]
async fn test_drag_unpinned_tab_to_split_creates_pane_with_unpinned_tab(
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B. Pin B. Activate A
let item_a = add_labeled_item(&pane_a, "A", false, cx);
let item_b = add_labeled_item(&pane_a, "B", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.activate_item(ix, true, true, window, cx);
});
// Drag A to create new split
pane_a.update_in(cx, |pane, window, cx| {
pane.drag_split_direction = Some(SplitDirection::Right);
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// A should be moved to new pane. B should remain pinned, A should not be pinned
let (pane_a, pane_b) = workspace.read_with(cx, |workspace, _| {
let panes = workspace.panes();
(panes[0].clone(), panes[1].clone())
});
assert_item_labels(&pane_a, ["B*!"], cx);
assert_item_labels(&pane_b, ["A*"], cx);
}
#[gpui::test]
async fn test_drag_pinned_tab_to_split_creates_pane_with_pinned_tab(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B. Pin both. Activate A
let item_a = add_labeled_item(&pane_a, "A", false, cx);
let item_b = add_labeled_item(&pane_a, "B", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.activate_item(ix, true, true, window, cx);
});
assert_item_labels(&pane_a, ["A*!", "B!"], cx);
// Drag A to create new split
pane_a.update_in(cx, |pane, window, cx| {
pane.drag_split_direction = Some(SplitDirection::Right);
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// A should be moved to new pane. Both A and B should still be pinned
let (pane_a, pane_b) = workspace.read_with(cx, |workspace, _| {
let panes = workspace.panes();
(panes[0].clone(), panes[1].clone())
});
assert_item_labels(&pane_a, ["B*!"], cx);
assert_item_labels(&pane_b, ["A*!"], cx);
}
#[gpui::test]
async fn test_drag_pinned_tab_into_existing_panes_pinned_region(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A to pane A and pin
let item_a = add_labeled_item(&pane_a, "A", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A*!"], cx);
// Add B to pane B and pin
let pane_b = workspace.update_in(cx, |workspace, window, cx| {
workspace.split_pane(pane_a.clone(), SplitDirection::Right, window, cx)
});
let item_b = add_labeled_item(&pane_b, "B", false, cx);
pane_b.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_b, ["B*!"], cx);
// Move A from pane A to pane B's pinned region
pane_b.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// A should stay pinned
assert_item_labels(&pane_a, [], cx);
assert_item_labels(&pane_b, ["A*!", "B!"], cx);
}
#[gpui::test]
async fn test_drag_pinned_tab_into_existing_panes_unpinned_region(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A to pane A and pin
let item_a = add_labeled_item(&pane_a, "A", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A*!"], cx);
// Create pane B with pinned item B
let pane_b = workspace.update_in(cx, |workspace, window, cx| {
workspace.split_pane(pane_a.clone(), SplitDirection::Right, window, cx)
});
let item_b = add_labeled_item(&pane_b, "B", false, cx);
assert_item_labels(&pane_b, ["B*"], cx);
pane_b.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_b, ["B*!"], cx);
// Move A from pane A to pane B's unpinned region
pane_b.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 1, window, cx);
});
// A should become pinned
assert_item_labels(&pane_a, [], cx);
assert_item_labels(&pane_b, ["B!", "A*"], cx);
}
#[gpui::test]
async fn test_drag_pinned_tab_into_existing_panes_first_position_with_no_pinned_tabs(
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A to pane A and pin
let item_a = add_labeled_item(&pane_a, "A", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A*!"], cx);
// Add B to pane B
let pane_b = workspace.update_in(cx, |workspace, window, cx| {
workspace.split_pane(pane_a.clone(), SplitDirection::Right, window, cx)
});
add_labeled_item(&pane_b, "B", false, cx);
assert_item_labels(&pane_b, ["B*"], cx);
// Move A from pane A to position 0 in pane B, indicating it should stay pinned
pane_b.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// A should stay pinned
assert_item_labels(&pane_a, [], cx);
assert_item_labels(&pane_b, ["A*!", "B"], cx);
}
#[gpui::test]
async fn test_drag_pinned_tab_into_existing_pane_at_max_capacity_closes_unpinned_tabs(
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
set_max_tabs(cx, Some(2));
// Add A, B to pane A. Pin both
let item_a = add_labeled_item(&pane_a, "A", false, cx);
let item_b = add_labeled_item(&pane_a, "B", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A!", "B*!"], cx);
// Add C, D to pane B. Pin both
let pane_b = workspace.update_in(cx, |workspace, window, cx| {
workspace.split_pane(pane_a.clone(), SplitDirection::Right, window, cx)
});
let item_c = add_labeled_item(&pane_b, "C", false, cx);
let item_d = add_labeled_item(&pane_b, "D", false, cx);
pane_b.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_c.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
let ix = pane.index_for_item_id(item_d.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_b, ["C!", "D*!"], cx);
// Add a third unpinned item to pane B (exceeds max tabs), but is allowed,
// as we allow 1 tab over max if the others are pinned or dirty
add_labeled_item(&pane_b, "E", false, cx);
assert_item_labels(&pane_b, ["C!", "D!", "E*"], cx);
// Drag pinned A from pane A to position 0 in pane B
pane_b.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// E (unpinned) should be closed, leaving 3 pinned items
assert_item_labels(&pane_a, ["B*!"], cx);
assert_item_labels(&pane_b, ["A*!", "C!", "D!"], cx);
}
#[gpui::test]
async fn test_drag_last_pinned_tab_to_same_position_stays_pinned(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A to pane A and pin it
let item_a = add_labeled_item(&pane_a, "A", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A*!"], cx);
// Drag pinned A to position 1 (directly to the right) in the same pane
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 1, window, cx);
});
// A should still be pinned and active
assert_item_labels(&pane_a, ["A*!"], cx);
}
#[gpui::test]
async fn test_drag_pinned_tab_beyond_last_pinned_tab_in_same_pane_stays_pinned(
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B to pane A and pin both
let item_a = add_labeled_item(&pane_a, "A", false, cx);
let item_b = add_labeled_item(&pane_a, "B", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A!", "B*!"], cx);
// Drag pinned A right of B in the same pane
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 2, window, cx);
});
// A stays pinned
assert_item_labels(&pane_a, ["B!", "A*!"], cx);
}
#[gpui::test]
async fn test_drag_pinned_tab_beyond_unpinned_tab_in_same_pane_becomes_unpinned(
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B to pane A and pin A
let item_a = add_labeled_item(&pane_a, "A", false, cx);
add_labeled_item(&pane_a, "B", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A!", "B*"], cx);
// Drag pinned A right of B in the same pane
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 2, window, cx);
});
// A becomes unpinned
assert_item_labels(&pane_a, ["B", "A*"], cx);
}
#[gpui::test]
async fn test_drag_unpinned_tab_in_front_of_pinned_tab_in_same_pane_becomes_pinned(
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B to pane A and pin A
let item_a = add_labeled_item(&pane_a, "A", false, cx);
let item_b = add_labeled_item(&pane_a, "B", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A!", "B*"], cx);
// Drag pinned B left of A in the same pane
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_b.boxed_clone(),
ix: 1,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// A becomes unpinned
assert_item_labels(&pane_a, ["B*!", "A!"], cx);
}
#[gpui::test]
async fn test_drag_unpinned_tab_to_the_pinned_region_stays_pinned(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B, C to pane A and pin A
let item_a = add_labeled_item(&pane_a, "A", false, cx);
add_labeled_item(&pane_a, "B", false, cx);
let item_c = add_labeled_item(&pane_a, "C", false, cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A!", "B", "C*"], cx);
// Drag pinned C left of B in the same pane
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_c.boxed_clone(),
ix: 2,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 1, window, cx);
});
// A stays pinned, B and C remain unpinned
assert_item_labels(&pane_a, ["A!", "C*", "B"], cx);
}
#[gpui::test]
async fn test_drag_unpinned_tab_into_existing_panes_pinned_region(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add unpinned item A to pane A
let item_a = add_labeled_item(&pane_a, "A", false, cx);
assert_item_labels(&pane_a, ["A*"], cx);
// Create pane B with pinned item B
let pane_b = workspace.update_in(cx, |workspace, window, cx| {
workspace.split_pane(pane_a.clone(), SplitDirection::Right, window, cx)
});
let item_b = add_labeled_item(&pane_b, "B", false, cx);
pane_b.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_b, ["B*!"], cx);
// Move A from pane A to pane B's pinned region
pane_b.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// A should become pinned since it was dropped in the pinned region
assert_item_labels(&pane_a, [], cx);
assert_item_labels(&pane_b, ["A*!", "B!"], cx);
}
#[gpui::test]
async fn test_drag_unpinned_tab_into_existing_panes_unpinned_region(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add unpinned item A to pane A
let item_a = add_labeled_item(&pane_a, "A", false, cx);
assert_item_labels(&pane_a, ["A*"], cx);
// Create pane B with one pinned item B
let pane_b = workspace.update_in(cx, |workspace, window, cx| {
workspace.split_pane(pane_a.clone(), SplitDirection::Right, window, cx)
});
let item_b = add_labeled_item(&pane_b, "B", false, cx);
pane_b.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_b, ["B*!"], cx);
// Move A from pane A to pane B's unpinned region
pane_b.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 1, window, cx);
});
// A should remain unpinned since it was dropped outside the pinned region
assert_item_labels(&pane_a, [], cx);
assert_item_labels(&pane_b, ["B!", "A*"], cx);
}
#[gpui::test]
async fn test_drag_pinned_tab_throughout_entire_range_of_pinned_tabs_both_directions(
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B, C and pin all
let item_a = add_labeled_item(&pane_a, "A", false, cx);
let item_b = add_labeled_item(&pane_a, "B", false, cx);
let item_c = add_labeled_item(&pane_a, "C", false, cx);
assert_item_labels(&pane_a, ["A", "B", "C*"], cx);
pane_a.update_in(cx, |pane, window, cx| {
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
let ix = pane.index_for_item_id(item_b.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
let ix = pane.index_for_item_id(item_c.item_id()).unwrap();
pane.pin_tab_at(ix, window, cx);
});
assert_item_labels(&pane_a, ["A!", "B!", "C*!"], cx);
// Move A to right of B
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 1, window, cx);
});
// A should be after B and all are pinned
assert_item_labels(&pane_a, ["B!", "A*!", "C!"], cx);
// Move A to right of C
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 1,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 2, window, cx);
});
// A should be after C and all are pinned
assert_item_labels(&pane_a, ["B!", "C!", "A*!"], cx);
// Move A to left of C
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 2,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 1, window, cx);
});
// A should be before C and all are pinned
assert_item_labels(&pane_a, ["B!", "A*!", "C!"], cx);
// Move A to left of B
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 1,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// A should be before B and all are pinned
assert_item_labels(&pane_a, ["A*!", "B!", "C!"], cx);
}
#[gpui::test]
async fn test_drag_first_tab_to_last_position(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B, C
let item_a = add_labeled_item(&pane_a, "A", false, cx);
add_labeled_item(&pane_a, "B", false, cx);
add_labeled_item(&pane_a, "C", false, cx);
assert_item_labels(&pane_a, ["A", "B", "C*"], cx);
// Move A to the end
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_a.boxed_clone(),
ix: 0,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 2, window, cx);
});
// A should be at the end
assert_item_labels(&pane_a, ["B", "C", "A*"], cx);
}
#[gpui::test]
async fn test_drag_last_tab_to_first_position(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, None, cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// Add A, B, C
add_labeled_item(&pane_a, "A", false, cx);
add_labeled_item(&pane_a, "B", false, cx);
let item_c = add_labeled_item(&pane_a, "C", false, cx);
assert_item_labels(&pane_a, ["A", "B", "C*"], cx);
// Move C to the beginning
pane_a.update_in(cx, |pane, window, cx| {
let dragged_tab = DraggedTab {
pane: pane_a.clone(),
item: item_c.boxed_clone(),
ix: 2,
detail: 0,
is_active: true,
};
pane.handle_tab_drop(&dragged_tab, 0, window, cx);
});
// C should be at the beginning
assert_item_labels(&pane_a, ["C*", "A", "B"], cx);
}
#[gpui::test]
async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
init_test(cx);

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
version = "0.190.0"
version = "0.190.3"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
dev
preview

View File

@@ -5,6 +5,7 @@ components = [ "rustfmt", "clippy" ]
targets = [
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-unknown-freebsd",
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
"wasm32-wasip2", # extensions

160
script/bundle-freebsd Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
set -euxo pipefail
source script/lib/blob-store.sh
# Function for displaying help info
help_info() {
echo "
Usage: ${0##*/} [options]
Build a release .tar.gz for FreeBSD.
Options:
-h Display this help and exit.
"
}
while getopts 'h' flag; do
case "${flag}" in
h)
help_info
exit 0
;;
esac
done
export ZED_BUNDLE=true
channel=$(<crates/zed/RELEASE_CHANNEL)
target_dir="${CARGO_TARGET_DIR:-target}"
version="$(script/get-crate-version zed)"
# Set RELEASE_VERSION so it's compiled into GPUI and it knows about the version.
export RELEASE_VERSION="${version}"
commit=$(git rev-parse HEAD | cut -c 1-7)
version_info=$(rustc --version --verbose)
host_line=$(echo "$version_info" | grep host)
target_triple=${host_line#*: }
remote_server_triple=${REMOTE_SERVER_TARGET:-"${target_triple}"}
# musl_triple=${target_triple%-gnu}-musl
# rustup_installed=false
# if command -v rustup >/dev/null 2>&1; then
# rustup_installed=true
# fi
# Generate the licenses first, so they can be baked into the binaries
# script/generate-licenses
# if "$rustup_installed"; then
# rustup target add "$remote_server_triple"
# fi
# export CC=$(which clang)
# Build binary in release mode
export RUSTFLAGS="${RUSTFLAGS:-} -C link-args=-Wl,--disable-new-dtags,-rpath,\$ORIGIN/../lib"
# cargo build --release --target "${target_triple}" --package zed --package cli
# Build remote_server in separate invocation to prevent feature unification from other crates
# from influencing dynamic libraries required by it.
# if [[ "$remote_server_triple" == "$musl_triple" ]]; then
# export RUSTFLAGS="${RUSTFLAGS:-} -C target-feature=+crt-static"
# fi
cargo build --release --target "${remote_server_triple}" --package remote_server
# Strip debug symbols and save them for upload to DigitalOcean
# objcopy --only-keep-debug "${target_dir}/${target_triple}/release/zed" "${target_dir}/${target_triple}/release/zed.dbg"
# objcopy --only-keep-debug "${target_dir}/${remote_server_triple}/release/remote_server" "${target_dir}/${remote_server_triple}/release/remote_server.dbg"
# objcopy --strip-debug "${target_dir}/${target_triple}/release/zed"
# objcopy --strip-debug "${target_dir}/${target_triple}/release/cli"
# objcopy --strip-debug "${target_dir}/${remote_server_triple}/release/remote_server"
# gzip -f "${target_dir}/${target_triple}/release/zed.dbg"
# gzip -f "${target_dir}/${remote_server_triple}/release/remote_server.dbg"
# if [[ -n "${DIGITALOCEAN_SPACES_SECRET_KEY:-}" && -n "${DIGITALOCEAN_SPACES_ACCESS_KEY:-}" ]]; then
# upload_to_blob_store_public \
# "zed-debug-symbols" \
# "${target_dir}/${target_triple}/release/zed.dbg.gz" \
# "$channel/zed-$version-${target_triple}.dbg.gz"
# upload_to_blob_store_public \
# "zed-debug-symbols" \
# "${target_dir}/${remote_server_triple}/release/remote_server.dbg.gz" \
# "$channel/remote_server-$version-${remote_server_triple}.dbg.gz"
# fi
# Ensure that remote_server does not depend on libssl nor libcrypto, as we got rid of these deps.
if ldd "${target_dir}/${remote_server_triple}/release/remote_server" | grep -q 'libcrypto\|libssl'; then
echo "Error: remote_server still depends on libssl or libcrypto" && exit 1
fi
suffix=""
if [ "$channel" != "stable" ]; then
suffix="-$channel"
fi
# Move everything that should end up in the final package
# into a temp directory.
# temp_dir=$(mktemp -d)
# zed_dir="${temp_dir}/zed$suffix.app"
# Binary
# mkdir -p "${zed_dir}/bin" "${zed_dir}/libexec"
# cp "${target_dir}/${target_triple}/release/zed" "${zed_dir}/libexec/zed-editor"
# cp "${target_dir}/${target_triple}/release/cli" "${zed_dir}/bin/zed"
# Libs
# find_libs() {
# ldd ${target_dir}/${target_triple}/release/zed |
# cut -d' ' -f3 |
# grep -v '\<\(libstdc++.so\|libc.so\|libgcc_s.so\|libm.so\|libpthread.so\|libdl.so\|libasound.so\)'
# }
# mkdir -p "${zed_dir}/lib"
# rm -rf "${zed_dir}/lib/*"
# cp $(find_libs) "${zed_dir}/lib"
# Icons
# mkdir -p "${zed_dir}/share/icons/hicolor/512x512/apps"
# cp "crates/zed/resources/app-icon$suffix.png" "${zed_dir}/share/icons/hicolor/512x512/apps/zed.png"
# mkdir -p "${zed_dir}/share/icons/hicolor/1024x1024/apps"
# cp "crates/zed/resources/app-icon$suffix@2x.png" "${zed_dir}/share/icons/hicolor/1024x1024/apps/zed.png"
# .desktop
# export DO_STARTUP_NOTIFY="true"
# export APP_CLI="zed"
# export APP_ICON="zed"
# export APP_ARGS="%U"
# if [[ "$channel" == "preview" ]]; then
# export APP_NAME="Zed Preview"
# elif [[ "$channel" == "nightly" ]]; then
# export APP_NAME="Zed Nightly"
# elif [[ "$channel" == "dev" ]]; then
# export APP_NAME="Zed Devel"
# else
# export APP_NAME="Zed"
# fi
# mkdir -p "${zed_dir}/share/applications"
# envsubst <"crates/zed/resources/zed.desktop.in" >"${zed_dir}/share/applications/zed$suffix.desktop"
# Copy generated licenses so they'll end up in archive too
# cp "assets/licenses.md" "${zed_dir}/licenses.md"
# Create archive out of everything that's in the temp directory
arch=$(uname -m)
# target="freebsd-${arch}"
# if [[ "$channel" == "dev" ]]; then
# archive="zed-${commit}-${target}.tar.gz"
# else
# archive="zed-${target}.tar.gz"
# fi
# rm -rf "${archive}"
# remove_match="zed(-[a-zA-Z0-9]+)?-linux-$(uname -m)\.tar\.gz"
# ls "${target_dir}/release" | grep -E ${remove_match} | xargs -d "\n" -I {} rm -f "${target_dir}/release/{}" || true
# tar -czvf "${target_dir}/release/$archive" -C ${temp_dir} "zed$suffix.app"
gzip -f --stdout --best "${target_dir}/${remote_server_triple}/release/remote_server" \
> "${target_dir}/zed-remote-server-freebsd-x86_64.gz"