Compare commits

...

19 Commits

Author SHA1 Message Date
Bennet Bo Fenner
6f8d3e64a5 zed 0.132.4 2024-04-29 18:06:46 +02:00
gcp-cherry-pick-bot[bot]
ed6c68add5 Fix relative line numbers (cherry-pick #11161) (#11169)
Cherry-picked Fix relative line numbers (#11161)

Closes #11105

Release Notes:

- Fixed rendering of relative line numbers in editor
([#11105](https://github.com/zed-industries/zed/issues/11105)).

Co-authored-by: Thorsten <thorsten@zed.dev>

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
Co-authored-by: Thorsten <thorsten@zed.dev>
2024-04-29 18:05:38 +02:00
Max Brunsfeld
3518c939cb Don't panic when a tree-sitter parse fails (#11076)
Fixes
https://zed-industries.slack.com/archives/C04S6T1T7TQ/p1714162894982749

Release Notes:
* Fixed a crash that could happen if an error occurred in a parser
provided by an extension.

Co-authored-by: Conrad <conrad@zed.dev>
2024-04-26 17:08:07 -06:00
Zed Bot
e4316e5430 Bump to 0.132.3 for @ConradIrwin 2024-04-26 14:12:18 -07:00
gcp-cherry-pick-bot[bot]
7e626ba9a0 Fix panic in Diagnostics (cherry-pick #11066) (#11069)
Cherry-picked Fix panic in Diagnostics (#11066)

cc @maxbrunsfeld

Release Notes:

- Fixed a panic in populating diagnostics

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-26 14:18:00 -06:00
gcp-cherry-pick-bot[bot]
a8a402e5ac Fix panic in vim search (cherry-pick #11022) (#11031)
Cherry-picked Fix panic in vim search (#11022)

Release Notes:

- vim: Fixed a panic when searching

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-25 20:45:16 -06:00
Joseph T. Lyons
6d6d6c69db v0.132.x stable 2024-04-24 12:46:14 -04:00
Kirill Bulatov
81cb4ee157 Properly extract package name out of cargo pkgid (#10929)
Fixes https://github.com/zed-industries/zed/issues/10925

Uses correct package name to generate Rust `cargo` tasks.
Also deduplicates lines in task modal item tooltips.

Release Notes:

- Fixed Rust tasks using incorrect package name
([10925](https://github.com/zed-industries/zed/issues/10925))
2024-04-24 14:13:23 +03:00
Marshall Bowers
4db7841fb1 Ensure target directory exists before drafting release notes 2024-04-22 16:04:38 -04:00
Zed Bot
3337e5f2f7 Bump to 0.132.2 for @maxdeviant 2024-04-22 12:30:03 -07:00
gcp-cherry-pick-bot[bot]
58dd76efaf Fix reading workspace-level LSP settings in extensions (cherry-pick #10859) (#10860)
Cherry-picked Fix reading workspace-level LSP settings in extensions
(#10859)

This PR fixes an issue where workspace-level LSP settings could be not
read using `LspSettings::for_worktree` in extensions.

We we erroneously always reading the global settings instead of
respecting the passed-in location.

Release Notes:

- Fixed a bug where workspace LSP settings could not be read by
extensions.

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-22 14:55:30 -04:00
Kirill Bulatov
bfc2057376 Filter out other languages' tasks from the task modal (#10839)
Release Notes:

- Fixed tasks modal showing history from languages, not matching the
currently active buffer's one
2024-04-22 12:36:50 +03:00
Kirill Bulatov
22d07862fd Properly pass nested script arguments for tasks (#10776)
Closes
https://github.com/zed-industries/zed/discussions/10732#discussion-6524347
introduced by https://github.com/zed-industries/zed/pull/10548 while
keeping both Python and Bash run selection capabilities.

Also replaced redundant `SpawnTask` struct with `SpawnInTerminal` that
has identical fields.

Release Notes:

- Fixed incorrect task escaping of nested script arguments

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
2024-04-19 17:27:08 +03:00
Kirill Bulatov
056739c1a2 Always provide default task context (#10764)
Based on
https://github.com/zed-industries/zed/issues/8324?notification_referrer_id=NT_kwDOACkO1bI5NTk0NjM0NzkyOjI2OTA3NzM&notifications_query=repo%3Azed-industries%2Fzed+is%3Aunread#issuecomment-2065551553

Release Notes:

- Fixed certain files' task modal not showing context-based tasks
2024-04-19 10:58:38 +03:00
Conrad Irwin
63f208b822 typo 2024-04-17 22:41:13 -06:00
Conrad Irwin
877666bd03 Have the CI server draft the release notes (#10700)
While I don't expect these to be useful for our weekly minor releases, I
hope that this will save a step for people doing mid-week patches.

Release Notes:

- N/A
2024-04-17 22:04:46 -06:00
Zed Bot
cabfb69329 Bump to 0.132.1 for @ConradIrwin 2024-04-17 15:12:25 -07:00
gcp-cherry-pick-bot[bot]
d97960fd8c Attempt to fix segfault in window drop (cherry-pick #10690) (#10701)
Cherry-picked Attempt to fix segfault in window drop (#10690)

By default NSWindow's release themselves when closed, which doesn't
interact well with rust's lifetime system.

Disable that behaviour, and explicitly release the NSWindow when the
window handle is dropped.

Release Notes:

- Fixed a (rare) panic when closing a window.

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-17 15:51:23 -06:00
Marshall Bowers
68e0bea835 v0.132.x preview 2024-04-17 13:05:02 -04:00
27 changed files with 713 additions and 229 deletions

View File

@@ -205,6 +205,8 @@ jobs:
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
mkdir -p target/
script/draft-release-notes "$version" "$channel" > target/release-notes.md
- name: Generate license file
run: script/generate-licenses
@@ -248,7 +250,7 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
body: ""
body_path: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

98
Cargo.lock generated
View File

@@ -643,7 +643,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -710,7 +710,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -741,7 +741,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -1385,7 +1385,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.48",
"syn 2.0.59",
"which 4.4.2",
]
@@ -1468,7 +1468,7 @@ source = "git+https://github.com/kvark/blade?rev=810ec594358aafea29a4a3d8ab601d2
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -1634,7 +1634,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -2019,7 +2019,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -2958,7 +2958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
dependencies = [
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -3441,7 +3441,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -3942,7 +3942,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -4185,7 +4185,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -5046,7 +5046,7 @@ checksum = "ce243b1bfa62ffc028f1cc3b6034ec63d649f3031bc8a4fbbb004e1ac17d1f68"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -5667,7 +5667,7 @@ checksum = "ba125974b109d512fccbc6c0244e7580143e460895dfd6ea7f8bbb692fd94396"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -5902,9 +5902,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.6.3"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "memfd"
@@ -6624,7 +6624,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -6700,7 +6700,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -6780,7 +6780,7 @@ checksum = "e8890702dbec0bad9116041ae586f84805b13eecd1d8b1df27c29998a9969d6d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -6958,7 +6958,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -7009,7 +7009,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -7233,7 +7233,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
dependencies = [
"proc-macro2",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -7290,9 +7290,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [
"unicode-ident",
]
@@ -7313,7 +7313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -8136,7 +8136,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.48",
"syn 2.0.59",
"walkdir",
]
@@ -8410,7 +8410,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -8451,7 +8451,7 @@ dependencies = [
"proc-macro2",
"quote",
"sea-bae",
"syn 2.0.48",
"syn 2.0.59",
"unicode-ident",
]
@@ -8635,7 +8635,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -8700,7 +8700,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -9465,7 +9465,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -9594,9 +9594,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.48"
version = "2.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
dependencies = [
"proc-macro2",
"quote",
@@ -9736,7 +9736,6 @@ dependencies = [
"file_icons",
"fuzzy",
"gpui",
"itertools 0.11.0",
"language",
"picker",
"project",
@@ -9841,7 +9840,6 @@ dependencies = [
"serde_json",
"settings",
"shellexpand",
"shlex",
"smol",
"task",
"terminal",
@@ -9963,7 +9961,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -10142,7 +10140,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -10367,7 +10365,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -10435,7 +10433,7 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.20.100"
source = "git+https://github.com/tree-sitter/tree-sitter?rev=7f21c3b98c0749ac192da67a0d65dfe3eabc4a63#7f21c3b98c0749ac192da67a0d65dfe3eabc4a63"
source = "git+https://github.com/tree-sitter/tree-sitter?rev=7b4894ba2ae81b988846676f54c0988d4027ef4f#7b4894ba2ae81b988846676f54c0988d4027ef4f"
dependencies = [
"cc",
"regex",
@@ -11134,7 +11132,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
"wasm-bindgen-shared",
]
@@ -11168,7 +11166,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -11305,7 +11303,7 @@ dependencies = [
"anyhow",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
"wasmtime-component-util",
"wasmtime-wit-bindgen",
"wit-parser",
@@ -11466,7 +11464,7 @@ checksum = "6d6d967f01032da7d4c6303da32f6a00d5efe1bac124b156e7342d8ace6ffdfc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -11746,7 +11744,7 @@ dependencies = [
"proc-macro2",
"quote",
"shellexpand",
"syn 2.0.48",
"syn 2.0.59",
"witx",
]
@@ -11758,7 +11756,7 @@ checksum = "512d816dbcd0113103b2eb2402ec9018e7f0755202a5b3e67db726f229d8dcae"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
"wiggle-generate",
]
@@ -11876,7 +11874,7 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -11887,7 +11885,7 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -12204,7 +12202,7 @@ dependencies = [
"anyhow",
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
"wit-bindgen-core",
"wit-bindgen-rust",
]
@@ -12522,7 +12520,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.132.0"
version = "0.132.4"
dependencies = [
"activity_indicator",
"anyhow",
@@ -12813,7 +12811,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]
@@ -12833,7 +12831,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"syn 2.0.59",
]
[[package]]

View File

@@ -298,7 +298,6 @@ serde_json_lenient = { version = "0.1", features = [
] }
serde_repr = "0.1"
sha2 = "0.10"
shlex = "1.3"
shellexpand = "2.1.0"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
@@ -391,7 +390,7 @@ features = [
]
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7f21c3b98c0749ac192da67a0d65dfe3eabc4a63" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7b4894ba2ae81b988846676f54c0988d4027ef4f" }
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }

View File

@@ -887,22 +887,27 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
})
}
fn compare_diagnostics<L: language::ToOffset, R: language::ToOffset>(
lhs: &DiagnosticEntry<L>,
rhs: &DiagnosticEntry<R>,
fn compare_diagnostics(
old: &DiagnosticEntry<language::Anchor>,
new: &DiagnosticEntry<language::Anchor>,
snapshot: &language::BufferSnapshot,
) -> Ordering {
lhs.range
use language::ToOffset;
// The old diagnostics may point to a previously open Buffer for this file.
if !old.range.start.is_valid(snapshot) {
return Ordering::Greater;
}
old.range
.start
.to_offset(snapshot)
.cmp(&rhs.range.start.to_offset(snapshot))
.cmp(&new.range.start.to_offset(snapshot))
.then_with(|| {
lhs.range
old.range
.end
.to_offset(snapshot)
.cmp(&rhs.range.end.to_offset(snapshot))
.cmp(&new.range.end.to_offset(snapshot))
})
.then_with(|| lhs.diagnostic.message.cmp(&rhs.diagnostic.message))
.then_with(|| old.diagnostic.message.cmp(&new.diagnostic.message))
}
#[cfg(test)]

View File

@@ -1261,7 +1261,7 @@ impl EditorElement {
fn calculate_relative_line_numbers(
&self,
buffer_rows: Vec<Option<u32>>,
snapshot: &EditorSnapshot,
rows: &Range<u32>,
relative_to: Option<u32>,
) -> HashMap<u32, u32> {
@@ -1271,6 +1271,12 @@ impl EditorElement {
};
let start = rows.start.min(relative_to);
let end = rows.end.max(relative_to);
let buffer_rows = snapshot
.buffer_rows(start)
.take(1 + (end - start) as usize)
.collect::<Vec<_>>();
let head_idx = relative_to - start;
let mut delta = 1;
@@ -1345,9 +1351,7 @@ impl EditorElement {
None
};
let buffer_rows = buffer_rows.collect::<Vec<_>>();
let relative_rows =
self.calculate_relative_line_numbers(buffer_rows.clone(), &rows, relative_to);
let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
for (ix, row) in buffer_rows.into_iter().enumerate() {
let display_row = rows.start + ix as u32;
@@ -4551,8 +4555,12 @@ mod tests {
.unwrap();
assert_eq!(layouts.len(), 6);
let relative_rows =
element.calculate_relative_line_numbers((0..6).map(Some).collect(), &(0..6), Some(3));
let relative_rows = window
.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
})
.unwrap();
assert_eq!(relative_rows[&0], 3);
assert_eq!(relative_rows[&1], 2);
assert_eq!(relative_rows[&2], 1);
@@ -4561,16 +4569,24 @@ mod tests {
assert_eq!(relative_rows[&5], 2);
// works if cursor is before screen
let relative_rows =
element.calculate_relative_line_numbers((0..6).map(Some).collect(), &(3..6), Some(1));
let relative_rows = window
.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
})
.unwrap();
assert_eq!(relative_rows.len(), 3);
assert_eq!(relative_rows[&3], 2);
assert_eq!(relative_rows[&4], 3);
assert_eq!(relative_rows[&5], 4);
// works if cursor is after screen
let relative_rows =
element.calculate_relative_line_numbers((0..6).map(Some).collect(), &(0..3), Some(6));
let relative_rows = window
.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
})
.unwrap();
assert_eq!(relative_rows.len(), 3);
assert_eq!(relative_rows[&0], 5);
assert_eq!(relative_rows[&1], 4);

View File

@@ -227,7 +227,7 @@ impl ExtensionImports for WasmState {
"lsp" => {
let settings = key
.and_then(|key| {
ProjectSettings::get_global(cx)
ProjectSettings::get(location, cx)
.lsp
.get(&Arc::<str>::from(key))
})

View File

@@ -337,7 +337,6 @@ struct MacWindowState {
handle: AnyWindowHandle,
executor: ForegroundExecutor,
native_window: id,
native_window_was_closed: bool,
native_view: NonNull<Object>,
display_link: Option<DisplayLink>,
renderer: renderer::Renderer,
@@ -605,6 +604,10 @@ impl MacWindow {
registerForDraggedTypes:
NSArray::arrayWithObject(nil, NSFilenamesPboardType)
];
let () = msg_send![
native_window,
setReleasedWhenClosed: NO
];
let native_view: id = msg_send![VIEW_CLASS, alloc];
let native_view = NSView::init(native_view);
@@ -622,7 +625,6 @@ impl MacWindow {
handle,
executor,
native_window,
native_window_was_closed: false,
native_view: NonNull::new_unchecked(native_view),
display_link: None,
renderer: renderer::new_renderer(
@@ -770,19 +772,17 @@ impl Drop for MacWindow {
this.renderer.destroy();
let window = this.native_window;
this.display_link.take();
if !this.native_window_was_closed {
unsafe {
this.native_window.setDelegate_(nil);
}
this.executor
.spawn(async move {
unsafe {
window.close();
}
})
.detach();
unsafe {
this.native_window.setDelegate_(nil);
}
this.executor
.spawn(async move {
unsafe {
window.close();
window.autorelease();
}
})
.detach();
}
}
@@ -1592,7 +1592,6 @@ extern "C" fn close_window(this: &Object, _: Sel) {
let close_callback = {
let window_state = get_window_state(this);
let mut lock = window_state.as_ref().lock();
lock.native_window_was_closed = true;
lock.close_callback.take()
};

View File

@@ -606,13 +606,21 @@ impl SyntaxSnapshot {
LogIncludedRanges(&included_ranges),
);
tree = parse_text(
let result = parse_text(
grammar,
text.as_rope(),
step_start_byte,
included_ranges,
Some(old_tree.clone()),
);
match result {
Ok(t) => tree = t,
Err(e) => {
log::error!("error parsing text: {:?}", e);
continue;
}
};
changed_ranges = join_ranges(
invalidated_ranges
.iter()
@@ -651,13 +659,20 @@ impl SyntaxSnapshot {
LogIncludedRanges(&included_ranges),
);
tree = parse_text(
let result = parse_text(
grammar,
text.as_rope(),
step_start_byte,
included_ranges,
None,
);
match result {
Ok(t) => tree = t,
Err(e) => {
log::error!("error parsing text: {:?}", e);
continue;
}
};
changed_ranges = vec![step_start_byte..step_end_byte];
}
@@ -1161,16 +1176,12 @@ fn parse_text(
start_byte: usize,
ranges: Vec<tree_sitter::Range>,
old_tree: Option<Tree>,
) -> Tree {
) -> anyhow::Result<Tree> {
PARSER.with(|parser| {
let mut parser = parser.borrow_mut();
let mut chunks = text.chunks_in_range(start_byte..text.len());
parser
.set_included_ranges(&ranges)
.expect("overlapping ranges");
parser
.set_language(&grammar.ts_language)
.expect("incompatible grammar");
parser.set_included_ranges(&ranges)?;
parser.set_language(&grammar.ts_language)?;
parser
.parse_with(
&mut move |offset, _| {
@@ -1179,7 +1190,7 @@ fn parse_text(
},
old_tree.as_ref(),
)
.expect("invalid language")
.ok_or_else(|| anyhow::anyhow!("failed to parse"))
})
}

View File

@@ -15,9 +15,9 @@ pub trait ContextProvider: Send + Sync {
/// Builds a specific context to be placed on top of the basic one (replacing all conflicting entries) and to be used for task resolving later.
fn build_context(
&self,
_: Option<&Path>,
_: &Location,
_: &mut AppContext,
_worktree_abs_path: Option<&Path>,
_location: &Location,
_cx: &mut AppContext,
) -> Result<TaskVariables> {
Ok(TaskVariables::default())
}

View File

@@ -85,13 +85,7 @@ pub fn init(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new(language::BasicContextProvider)),
))
},
move || Ok((config.clone(), load_queries($name), None)),
);
};
($name:literal, $adapters:expr) => {
@@ -105,13 +99,7 @@ pub fn init(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new(language::BasicContextProvider)),
))
},
move || Ok((config.clone(), load_queries($name), None)),
);
};
($name:literal, $adapters:expr, $context_provider:expr) => {

View File

@@ -186,13 +186,7 @@ pub(super) fn python_task_context() -> ContextProviderWithTasks {
TaskTemplate {
label: "execute selection".to_owned(),
command: "python3".to_owned(),
args: vec![
"-c".to_owned(),
format!(
"exec(r'''{}''')",
VariableName::SelectedText.template_value()
),
],
args: vec!["-c".to_owned(), VariableName::SelectedText.template_value()],
ignore_previously_resolved: true,
..TaskTemplate::default()
},

View File

@@ -421,13 +421,6 @@ impl ContextProvider for RustContextProvider {
}
fn human_readable_package_name(package_directory: &Path) -> Option<String> {
fn split_off_suffix(input: &str, suffix_start: char) -> &str {
match input.rsplit_once(suffix_start) {
Some((without_suffix, _)) => without_suffix,
None => input,
}
}
let pkgid = String::from_utf8(
std::process::Command::new("cargo")
.current_dir(package_directory)
@@ -437,19 +430,40 @@ fn human_readable_package_name(package_directory: &Path) -> Option<String> {
.stdout,
)
.ok()?;
// For providing local `cargo check -p $pkgid` task, we do not need most of the information we have returned.
// Output example in the root of Zed project:
// ```bash
// cargo pkgid zed
// path+file:///absolute/path/to/project/zed/crates/zed#0.131.0
// ```
// Extrarct the package name from the output according to the spec:
// https://doc.rust-lang.org/cargo/reference/pkgid-spec.html#specification-grammar
let mut package_name = pkgid.trim();
package_name = split_off_suffix(package_name, '#');
package_name = split_off_suffix(package_name, '?');
let (_, package_name) = package_name.rsplit_once('/')?;
Some(package_name.to_string())
Some(package_name_from_pkgid(&pkgid)?.to_owned())
}
// For providing local `cargo check -p $pkgid` task, we do not need most of the information we have returned.
// Output example in the root of Zed project:
// ```bash
// cargo pkgid zed
// path+file:///absolute/path/to/project/zed/crates/zed#0.131.0
// ```
// Another variant, if a project has a custom package name or hyphen in the name:
// ```
// path+file:///absolute/path/to/project/custom-package#my-custom-package@0.1.0
// ```
//
// Extracts the package name from the output according to the spec:
// https://doc.rust-lang.org/cargo/reference/pkgid-spec.html#specification-grammar
fn package_name_from_pkgid(pkgid: &str) -> Option<&str> {
fn split_off_suffix(input: &str, suffix_start: char) -> &str {
match input.rsplit_once(suffix_start) {
Some((without_suffix, _)) => without_suffix,
None => input,
}
}
let (version_prefix, version_suffix) = pkgid.trim().rsplit_once('#')?;
let package_name = match version_suffix.rsplit_once('@') {
Some((custom_package_name, _version)) => custom_package_name,
None => {
let host_and_path = split_off_suffix(version_prefix, '?');
let (_, package_name) = host_and_path.rsplit_once('/')?;
package_name
}
};
Some(package_name)
}
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
@@ -750,4 +764,20 @@ mod tests {
buffer
});
}
#[test]
fn test_package_name_from_pkgid() {
for (input, expected) in [
(
"path+file:///absolute/path/to/project/zed/crates/zed#0.131.0",
"zed",
),
(
"path+file:///absolute/path/to/project/custom-package#my-custom-package@0.1.0",
"my-custom-package",
),
] {
assert_eq!(package_name_from_pkgid(input), Some(expected));
}
}
}

View File

@@ -219,6 +219,13 @@ impl Inventory {
.iter()
.rev()
.filter(|(_, task)| !task.original_task().ignore_previously_resolved)
.filter(|(task_kind, _)| {
if matches!(task_kind, TaskSourceKind::Language { .. }) {
Some(task_kind) == task_source_kind.as_ref()
} else {
true
}
})
.fold(
HashMap::default(),
|mut tasks, (task_source_kind, resolved_task)| {

View File

@@ -4,9 +4,10 @@ use gpui::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
use settings::Settings;
use smol::channel::bounded;
use std::path::{Path, PathBuf};
use task::SpawnInTerminal;
use terminal::{
terminal_settings::{self, Shell, TerminalSettings, VenvSettingsContent},
SpawnTask, TaskState, TaskStatus, Terminal, TerminalBuilder,
TaskState, TaskStatus, Terminal, TerminalBuilder,
};
use util::ResultExt;
@@ -21,7 +22,7 @@ impl Project {
pub fn create_terminal(
&mut self,
working_directory: Option<PathBuf>,
spawn_task: Option<SpawnTask>,
spawn_task: Option<SpawnInTerminal>,
window: AnyWindowHandle,
cx: &mut ModelContext<Self>,
) -> anyhow::Result<Model<Terminal>> {
@@ -55,14 +56,7 @@ impl Project {
id: spawn_task.id,
full_label: spawn_task.full_label,
label: spawn_task.label,
command_label: spawn_task.args.iter().fold(
spawn_task.command.clone(),
|mut command_label, new_arg| {
command_label.push(' ');
command_label.push_str(new_arg);
command_label
},
),
command_label: spawn_task.command_label,
status: TaskStatus::Running,
completion_rx,
}),

View File

@@ -31,8 +31,11 @@ pub struct SpawnInTerminal {
pub label: String,
/// Executable command to spawn.
pub command: String,
/// Arguments to the command.
/// Arguments to the command, potentially unsubstituted,
/// to let the shell that spawns the command to do the substitution, if needed.
pub args: Vec<String>,
/// A human-readable label, containing command and all of its arguments, joined and substituted.
pub command_label: String,
/// Current working directory to spawn the command into.
pub cwd: Option<PathBuf>,
/// Env overrides for the command, will be appended to the terminal's environment from the settings.
@@ -75,6 +78,14 @@ impl ResolvedTask {
pub fn substituted_variables(&self) -> &HashSet<VariableName> {
&self.substituted_variables
}
/// A human-readable label to display in the UI.
pub fn display_label(&self) -> &str {
self.resolved
.as_ref()
.map(|resolved| resolved.label.as_str())
.unwrap_or_else(|| self.resolved_label.as_str())
}
}
/// Variables, available for use in [`TaskContext`] when a Zed's [`TaskTemplate`] gets resolved into a [`ResolvedTask`].

View File

@@ -156,7 +156,7 @@ impl TaskTemplate {
&variable_names,
&mut substituted_variables,
)?;
let args = substitute_all_template_variables_in_vec(
let args_with_substitutions = substitute_all_template_variables_in_vec(
&self.args,
&task_variables,
&variable_names,
@@ -187,8 +187,16 @@ impl TaskTemplate {
cwd,
full_label,
label: human_readable_label,
command_label: args_with_substitutions.iter().fold(
command.clone(),
|mut command_label, arg| {
command_label.push(' ');
command_label.push_str(arg);
command_label
},
),
command,
args,
args: self.args.clone(),
env,
use_new_terminal: self.use_new_terminal,
allow_concurrent_runs: self.allow_concurrent_runs,
@@ -524,11 +532,16 @@ mod tests {
assert_eq!(
spawn_in_terminal.args,
&[
"arg1 test_selected_text",
"arg2 5678",
&format!("arg3 {long_value}")
"arg1 $ZED_SELECTED_TEXT",
"arg2 $ZED_COLUMN",
"arg3 $ZED_SYMBOL",
],
"Args should be substituted with variables and those should not be shortened"
"Args should not be substituted with variables"
);
assert_eq!(
spawn_in_terminal.command_label,
format!("{} arg1 test_selected_text arg2 5678 arg3 {long_value}", spawn_in_terminal.command),
"Command label args should be substituted with variables and those should not be shortened"
);
assert_eq!(

View File

@@ -25,7 +25,6 @@ util.workspace = true
terminal.workspace = true
workspace.workspace = true
language.workspace = true
itertools.workspace = true
[dev-dependencies]

View File

@@ -168,8 +168,8 @@ fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContex
let language_context_provider = buffer
.read(cx)
.language()
.and_then(|language| language.context_provider())?;
.and_then(|language| language.context_provider())
.unwrap_or_else(|| Arc::new(BasicContextProvider));
let selection_range = selection.range();
let start = editor_snapshot
.display_snapshot
@@ -470,6 +470,7 @@ mod tests {
pub(crate) fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
cx.update(|cx| {
let state = AppState::test(cx);
file_icons::init((), cx);
language::init(cx);
crate::init(cx);
editor::init(cx);

View File

@@ -306,7 +306,30 @@ impl PickerDelegate for TasksModalDelegate {
) -> Option<Self::ListItem> {
let candidates = self.candidates.as_ref()?;
let hit = &self.matches[ix];
let (source_kind, _) = &candidates.get(hit.candidate_id)?;
let (source_kind, resolved_task) = &candidates.get(hit.candidate_id)?;
let template = resolved_task.original_task();
let display_label = resolved_task.display_label();
let mut tooltip_label_text = if display_label != &template.label {
resolved_task.resolved_label.clone()
} else {
String::new()
};
if let Some(resolved) = resolved_task.resolved.as_ref() {
if resolved.command_label != display_label
&& resolved.command_label != resolved_task.resolved_label
{
if !tooltip_label_text.trim().is_empty() {
tooltip_label_text.push('\n');
}
tooltip_label_text.push_str(&resolved.command_label);
}
}
let tooltip_label = if tooltip_label_text.trim().is_empty() {
None
} else {
Some(Tooltip::text(tooltip_label_text, cx))
};
let highlighted_location = HighlightedText {
text: hit.string.clone(),
@@ -325,6 +348,9 @@ impl PickerDelegate for TasksModalDelegate {
ListItem::new(SharedString::from(format!("tasks-modal-{ix}")))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.when_some(tooltip_label, |list_item, item_label| {
list_item.tooltip(move |_| item_label.clone())
})
.map(|item| {
let item = if matches!(source_kind, TaskSourceKind::UserInput)
|| Some(ix) <= self.last_used_candidate_index
@@ -368,18 +394,10 @@ impl PickerDelegate for TasksModalDelegate {
}
fn selected_as_query(&self) -> Option<String> {
use itertools::intersperse;
let task_index = self.matches.get(self.selected_index())?.candidate_id;
let tasks = self.candidates.as_ref()?;
let (_, task) = tasks.get(task_index)?;
task.resolved.as_ref().map(|spawn_in_terminal| {
let mut command = spawn_in_terminal.command.clone();
if !spawn_in_terminal.args.is_empty() {
command.push(' ');
command.extend(intersperse(spawn_in_terminal.args.clone(), " ".to_string()));
}
command
})
Some(task.resolved.as_ref()?.command_label.clone())
}
fn confirm_input(&mut self, omit_history_entry: bool, cx: &mut ViewContext<Picker<Self>>) {
@@ -405,17 +423,23 @@ impl PickerDelegate for TasksModalDelegate {
#[cfg(test)]
mod tests {
use std::{path::PathBuf, sync::Arc};
use editor::Editor;
use gpui::{TestAppContext, VisualTestContext};
use language::{ContextProviderWithTasks, Language, LanguageConfig, LanguageMatcher, Point};
use project::{FakeFs, Project};
use serde_json::json;
use task::TaskTemplates;
use workspace::CloseInactiveTabsAndPanes;
use crate::modal::Spawn;
use crate::{modal::Spawn, tests::init_test};
use super::*;
#[gpui::test]
async fn test_spawn_tasks_modal_query_reuse(cx: &mut TestAppContext) {
crate::tests::init_test(cx);
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
@@ -561,6 +585,294 @@ mod tests {
);
}
#[gpui::test]
async fn test_basic_context_for_simple_files(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
".zed": {
"tasks.json": r#"[
{
"label": "hello from $ZED_FILE:$ZED_ROW:$ZED_COLUMN",
"command": "echo",
"args": ["hello", "from", "$ZED_FILE", ":", "$ZED_ROW", ":", "$ZED_COLUMN"]
},
{
"label": "opened now: $ZED_WORKTREE_ROOT",
"command": "echo",
"args": ["opened", "now:", "$ZED_WORKTREE_ROOT"]
}
]"#,
},
"file_without_extension": "aaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaa",
"file_with.odd_extension": "b",
}),
)
.await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!(
task_names(&tasks_picker, cx),
Vec::<String>::new(),
"Should list no file or worktree context-dependent when no file is open"
);
tasks_picker.update(cx, |_, cx| {
cx.emit(DismissEvent);
});
drop(tasks_picker);
cx.executor().run_until_parked();
let _ = workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(PathBuf::from("/dir/file_with.odd_extension"), true, cx)
})
.await
.unwrap();
cx.executor().run_until_parked();
let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!(
task_names(&tasks_picker, cx),
vec![
"hello from …th.odd_extension:1:1".to_string(),
"opened now: /dir".to_string()
],
"Second opened buffer should fill the context, labels should be trimmed if long enough"
);
tasks_picker.update(cx, |_, cx| {
cx.emit(DismissEvent);
});
drop(tasks_picker);
cx.executor().run_until_parked();
let second_item = workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(PathBuf::from("/dir/file_without_extension"), true, cx)
})
.await
.unwrap();
let editor = cx.update(|cx| second_item.act_as::<Editor>(cx)).unwrap();
editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 2)..Point::new(1, 5)))
})
});
cx.executor().run_until_parked();
let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!(
task_names(&tasks_picker, cx),
vec![
"hello from …ithout_extension:2:3".to_string(),
"opened now: /dir".to_string()
],
"Opened buffer should fill the context, labels should be trimmed if long enough"
);
tasks_picker.update(cx, |_, cx| {
cx.emit(DismissEvent);
});
drop(tasks_picker);
cx.executor().run_until_parked();
}
#[gpui::test]
async fn test_language_task_filtering(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
"a1.ts": "// a1",
"a2.ts": "// a2",
"b.rs": "// b",
}),
)
.await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
project.read_with(cx, |project, _| {
let language_registry = project.languages();
language_registry.add(Arc::new(
Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..LanguageMatcher::default()
},
..LanguageConfig::default()
},
None,
)
.with_context_provider(Some(Arc::new(
ContextProviderWithTasks::new(TaskTemplates(vec![
TaskTemplate {
label: "Task without variables".to_string(),
command: "npm run clean".to_string(),
..TaskTemplate::default()
},
TaskTemplate {
label: "TypeScript task from file $ZED_FILE".to_string(),
command: "npm run build".to_string(),
..TaskTemplate::default()
},
TaskTemplate {
label: "Another task from file $ZED_FILE".to_string(),
command: "npm run lint".to_string(),
..TaskTemplate::default()
},
])),
))),
));
language_registry.add(Arc::new(
Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..LanguageMatcher::default()
},
..LanguageConfig::default()
},
None,
)
.with_context_provider(Some(Arc::new(
ContextProviderWithTasks::new(TaskTemplates(vec![TaskTemplate {
label: "Rust task".to_string(),
command: "cargo check".into(),
..TaskTemplate::default()
}])),
))),
));
});
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let _ts_file_1 = workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(PathBuf::from("/dir/a1.ts"), true, cx)
})
.await
.unwrap();
let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!(
task_names(&tasks_picker, cx),
vec![
"Another task from file /dir/a1.ts",
"TypeScript task from file /dir/a1.ts",
"Task without variables",
],
"Should open spawn TypeScript tasks for the opened file, tasks with most template variables above, all groups sorted alphanumerically"
);
emulate_task_schedule(
tasks_picker,
&project,
"TypeScript task from file /dir/a1.ts",
cx,
);
let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!(
task_names(&tasks_picker, cx),
vec!["TypeScript task from file /dir/a1.ts", "Another task from file /dir/a1.ts", "Task without variables"],
"After spawning the task and getting it into the history, it should be up in the sort as recently used"
);
tasks_picker.update(cx, |_, cx| {
cx.emit(DismissEvent);
});
drop(tasks_picker);
cx.executor().run_until_parked();
let _ts_file_2 = workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(PathBuf::from("/dir/a2.ts"), true, cx)
})
.await
.unwrap();
let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!(
task_names(&tasks_picker, cx),
vec![
"TypeScript task from file /dir/a1.ts",
"Another task from file /dir/a2.ts",
"TypeScript task from file /dir/a2.ts",
"Task without variables"
],
"Even when both TS files are open, should only show the history (on the top), and tasks, resolved for the current file"
);
tasks_picker.update(cx, |_, cx| {
cx.emit(DismissEvent);
});
drop(tasks_picker);
cx.executor().run_until_parked();
let _rs_file = workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(PathBuf::from("/dir/b.rs"), true, cx)
})
.await
.unwrap();
let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!(
task_names(&tasks_picker, cx),
vec!["Rust task"],
"Even when both TS files are open and one TS task spawned, opened file's language tasks should be displayed only"
);
cx.dispatch_action(CloseInactiveTabsAndPanes::default());
emulate_task_schedule(tasks_picker, &project, "Rust task", cx);
let _ts_file_2 = workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(PathBuf::from("/dir/a2.ts"), true, cx)
})
.await
.unwrap();
let tasks_picker = open_spawn_tasks(&workspace, cx);
assert_eq!(
task_names(&tasks_picker, cx),
vec![
"TypeScript task from file /dir/a1.ts",
"Another task from file /dir/a2.ts",
"TypeScript task from file /dir/a2.ts",
"Task without variables"
],
"After closing all but *.rs tabs, running a Rust task and switching back to TS tasks, \
same TS spawn history should be restored"
);
}
fn emulate_task_schedule(
tasks_picker: View<Picker<TasksModalDelegate>>,
project: &Model<Project>,
scheduled_task_label: &str,
cx: &mut VisualTestContext,
) {
let scheduled_task = tasks_picker.update(cx, |tasks_picker, _| {
tasks_picker
.delegate
.candidates
.iter()
.flatten()
.find(|(_, task)| task.resolved_label == scheduled_task_label)
.cloned()
.unwrap()
});
project.update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, _| {
let (kind, task) = scheduled_task;
inventory.task_scheduled(kind, task);
})
});
tasks_picker.update(cx, |_, cx| {
cx.emit(DismissEvent);
});
drop(tasks_picker);
cx.executor().run_until_parked()
}
fn open_spawn_tasks(
workspace: &View<Workspace>,
cx: &mut VisualTestContext,
@@ -569,7 +881,7 @@ mod tests {
workspace.update(cx, |workspace, cx| {
workspace
.active_modal::<TasksModal>(cx)
.unwrap()
.expect("no task modal after `Spawn` action was dispatched")
.read(cx)
.picker
.clone()

View File

@@ -39,7 +39,7 @@ use pty_info::PtyProcessInfo;
use serde::{Deserialize, Serialize};
use settings::Settings;
use smol::channel::{Receiver, Sender};
use task::{RevealStrategy, TaskId};
use task::TaskId;
use terminal_settings::{AlternateScroll, Shell, TerminalBlink, TerminalSettings};
use theme::{ActiveTheme, Theme};
use util::truncate_and_trailoff;
@@ -286,17 +286,6 @@ impl Display for TerminalError {
}
}
#[derive(Debug)]
pub struct SpawnTask {
pub id: TaskId,
pub full_label: String,
pub label: String,
pub command: String,
pub args: Vec<String>,
pub env: HashMap<String, String>,
pub reveal: RevealStrategy,
}
// https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213
const DEFAULT_SCROLL_HISTORY_LINES: usize = 10_000;
const MAX_SCROLL_HISTORY_LINES: usize = 100_000;

View File

@@ -28,7 +28,6 @@ search.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
shlex.workspace = true
shellexpand.workspace = true
smol.workspace = true
terminal.workspace = true

View File

@@ -1,4 +1,4 @@
use std::{borrow::Cow, ops::ControlFlow, path::PathBuf, sync::Arc};
use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
use crate::TerminalView;
use collections::{HashMap, HashSet};
@@ -15,10 +15,7 @@ use search::{buffer_search::DivRegistrar, BufferSearchBar};
use serde::{Deserialize, Serialize};
use settings::Settings;
use task::{RevealStrategy, SpawnInTerminal, TaskId};
use terminal::{
terminal_settings::{Shell, TerminalDockPosition, TerminalSettings},
SpawnTask,
};
use terminal::terminal_settings::{Shell, TerminalDockPosition, TerminalSettings};
use ui::{h_flex, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip};
use util::{ResultExt, TryFutureExt};
use workspace::{
@@ -302,36 +299,31 @@ impl TerminalPanel {
}
fn spawn_task(&mut self, spawn_in_terminal: &SpawnInTerminal, cx: &mut ViewContext<Self>) {
let mut spawn_task = SpawnTask {
id: spawn_in_terminal.id.clone(),
full_label: spawn_in_terminal.full_label.clone(),
label: spawn_in_terminal.label.clone(),
command: spawn_in_terminal.command.clone(),
args: spawn_in_terminal.args.clone(),
env: spawn_in_terminal.env.clone(),
reveal: spawn_in_terminal.reveal,
};
let mut spawn_task = spawn_in_terminal.clone();
// Set up shell args unconditionally, as tasks are always spawned inside of a shell.
let Some((shell, mut user_args)) = (match TerminalSettings::get_global(cx).shell.clone() {
Shell::System => std::env::var("SHELL").ok().map(|shell| (shell, vec![])),
Shell::Program(shell) => Some((shell, vec![])),
Shell::System => std::env::var("SHELL").ok().map(|shell| (shell, Vec::new())),
Shell::Program(shell) => Some((shell, Vec::new())),
Shell::WithArguments { program, args } => Some((program, args)),
}) else {
return;
};
let mut command = std::mem::take(&mut spawn_task.command);
let args = std::mem::take(&mut spawn_task.args);
for arg in args {
command.push(' ');
let arg = shlex::try_quote(&arg).unwrap_or(Cow::Borrowed(&arg));
command.push_str(&arg);
}
spawn_task.command = shell;
user_args.extend(["-i".to_owned(), "-c".to_owned(), command]);
spawn_task.command_label = format!("{shell} -i -c `{}`", spawn_task.command_label);
let task_command = std::mem::replace(&mut spawn_task.command, shell);
let task_args = std::mem::take(&mut spawn_task.args);
let combined_command = task_args
.into_iter()
.fold(task_command, |mut command, arg| {
command.push(' ');
command.push_str(&arg);
command
});
user_args.extend(["-i".to_owned(), "-c".to_owned(), combined_command]);
spawn_task.args = user_args;
let reveal = spawn_task.reveal;
let spawn_task = spawn_task;
let reveal = spawn_task.reveal;
let working_directory = spawn_in_terminal.cwd.clone();
let allow_concurrent_runs = spawn_in_terminal.allow_concurrent_runs;
let use_new_terminal = spawn_in_terminal.use_new_terminal;
@@ -407,7 +399,7 @@ impl TerminalPanel {
fn spawn_in_new_terminal(
&mut self,
spawn_task: SpawnTask,
spawn_task: SpawnInTerminal,
working_directory: Option<PathBuf>,
cx: &mut ViewContext<Self>,
) {
@@ -470,7 +462,7 @@ impl TerminalPanel {
fn add_terminal(
&mut self,
working_directory: Option<PathBuf>,
spawn_task: Option<SpawnTask>,
spawn_task: Option<SpawnInTerminal>,
cx: &mut ViewContext<Self>,
) {
let workspace = self.workspace.clone();
@@ -562,7 +554,7 @@ impl TerminalPanel {
fn replace_terminal(
&self,
working_directory: Option<PathBuf>,
spawn_task: SpawnTask,
spawn_task: SpawnInTerminal,
terminal_item_index: usize,
terminal_to_replace: View<TerminalView>,
cx: &mut ViewContext<'_, Self>,

View File

@@ -159,11 +159,21 @@ fn search_submit(workspace: &mut Workspace, _: &SearchSubmit, cx: &mut ViewConte
search_bar.select_match(direction, count, cx);
search_bar.focus_editor(&Default::default(), cx);
let prior_selections = state.prior_selections.drain(..).collect();
let mut prior_selections: Vec<_> = state.prior_selections.drain(..).collect();
let prior_mode = state.prior_mode;
let prior_operator = state.prior_operator.take();
let new_selections = vim.editor_selections(cx);
// If the active editor has changed during a search, don't panic.
if prior_selections.iter().any(|s| {
vim.update_active_editor(cx, |_vim, editor, cx| {
!s.start.is_valid(&editor.snapshot(cx).buffer_snapshot)
})
.unwrap_or(true)
}) {
prior_selections.clear();
}
if prior_mode != vim.state().mode {
vim.switch_mode(prior_mode, true, cx);
}

View File

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

View File

@@ -1 +1 @@
dev
stable

View File

@@ -17,25 +17,31 @@ You will need write access to the Zed repository to do this:
- Run `./script/bump-zed-minor-versions` and push the tags
and branches as instructed.
- Wait for the builds to appear at https://github.com/zed-industries/zed/releases (typically takes around 30 minutes)
- Copy the release notes from the previous Preview release(s) to the current Stable release.
- Write new release notes for Preview. `/script/get-preview-channel-changes` can help with this, but you'll need to edit and format the output to make it good.
- Download the artifacts for each release and test that you can run them locally.
- Publish the releases.
- While you're waiting:
- Start creating the new release notes for preview. You can start with the output of `./script/get-preview-channel-changes`.
- Start drafting the release tweets.
- Once the builds are ready:
- Copy the release notes from the previous Preview release(s) to the current Stable release.
- Download the artifacts for each release and test that you can run them locally.
- Publish the releases on GitHub.
- Tweet the tweets (Credentials are in 1password).
## Patch release process
If your PR fixes a panic or a crash, you should cherry-pick it to the current stable and preview branches. If your PR fixes a regression in recently released code, you should cherry-pick it to the appropriate branch.
If your PR fixes a panic or a crash, you should cherry-pick it to the current stable and preview branches. If your PR fixes a regression in recently released code, you should cherry-pick it to preview.
You will need write access to the Zed repository to do this:
- Cherry pick them onto the correct branch. You can either do this manually, or leave a comment of the form `/cherry-pick v0.XXX.x` on the PR, and the GitHub bot should do it for you.
- Run `./script/trigger-release {preview|stable}`
- Send a PR containing your change to `main` as normal.
- Leave a comment on the PR `/cherry-pick v0.XXX.x`. Once your PR is merged, the Github bot will send a PR to the branch.
- In case of a merge conflict, you will have to cherry-pick manually and push the change to the `v0.XXX.x` branch.
- After the commits are cherry-picked onto the branch, run `./script/trigger-release {preview|stable}`. This will bump the version numbers, create a new release tag, and kick off a release build.
- Wait for the builds to appear at https://github.com/zed-industries/zed/releases (typically takes around 30 minutes)
- Add release notes using the `Release notes:` section of each cherry-picked PR.
- Proof-read and edit the release notes as needed.
- Download the artifacts for each release and test that you can run them locally.
- Publish the release.
## Nightly release process
- Merge your changes to main
- Run `./script/trigger-release {nightly}`
In addition to the public releases, we also have a nightly build that we encourage employees to use.
Nightly is released by cron once a day, and can be shipped as often as you'd like. There are no release notes or announcements, so you can just merge your changes to main and run `./script/trigger-release nightly`.

109
script/draft-release-notes Executable file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env node --redirect-warnings=/dev/null
const { execFileSync } = require("child_process");
main();
async function main() {
let version = process.argv[2];
let channel = process.argv[3];
let parts = version.split(".");
if (
process.argv.length != 4 ||
parts.length != 3 ||
parts.find((part) => isNaN(part)) != null ||
(channel != "stable" && channel != "preview")
) {
console.log("Usage: draft-release-notes <version> {stable|preview}");
process.exit(1);
}
let priorVersion = [parts[0], parts[1], parts[2] - 1].join(".");
let suffix = "";
if (channel == "preview") {
suffix = "-pre";
if (parts[2] == 0) {
priorVersion = [parts[0], parts[1] - 1, 0].join(".");
}
} else if (!tagExists("v${priorVersion}")) {
console.log("Copy the release notes from preview.");
process.exit(0);
}
let [tag, priorTag] = [`v${version}${suffix}`, `v${priorVersion}${suffix}`];
const newCommits = getCommits(priorTag, tag);
let releaseNotes = [];
let missing = [];
let skipped = [];
for (const commit of newCommits) {
let link = "https://github.com/zed-industries/zed/pull/" + commit.pr;
let notes = commit.releaseNotes;
if (commit.pr == "") {
link = "https://github.com/zed-industries/zed/commits/" + commit.hash;
} else if (!notes.includes("zed-industries/zed/issues")) {
notes = notes + " ([#" + commit.pr + "](" + link + "))";
}
if (commit.releaseNotes == "") {
missing.push("- MISSING " + commit.firstLine + " " + link);
} else if (commit.releaseNotes.startsWith("- N/A")) {
skipped.push("- N/A " + commit.firstLine + " " + link);
} else {
releaseNotes.push(notes);
}
}
console.log(releaseNotes.join("\n") + "\n");
console.log("<!-- ");
console.log(missing.join("\n"));
console.log(skipped.join("\n"));
console.log("-->");
}
function getCommits(oldTag, newTag) {
const pullRequestNumbers = execFileSync(
"git",
["log", `${oldTag}..${newTag}`, "--format=DIVIDER\n%H|||%B"],
{ encoding: "utf8" },
)
.replace(/\r\n/g, "\n")
.split("DIVIDER\n")
.filter((commit) => commit.length > 0)
.map((commit) => {
let [hash, firstLine] = commit.split("\n")[0].split("|||");
let cherryPick = firstLine.match(/\(cherry-pick #([0-9]+)\)/)?.[1] || "";
let pr = firstLine.match(/\(#(\d+)\)$/)?.[1] || "";
let releaseNotes = (commit.split(/Release notes:.*\n/i)[1] || "")
.split("\n\n")[0]
.trim()
.replace(/\n(?![\n-])/g, " ");
if (releaseNotes.includes("<public_issue_number_if_exists>")) {
releaseNotes = "";
}
return {
hash,
pr,
cherryPick,
releaseNotes,
firstLine,
};
});
return pullRequestNumbers;
}
function tagExists(tag) {
try {
execFileSync("git", ["rev-parse", "--verify", tag]);
return true;
} catch (e) {
return false;
}
}