Compare commits
21 Commits
vim-syntax
...
v0.131.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb4e635314 | ||
|
|
631ac30719 | ||
|
|
67002457da | ||
|
|
7ac4eec9cd | ||
|
|
518399f807 | ||
|
|
5d68e2fbf9 | ||
|
|
f0560213c4 | ||
|
|
10499204fa | ||
|
|
d8fd1f5a49 | ||
|
|
bc6e7dbd1b | ||
|
|
18f0bc4c84 | ||
|
|
4ef4748681 | ||
|
|
30824089b2 | ||
|
|
5df5a400e9 | ||
|
|
ca62f6e231 | ||
|
|
b01c07b17d | ||
|
|
c35c957e7a | ||
|
|
0137e7e1e0 | ||
|
|
25b8e46b98 | ||
|
|
04e6d5d553 | ||
|
|
547d02227d |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -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
3
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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', ""))
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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))?
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
))
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -866,6 +866,7 @@ impl Item for TerminalView {
|
||||
Some(vec![BreadcrumbText {
|
||||
text: self.terminal().read(cx).breadcrumb_text.clone(),
|
||||
highlights: None,
|
||||
font: None,
|
||||
}])
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
109
script/draft-release-notes
Executable file
109
script/draft-release-notes
Executable 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user