Compare commits

...

21 Commits

Author SHA1 Message Date
Zed Bot
eb4e635314 Bump to 0.131.7 for @maxdeviant 2024-04-22 12:39:11 -07:00
gcp-cherry-pick-bot[bot]
631ac30719 Fix reading workspace-level LSP settings in extensions (cherry-pick #10859) (#10861)
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:36 -04:00
Conrad Irwin
67002457da 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:18 -06:00
Marshall Bowers
7ac4eec9cd v0.131.x stable 2024-04-17 13:05:02 -04:00
Zed Bot
518399f807 Bump to 0.131.6 for @ConradIrwin 2024-04-15 20:13:14 -07:00
Kirill Bulatov
5d68e2fbf9 Merge Zed task context providing logic (#10544)
Before, `tasks_ui` set most of the context with `SymbolContextProvider`
providing the symbol data part of the context. Now, there's a
`BasicContextProvider` that forms all standard Zed context and it
automatically serves as a base, with no need for other providers like
`RustContextProvider` to call it as before.

Also, stop adding `SelectedText` task variable into the context for
blank text selection.

Release Notes:

- N/A
2024-04-16 01:08:28 +03:00
Conrad Irwin
f0560213c4 zed 0.131.5 2024-04-15 14:20:38 -06:00
gcp-cherry-pick-bot[bot]
10499204fa Fuse iterator supplied to SumTree::from_iter (cherry-pick #10571) (#10573)
Cherry-picked Fuse iterator supplied to `SumTree::from_iter` (#10571)

This fixes an issue that could cause `from_iter` to never finish if the
underlying iterator restarted after returning `None` for the first time.

We only saw this in development but I wanna cherry-pick it to stable and
preview, just in case.

Release Notes:

- N/A

Co-authored-by: Kyle <kylek@zed.dev>

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Kyle <kylek@zed.dev>
2024-04-15 20:11:36 +02:00
gcp-cherry-pick-bot[bot]
d8fd1f5a49 Defer first update active buffer for conversation (cherry-pick #10564) (#10567)
Cherry-picked Defer first update active buffer for conversation (#10564)

This fixes when the workspace is not actually available for a
`.read(cx)`.

Release Notes:

- Fix a panic when quoting a selection before the assistant panel has
been started

Co-authored-by: Conrad Irwin <conrad@zed.dev>

Co-authored-by: Kyle Kelley <rgbkrk@gmail.com>
Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-04-15 11:20:50 -06:00
Conrad Irwin
bc6e7dbd1b zed 0.131.4 2024-04-12 14:27:05 -06:00
gcp-cherry-pick-bot[bot]
18f0bc4c84 Fix emojis when rendering with the system ui font (cherry-pick #10491) (#10493)
Cherry-picked Fix emojis when rendering with the system ui font (#10491)

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-12 14:20:56 -06:00
gcp-cherry-pick-bot[bot]
4ef4748681 Use buffer font for search (cherry-pick #10455) (#10494)
Cherry-picked Use buffer font for search (#10455)

It's wierd to type code/regex in the UI font

Release Notes:

- Continue to use buffer font for search

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-12 14:20:46 -06:00
gcp-cherry-pick-bot[bot]
30824089b2 Use buffer font when rendering editor breadcrumbs and diagnostics (cherry-pick #10488) (#10495)
Cherry-picked Use buffer font when rendering editor breadcrumbs and
diagnostics (#10488)

Before:

<img width="592" alt="Screenshot 2024-04-12 at 12 00 00 PM"

src="https://github.com/zed-industries/zed/assets/2280405/3251743e-4f2c-4ca3-9bc5-88f37660f7b9">

After:

<img width="673" alt="Screenshot 2024-04-12 at 12 11 37 PM"

src="https://github.com/zed-industries/zed/assets/2280405/6a8ac597-261a-45d9-bf2a-a673b6f26b0e">


Release Notes:

- N/A

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-04-12 14:20:36 -06:00
Kyle Kelley
5df5a400e9 zed 0.131.3 2024-04-11 13:54:35 -07:00
gcp-cherry-pick-bot[bot]
ca62f6e231 Do not show image viewer for SVGs (cherry-pick #10435) (#10447)
Cherry-picked Do not show image viewer for SVGs (#10435)

Absent some ability to toggle between viewing and editing a file, I
think it would be best to get a fix out quick for people to edit SVGs as
text files.

Release Notes:

- Fixed editing of SVG images by disabling it from the image viewer
([#10403](https://github.com/zed-industries/zed/issues/10403)).

Co-authored-by: Kyle Kelley <rgbkrk@gmail.com>
2024-04-11 13:50:38 -07:00
Marshall Bowers
b01c07b17d zed 0.131.2 2024-04-11 14:54:50 -04:00
gcp-cherry-pick-bot[bot]
c35c957e7a Validate content-length of downloaded extension tar gz files (cherry-pick #10430) (#10440)
Cherry-picked Validate content-length of downloaded extension tar gz
files (#10430)

Release Notes:

- Fixed a bug where extension installation would appear to succeed even
if the download did not complete due to network interruptions
([#10330](https://github.com/zed-industries/zed/issues/10330)).

Co-authored-by: Marshall <marshall@zed.dev>

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Marshall <marshall@zed.dev>
2024-04-11 14:53:36 -04:00
Kirill Bulatov
0137e7e1e0 zed 0.131.1 2024-04-11 01:12:45 +03:00
Kirill Bulatov
25b8e46b98 Use proper Workspace references when querying for tasks by name (#10383)
Closes https://github.com/zed-industries/zed/issues/10380

Release Notes:

- Fixed Zed panicking when running tasks via a keybinding
([10380](https://github.com/zed-industries/zed/issues/10380))
2024-04-11 00:43:26 +03:00
gcp-cherry-pick-bot[bot]
04e6d5d553 Fix panic when loading malformed Wasm files (cherry-pick #10370) (#10371)
Cherry-picked Fix panic when loading malformed Wasm files (#10370)

This PR fixes a potential panic that could occur when loading malformed
Wasm files.

We now use the `parse_wasm_extension_version` function that was
previously used just to extract the Zed extension API version from the
Wasm bytes as a pre-validation step. By parsing the entirety of the Wasm
file here instead of returning as soon as we find the version, the
invalid Wasm bytes are now surfaced as an `Err` instead of a panic.

We were able to replicate the panic using the following test:

```rs
#[gpui::test]
async fn test_bad_wasm(cx: &mut TestAppContext) {
    init_test(cx);

    let wasm_host = cx.update(|cx| {
        WasmHost::new(
            FakeFs::new(cx.background_executor().clone()),
            FakeHttpClient::with_200_response(),
            FakeNodeRuntime::new(),
            Arc::new(LanguageRegistry::test(cx.background_executor().clone())),
            PathBuf::from("/the/work/dir".to_string()),
            cx,
        )
    });

    let mut wasm_bytes = std::fs::read("/Users/maxdeviant/Library/Application Support/Zed/extensions/installed/dart/extension.wasm").unwrap();

    // This is the error message we were seeing in the stack trace:
    // range end index 267037 out of range for slice of length 253952

    dbg!(&wasm_bytes.len());

    // Truncate the bytes to the same point:
    wasm_bytes.truncate(253952);

    std::fs::write("/tmp/bad-extension.wasm", wasm_bytes.clone()).unwrap();

    let manifest = Arc::new(ExtensionManifest {
        id: "the-extension".into(),
        name: "The Extension".into(),
        version: "0.0.1".into(),
        schema_version: SchemaVersion(1),
        description: Default::default(),
        repository: Default::default(),
        authors: Default::default(),
        lib: LibManifestEntry {
            kind: None,
            version: None,
        },
        themes: Default::default(),
        languages: Default::default(),
        grammars: Default::default(),
        language_servers: Default::default(),
    });

    // 💥
    let result = wasm_host
        .load_extension(wasm_bytes, manifest, cx.executor())
        .await;
    dbg!(result.map(|_| ()));
```



Release Notes:

- Fixed a crash that could occur when loading malformed Wasm extensions
([#10352](https://github.com/zed-industries/zed/issues/10352)).

---------

Co-authored-by: Max <max@zed.dev>

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Max <max@zed.dev>
2024-04-10 14:19:46 -04:00
Joseph T. Lyons
547d02227d v0.131.x preview 2024-04-10 11:44:54 -04:00
26 changed files with 414 additions and 173 deletions

View File

@@ -205,6 +205,7 @@ jobs:
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
script/draft-release-notes "$version" "$channel" > target/release-notes.md
- name: Generate license file
run: script/generate-licenses
@@ -248,7 +249,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_file: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3
Cargo.lock generated
View File

@@ -3588,6 +3588,7 @@ dependencies = [
"fs",
"futures 0.3.28",
"gpui",
"isahc",
"language",
"log",
"lsp",
@@ -12514,7 +12515,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.131.0"
version = "0.131.7"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -2060,7 +2060,7 @@ impl ConversationEditor {
workspace: workspace.downgrade(),
_subscriptions,
};
this.update_active_buffer(workspace, cx);
cx.defer(|this, cx| this.update_active_buffer(workspace, cx));
this.update_message_headers(cx);
this
}

View File

@@ -52,12 +52,19 @@ impl Render for Breadcrumbs {
Some(BreadcrumbText {
text: "".into(),
highlights: None,
font: None,
}),
);
}
let highlighted_segments = segments.into_iter().map(|segment| {
let mut text_style = cx.text_style();
if let Some(font) = segment.font {
text_style.font_family = font.family;
text_style.font_features = font.features;
text_style.font_style = font.style;
text_style.font_weight = font.weight;
}
text_style.color = Color::Muted.color(cx);
StyledText::new(segment.text.replace('\n', ""))

View File

@@ -10568,6 +10568,11 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
let mut text_style = cx.text_style().clone();
text_style.color = diagnostic_style(diagnostic.severity, true, cx.theme().status());
let theme_settings = ThemeSettings::get_global(cx);
text_style.font_family = theme_settings.buffer_font.family.clone();
text_style.font_style = theme_settings.buffer_font.style;
text_style.font_features = theme_settings.buffer_font.features;
text_style.font_weight = theme_settings.buffer_font.weight;
let multi_line_diagnostic = diagnostic.message.contains('\n');

View File

@@ -30,7 +30,7 @@ use std::{
sync::Arc,
};
use text::{BufferId, Selection};
use theme::Theme;
use theme::{Theme, ThemeSettings};
use ui::{h_flex, prelude::*, Label};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableItemHandle};
@@ -824,13 +824,18 @@ impl Item for Editor {
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string());
let settings = ThemeSettings::get_global(cx);
let mut breadcrumbs = vec![BreadcrumbText {
text: filename,
highlights: None,
font: Some(settings.buffer_font.clone()),
}];
breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
text: symbol.text,
highlights: Some(symbol.highlight_ranges),
font: Some(settings.buffer_font.clone()),
}));
Some(breadcrumbs)
}

View File

@@ -23,6 +23,7 @@ collections.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
isahc.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true

View File

@@ -606,7 +606,23 @@ impl ExtensionStore {
)
.await?;
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
let content_length = response
.headers()
.get(isahc::http::header::CONTENT_LENGTH)
.and_then(|value| value.to_str().ok()?.parse::<usize>().ok());
let mut body = BufReader::new(response.body_mut());
let mut tgz_bytes = Vec::new();
body.read_to_end(&mut tgz_bytes).await?;
if let Some(content_length) = content_length {
let actual_len = tgz_bytes.len();
if content_length != actual_len {
bail!("downloaded extension size {actual_len} does not match content length {content_length}");
}
}
let decompressed_bytes = GzipDecoder::new(BufReader::new(tgz_bytes.as_slice()));
// let decompressed_bytes = GzipDecoder::new(BufReader::new(tgz_bytes));
let archive = Archive::new(decompressed_bytes);
archive.unpack(extension_dir).await?;
this.update(&mut cx, |this, cx| {

View File

@@ -198,13 +198,15 @@ pub fn parse_wasm_extension_version(
extension_id: &str,
wasm_bytes: &[u8],
) -> Result<SemanticVersion> {
let mut version = None;
for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
if let wasmparser::Payload::CustomSection(s) = part? {
if let wasmparser::Payload::CustomSection(s) =
part.context("error parsing wasm extension")?
{
if s.name() == "zed:api-version" {
let version = parse_wasm_extension_version_custom_section(s.data());
if let Some(version) = version {
return Ok(version);
} else {
version = parse_wasm_extension_version_custom_section(s.data());
if version.is_none() {
bail!(
"extension {} has invalid zed:api-version section: {:?}",
extension_id,
@@ -214,7 +216,13 @@ pub fn parse_wasm_extension_version(
}
}
}
bail!("extension {} has no zed:api-version section", extension_id)
// The reason we wait until we're done parsing all of the Wasm bytes to return the version
// is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
//
// By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
// earlier as an `Err` rather than as a panic.
version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
}
fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {

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

@@ -334,7 +334,7 @@ impl MacTextSystemState {
self.postscript_names_by_font_id
.get(&font_id)
.map_or(false, |postscript_name| {
postscript_name == "AppleColorEmoji"
postscript_name == "AppleColorEmoji" || postscript_name == ".AppleColorEmojiUI"
})
}

View File

@@ -37,7 +37,9 @@ impl project::Item for ImageItem {
.and_then(OsStr::to_str)
.unwrap_or_default();
if Img::extensions().contains(&ext) {
// Only open the item if it's a binary image (no SVGs, etc.)
// Since we do not have a way to toggle to an editor
if Img::extensions().contains(&ext) && !ext.contains("svg") {
Some(cx.spawn(|mut cx| async move {
let abs_path = project
.read_with(&cx, |project, cx| project.absolute_path(&path, cx))?

View File

@@ -56,7 +56,7 @@ use std::{
},
};
use syntax_map::SyntaxSnapshot;
pub use task_context::{ContextProvider, ContextProviderWithTasks, SymbolContextProvider};
pub use task_context::{BasicContextProvider, ContextProvider, ContextProviderWithTasks};
use theme::SyntaxTheme;
use tree_sitter::{self, wasmtime, Query, WasmStore};
use util::http::HttpClient;

View File

@@ -1,34 +1,56 @@
use std::path::Path;
use crate::Location;
use anyhow::Result;
use gpui::AppContext;
use task::{static_source::TaskDefinitions, TaskVariables, VariableName};
use text::{Point, ToPoint};
/// Language Contexts are used by Zed tasks to extract information about source file.
/// Language Contexts are used by Zed tasks to extract information about the source file where the tasks are supposed to be scheduled from.
/// Multiple context providers may be used together: by default, Zed provides a base [`BasicContextProvider`] context that fills all non-custom [`VariableName`] variants.
///
/// The context will be used to fill data for the tasks, and filter out the ones that do not have the variables required.
pub trait ContextProvider: Send + Sync {
fn build_context(&self, _: Location, _: &mut AppContext) -> Result<TaskVariables> {
/// 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,
) -> Result<TaskVariables> {
Ok(TaskVariables::default())
}
/// Provides all tasks, associated with the current language.
fn associated_tasks(&self) -> Option<TaskDefinitions> {
None
}
// Determines whether the [`BasicContextProvider`] variables should be filled too (if `false`), or omitted (if `true`).
fn is_basic(&self) -> bool {
false
}
}
/// A context provider that finds out what symbol is currently focused in the buffer.
pub struct SymbolContextProvider;
/// A context provided that tries to provide values for all non-custom [`VariableName`] variants for a currently opened file.
/// Applied as a base for every custom [`ContextProvider`] unless explicitly oped out.
pub struct BasicContextProvider;
impl ContextProvider for BasicContextProvider {
fn is_basic(&self) -> bool {
true
}
impl ContextProvider for SymbolContextProvider {
fn build_context(
&self,
location: Location,
worktree_abs_path: Option<&Path>,
location: &Location,
cx: &mut AppContext,
) -> gpui::Result<TaskVariables> {
let symbols = location
.buffer
.read(cx)
.snapshot()
.symbols_containing(location.range.start, None);
) -> Result<TaskVariables> {
let buffer = location.buffer.read(cx);
let buffer_snapshot = buffer.snapshot();
let symbols = buffer_snapshot.symbols_containing(location.range.start, None);
let symbol = symbols.unwrap_or_default().last().map(|symbol| {
let range = symbol
.name_ranges
@@ -37,9 +59,40 @@ impl ContextProvider for SymbolContextProvider {
.unwrap_or(0..symbol.text.len());
symbol.text[range].to_string()
});
Ok(TaskVariables::from_iter(
Some(VariableName::Symbol).zip(symbol),
))
let current_file = buffer
.file()
.and_then(|file| file.as_local())
.map(|file| file.abs_path(cx).to_string_lossy().to_string());
let Point { row, column } = location.range.start.to_point(&buffer_snapshot);
let row = row + 1;
let column = column + 1;
let selected_text = buffer
.chars_for_range(location.range.clone())
.collect::<String>();
let mut task_variables = TaskVariables::from_iter([
(VariableName::Row, row.to_string()),
(VariableName::Column, column.to_string()),
]);
if let Some(symbol) = symbol {
task_variables.insert(VariableName::Symbol, symbol);
}
if !selected_text.trim().is_empty() {
task_variables.insert(VariableName::SelectedText, selected_text);
}
if let Some(path) = current_file {
task_variables.insert(VariableName::File, path);
}
if let Some(worktree_path) = worktree_abs_path {
task_variables.insert(
VariableName::WorktreeRoot,
worktree_path.to_string_lossy().to_string(),
);
}
Ok(task_variables)
}
}
@@ -59,7 +112,12 @@ impl ContextProvider for ContextProviderWithTasks {
Some(self.definitions.clone())
}
fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<TaskVariables> {
SymbolContextProvider.build_context(location, cx)
fn build_context(
&self,
worktree_abs_path: Option<&Path>,
location: &Location,
cx: &mut AppContext,
) -> Result<TaskVariables> {
BasicContextProvider.build_context(worktree_abs_path, location, cx)
}
}

View File

@@ -105,7 +105,7 @@ pub fn init(
Ok((
config.clone(),
load_queries($name),
Some(Arc::new(language::SymbolContextProvider)),
Some(Arc::new(language::BasicContextProvider)),
))
},
);
@@ -125,7 +125,7 @@ pub fn init(
Ok((
config.clone(),
load_queries($name),
Some(Arc::new(language::SymbolContextProvider)),
Some(Arc::new(language::BasicContextProvider)),
))
},
);

View File

@@ -334,25 +334,26 @@ const RUST_PACKAGE_TASK_VARIABLE: VariableName =
impl ContextProvider for RustContextProvider {
fn build_context(
&self,
location: Location,
_: Option<&Path>,
location: &Location,
cx: &mut gpui::AppContext,
) -> Result<TaskVariables> {
let mut context = SymbolContextProvider.build_context(location.clone(), cx)?;
let local_abs_path = location
.buffer
.read(cx)
.file()
.and_then(|file| Some(file.as_local()?.abs_path(cx)));
if let Some(package_name) = local_abs_path
.as_deref()
.and_then(|local_abs_path| local_abs_path.parent())
.and_then(human_readable_package_name)
{
context.insert(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name);
}
Ok(context)
Ok(
if let Some(package_name) = local_abs_path
.as_deref()
.and_then(|local_abs_path| local_abs_path.parent())
.and_then(human_readable_package_name)
{
TaskVariables::from_iter(Some((RUST_PACKAGE_TASK_VARIABLE.clone(), package_name)))
} else {
TaskVariables::default()
},
)
}
fn associated_tasks(&self) -> Option<TaskDefinitions> {

View File

@@ -113,8 +113,8 @@ impl BufferSearchBar {
} else {
color
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features,
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,

View File

@@ -1421,8 +1421,8 @@ impl ProjectSearchBar {
} else {
cx.theme().colors().text
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features,
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,

View File

@@ -179,7 +179,7 @@ impl<T: Item> SumTree<T> {
) -> Self {
let mut nodes = Vec::new();
let mut iter = iter.into_iter().peekable();
let mut iter = iter.into_iter().fuse().peekable();
while iter.peek().is_some() {
let items: ArrayVec<T, { 2 * TREE_BASE }> = iter.by_ref().take(2 * TREE_BASE).collect();
let item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> =
@@ -1244,6 +1244,27 @@ mod tests {
assert_eq!(tree.get(&4, &()), Some(&4));
}
#[test]
fn test_from_iter() {
assert_eq!(
SumTree::from_iter(0..100, &()).items(&()),
(0..100).collect::<Vec<_>>()
);
// Ensure `from_iter` works correctly when the given iterator restarts
// after calling `next` if `None` was already returned.
let mut ix = 0;
let iterator = std::iter::from_fn(|| {
ix = (ix + 1) % 2;
if ix == 1 {
Some(1)
} else {
None
}
});
assert_eq!(SumTree::from_iter(iterator, &()).items(&()), vec![1]);
}
#[derive(Clone, Default, Debug)]
pub struct IntegersSummary {
count: usize,

View File

@@ -1,12 +1,16 @@
use std::{path::PathBuf, sync::Arc};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use ::settings::Settings;
use anyhow::Context;
use editor::Editor;
use gpui::{AppContext, ViewContext, WeakView, WindowContext};
use language::{Language, Point};
use gpui::{AppContext, ViewContext, WindowContext};
use language::{BasicContextProvider, ContextProvider, Language};
use modal::{Spawn, TasksModal};
use project::{Location, WorktreeId};
use task::{Task, TaskContext, TaskVariables, VariableName};
use task::{Task, TaskContext, TaskVariables};
use util::ResultExt;
use workspace::Workspace;
@@ -29,8 +33,7 @@ pub fn init(cx: &mut AppContext) {
})
{
let task_context = if action.reevaluate_context {
let cwd = task_cwd(workspace, cx).log_err().flatten();
task_context(workspace, cwd, cx)
task_context(workspace, cx)
} else {
old_context
};
@@ -48,8 +51,7 @@ fn spawn_task_or_modal(workspace: &mut Workspace, action: &Spawn, cx: &mut ViewC
None => {
let inventory = workspace.project().read(cx).task_inventory().clone();
let workspace_handle = workspace.weak_handle();
let cwd = task_cwd(workspace, cx).log_err().flatten();
let task_context = task_context(workspace, cwd, cx);
let task_context = task_context(workspace, cx);
workspace.toggle_modal(cx, |cx| {
TasksModal::new(inventory, task_context, workspace_handle, cx)
})
@@ -60,17 +62,16 @@ fn spawn_task_or_modal(workspace: &mut Workspace, action: &Spawn, cx: &mut ViewC
fn spawn_task_with_name(name: String, cx: &mut ViewContext<Workspace>) {
cx.spawn(|workspace, mut cx| async move {
let did_spawn = workspace
.update(&mut cx, |this, cx| {
let (worktree, language) = active_item_selection_properties(&workspace, cx);
let tasks = this.project().update(cx, |project, cx| {
.update(&mut cx, |workspace, cx| {
let (worktree, language) = active_item_selection_properties(workspace, cx);
let tasks = workspace.project().update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
inventory.list_tasks(language, worktree, false, cx)
})
});
let (_, target_task) = tasks.into_iter().find(|(_, task)| task.name() == name)?;
let cwd = task_cwd(this, cx).log_err().flatten();
let task_context = task_context(this, cwd, cx);
schedule_task(this, &target_task, task_context, false, cx);
let task_context = task_context(workspace, cx);
schedule_task(workspace, &target_task, task_context, false, cx);
Some(())
})
.ok()
@@ -88,13 +89,10 @@ fn spawn_task_with_name(name: String, cx: &mut ViewContext<Workspace>) {
}
fn active_item_selection_properties(
workspace: &WeakView<Workspace>,
workspace: &Workspace,
cx: &mut WindowContext,
) -> (Option<WorktreeId>, Option<Arc<Language>>) {
let active_item = workspace
.update(cx, |workspace, cx| workspace.active_item(cx))
.ok()
.flatten();
let active_item = workspace.active_item(cx);
let worktree_id = active_item
.as_ref()
.and_then(|item| item.project_path(cx))
@@ -114,104 +112,89 @@ fn active_item_selection_properties(
(worktree_id, language)
}
fn task_context(
workspace: &Workspace,
cwd: Option<PathBuf>,
cx: &mut WindowContext<'_>,
) -> TaskContext {
let current_editor = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx));
if let Some(current_editor) = current_editor {
(|| {
let editor = current_editor.read(cx);
fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
fn task_context_impl(workspace: &Workspace, cx: &mut WindowContext<'_>) -> Option<TaskContext> {
let cwd = task_cwd(workspace, cx).log_err().flatten();
let editor = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))?;
let (selection, buffer, editor_snapshot) = editor.update(cx, |editor, cx| {
let selection = editor.selections.newest::<usize>(cx);
let (buffer, _, _) = editor
.buffer()
.read(cx)
.point_to_buffer_offset(selection.start, cx)?;
let snapshot = editor.snapshot(cx);
Some((selection, buffer, snapshot))
})?;
let language_context_provider = buffer
.read(cx)
.language()
.and_then(|language| language.context_provider())?;
current_editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let selection_range = selection.range();
let start = snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.start)
.text_anchor;
let end = snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.end)
.text_anchor;
let Point { row, column } = snapshot
.display_snapshot
.buffer_snapshot
.offset_to_point(selection_range.start);
let row = row + 1;
let column = column + 1;
let location = Location {
buffer: buffer.clone(),
range: start..end,
};
let current_file = location
.buffer
let selection_range = selection.range();
let start = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.start)
.text_anchor;
let end = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.end)
.text_anchor;
let worktree_abs_path = buffer
.read(cx)
.file()
.map(|file| WorktreeId::from_usize(file.worktree_id()))
.and_then(|worktree_id| {
workspace
.project()
.read(cx)
.file()
.and_then(|file| file.as_local())
.map(|file| file.abs_path(cx).to_string_lossy().to_string());
let worktree_id = location
.buffer
.read(cx)
.file()
.map(|file| WorktreeId::from_usize(file.worktree_id()));
let context = buffer
.read(cx)
.language()
.and_then(|language| language.context_provider())
.and_then(|provider| provider.build_context(location, cx).ok());
let worktree_path = worktree_id.and_then(|worktree_id| {
workspace
.project()
.read(cx)
.worktree_for_id(worktree_id, cx)
.map(|worktree| worktree.read(cx).abs_path().to_string_lossy().to_string())
});
let selected_text = buffer.read(cx).chars_for_range(selection_range).collect();
let mut task_variables = TaskVariables::from_iter([
(VariableName::Row, row.to_string()),
(VariableName::Column, column.to_string()),
(VariableName::SelectedText, selected_text),
]);
if let Some(path) = current_file {
task_variables.insert(VariableName::File, path);
}
if let Some(worktree_path) = worktree_path {
task_variables.insert(VariableName::WorktreeRoot, worktree_path);
}
if let Some(language_context) = context {
task_variables.extend(language_context);
}
Some(TaskContext {
cwd: cwd.clone(),
task_variables,
})
})
})()
.unwrap_or_else(|| TaskContext {
.worktree_for_id(worktree_id, cx)
.map(|worktree| worktree.read(cx).abs_path())
});
let location = Location {
buffer,
range: start..end,
};
let task_variables = combine_task_variables(
worktree_abs_path.as_deref(),
location,
language_context_provider.as_ref(),
cx,
)
.log_err()?;
Some(TaskContext {
cwd,
task_variables: Default::default(),
task_variables,
})
}
task_context_impl(workspace, cx).unwrap_or_default()
}
fn combine_task_variables(
worktree_abs_path: Option<&Path>,
location: Location,
context_provider: &dyn ContextProvider,
cx: &mut WindowContext<'_>,
) -> anyhow::Result<TaskVariables> {
if context_provider.is_basic() {
context_provider
.build_context(worktree_abs_path, &location, cx)
.context("building basic provider context")
} else {
TaskContext {
cwd,
task_variables: Default::default(),
}
let mut basic_context = BasicContextProvider
.build_context(worktree_abs_path, &location, cx)
.context("building basic default context")?;
basic_context.extend(
context_provider
.build_context(worktree_abs_path, &location, cx)
.context("building provider context ")?,
);
Ok(basic_context)
}
}
@@ -276,14 +259,14 @@ mod tests {
use editor::Editor;
use gpui::{Entity, TestAppContext};
use language::{Language, LanguageConfig, SymbolContextProvider};
use language::{BasicContextProvider, Language, LanguageConfig};
use project::{FakeFs, Project, TaskSourceKind};
use serde_json::json;
use task::{oneshot_source::OneshotSource, TaskContext, TaskVariables, VariableName};
use ui::VisualContext;
use workspace::{AppState, Workspace};
use crate::{task_context, task_cwd};
use crate::task_context;
#[gpui::test]
async fn test_default_language_context(cx: &mut TestAppContext) {
@@ -326,7 +309,7 @@ mod tests {
name: (_) @name) @item"#,
)
.unwrap()
.with_context_provider(Some(Arc::new(SymbolContextProvider))),
.with_context_provider(Some(Arc::new(BasicContextProvider))),
);
let typescript_language = Arc::new(
@@ -344,7 +327,7 @@ mod tests {
")" @context)) @item"#,
)
.unwrap()
.with_context_provider(Some(Arc::new(SymbolContextProvider))),
.with_context_provider(Some(Arc::new(BasicContextProvider))),
);
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
project.update(cx, |project, cx| {
@@ -383,7 +366,7 @@ mod tests {
this.add_item_to_center(Box::new(editor2.clone()), cx);
assert_eq!(this.active_item(cx).unwrap().item_id(), editor2.entity_id());
assert_eq!(
task_context(this, task_cwd(this, cx).unwrap(), cx),
task_context(this, cx),
TaskContext {
cwd: Some("/dir".into()),
task_variables: TaskVariables::from_iter([
@@ -391,7 +374,6 @@ mod tests {
(VariableName::WorktreeRoot, "/dir".into()),
(VariableName::Row, "1".into()),
(VariableName::Column, "1".into()),
(VariableName::SelectedText, "".into())
])
}
);
@@ -400,7 +382,7 @@ mod tests {
this.change_selections(None, cx, |selections| selections.select_ranges([14..18]))
});
assert_eq!(
task_context(this, task_cwd(this, cx).unwrap(), cx),
task_context(this, cx),
TaskContext {
cwd: Some("/dir".into()),
task_variables: TaskVariables::from_iter([
@@ -417,7 +399,7 @@ mod tests {
// Now, let's switch the active item to .ts file.
this.activate_item(&editor1, cx);
assert_eq!(
task_context(this, task_cwd(this, cx).unwrap(), cx),
task_context(this, cx),
TaskContext {
cwd: Some("/dir".into()),
task_variables: TaskVariables::from_iter([
@@ -425,7 +407,6 @@ mod tests {
(VariableName::WorktreeRoot, "/dir".into()),
(VariableName::Row, "1".into()),
(VariableName::Column, "1".into()),
(VariableName::SelectedText, "".into()),
(VariableName::Symbol, "this_is_a_test".into()),
])
}

View File

@@ -195,8 +195,13 @@ impl PickerDelegate for TasksModalDelegate {
let Some(candidates) = picker
.update(&mut cx, |picker, cx| {
let candidates = picker.delegate.candidates.get_or_insert_with(|| {
let (worktree, language) =
active_item_selection_properties(&picker.delegate.workspace, cx);
let Ok((worktree, language)) =
picker.delegate.workspace.update(cx, |workspace, cx| {
active_item_selection_properties(workspace, cx)
})
else {
return Vec::new();
};
picker.delegate.inventory.update(cx, |inventory, cx| {
inventory.list_tasks(language, worktree, true, cx)
})
@@ -376,6 +381,8 @@ mod tests {
use project::{FakeFs, Project};
use serde_json::json;
use crate::modal::Spawn;
use super::*;
#[gpui::test]
@@ -514,13 +521,29 @@ mod tests {
vec!["echo 4", "another one", "example task", "echo 40"],
"Last recently used one show task should be listed last, as it is a fire-and-forget task"
);
cx.dispatch_action(Spawn {
task_name: Some("example task".to_string()),
});
let tasks_picker = workspace.update(cx, |workspace, cx| {
workspace
.active_modal::<TasksModal>(cx)
.unwrap()
.read(cx)
.picker
.clone()
});
assert_eq!(
task_names(&tasks_picker, cx),
vec!["echo 4", "another one", "example task", "echo 40"],
);
}
fn open_spawn_tasks(
workspace: &View<Workspace>,
cx: &mut VisualTestContext,
) -> View<Picker<TasksModalDelegate>> {
cx.dispatch_action(crate::modal::Spawn::default());
cx.dispatch_action(Spawn::default());
workspace.update(cx, |workspace, cx| {
workspace
.active_modal::<TasksModal>(cx)

View File

@@ -866,6 +866,7 @@ impl Item for TerminalView {
Some(vec![BreadcrumbText {
text: self.terminal().read(cx).breadcrumb_text.clone(),
highlights: None,
font: None,
}])
}

View File

@@ -14,7 +14,7 @@ use client::{
use futures::{channel::mpsc, StreamExt};
use gpui::{
AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
Font, HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
WindowContext,
};
use project::{Project, ProjectEntryId, ProjectPath};
@@ -93,6 +93,7 @@ pub enum ItemEvent {
pub struct BreadcrumbText {
pub text: String,
pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
pub font: Option<Font>,
}
pub trait Item: FocusableView + EventEmitter<Self::Event> {

View File

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

View File

@@ -1 +1 @@
dev
stable

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;
}
}