Compare commits

...

14 Commits

Author SHA1 Message Date
Piotr Osiewicz
b6f43ba45b Worker pool is no longer scoped 2025-11-26 18:33:31 +01:00
Piotr Osiewicz
dc8150d410 project search: Get rid of lifetimes on Worker 2025-11-26 18:21:28 +01:00
Piotr Osiewicz
b9d84e5eef workspace: Fix broken main build after #43518
*cough* merge queue *cough*
2025-11-26 18:21:02 +01:00
Jason Lee
1a23115773 gpui: Unify track_scroll method to receive a reference type (#43518)
Release Notes:

- N/A

This PR to change the `track_scroll` method to receive a reference type
like the
[Div#track_scroll](https://docs.rs/gpui/latest/gpui/trait.StatefulInteractiveElement.html#method.track_scroll),
[Div#track_focus](https://docs.rs/gpui/latest/gpui/trait.InteractiveElement.html#method.track_focus).


```diff
- .track_scroll(self.scroll_handle.clone())
+ .track_scroll(&self.scroll_handle)

- .vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
+ .vertical_scrollbar_for(&self.scroll_handle, window, cx)
```


56a2f9cfcf/crates/gpui/src/elements/div.rs (L1088-L1093)


56a2f9cfcf/crates/gpui/src/elements/div.rs (L613-L620)
2025-11-26 18:03:42 +01:00
Cole Miller
757c043171 Fix git features not working when a Windows host collaborates with a unix guest (#43515)
We were using `std::path::Path::strip_prefix` to determine which
repository an absolute path belongs to, which doesn't work when the
paths are Windows-style but the code is running on unix. Replace it with
a platform-agnostic implementation of `strip_prefix`.

Release Notes:

- Fixed git features not working when a Windows host collaborates with a
unix guest
2025-11-26 16:56:34 +00:00
Marshall Bowers
57e1bb8106 collab: Add zed-zippy[bot] to the GET /contributor endpoint (#43568)
This PR adds the `zed-zippy[bot]` user to the `GET /contributor`
endpoint so that it passes the CLA check.

Release Notes:

- N/A
2025-11-26 16:53:05 +00:00
Finn Evers
5403e74bbd Add callable workflow to bump the version of an extension (#43566)
This adds an intial workflow file that can be pulled in to create a bump
commit for an extension version in an extension repository.

Release Notes:

- N/A
2025-11-26 16:51:50 +00:00
Mayank Verma
0713ddcabc editor: Fix vertical scroll margin not accounting for file header height (#43521)
Closes #43178

Release Notes:

- Fixed vertical scroll margin not accounting for file header height

Here's the before/after:

With `{ "vertical_scroll_margin": 0 }` in `~/.config/zed/settings.json`


https://github.com/user-attachments/assets/418c6d7f-de0f-4da6-a038-69927b1b8b88
2025-11-26 16:06:02 +00:00
Joseph T. Lyons
6fbbc89904 Bump Zed to v0.216 (#43564)
Release Notes:

- N/A
2025-11-26 15:59:13 +00:00
Remco Smits
8aa53612fd agent_ui: Add support for deleting thread history (#43370)
This PR adds support for deleting your entire thread history. This is
inspired by a Zed user from the meetup in Amsterdam, he was missing this
feature.

**Demo**


https://github.com/user-attachments/assets/5a195007-1094-4ec6-902a-1b83db5ec508

Release Notes:

- AI: Add support for deleting your entire thread history

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-11-26 15:55:12 +00:00
David Kleingeld
6a311cad11 Detail how to add symbols to samply's output (#43472)
Release Notes:

- N/A
2025-11-26 15:46:17 +00:00
Peter Tripp
51e97d343d languages: Recognize .clangd as YAML (#43557)
Follow-up to: https://github.com/zed-industries/zed/pull/43469

Thanks @WeetHet for [the idea]([WeetHet](https://github.com/WeetHet)).

Release Notes:

- Added support for identifying. .clangd files as YAML by default
2025-11-26 15:55:23 +01:00
Lukas Wirth
c36b12f3b2 settings_ui: Pick a more reasonable minimum window size (#43556)
Closes https://github.com/zed-industries/zed/issues/41903

Release Notes:

- Fixed settings ui being forced larger than small screens
2025-11-26 14:32:25 +00:00
Finn Evers
7c724c0f10 editor: Do not show scroll thumb if page fits (#43548)
Follow-up to https://github.com/zed-industries/zed/pull/39367

Release Notes:

- Fixed a small issue where a scrollbar would sometimes show in the
editor although the content fix exactly on screen.
2025-11-26 13:11:47 +00:00
70 changed files with 1005 additions and 172 deletions

136
.github/workflows/extension_bump.yml vendored Normal file
View File

@@ -0,0 +1,136 @@
# Generated from xtask::workflows::extension_bump
# Rebuild with `cargo xtask workflows`.
name: extension_bump
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: '1'
CARGO_INCREMENTAL: '0'
ZED_EXTENSION_CLI_SHA: 7cfce605704d41ca247e3f84804bf323f6c6caaf
on:
workflow_call:
inputs:
bump-type:
description: bump-type
type: string
default: patch
secrets:
app-id:
description: The app ID used to create the PR
required: true
app-secret:
description: The app secret for the corresponding app ID
required: true
jobs:
check_extension:
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- id: cache-zed-extension-cli
name: extension_tests::cache_zed_extension_cli
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: zed-extension
key: zed-extension-${{ env.ZED_EXTENSION_CLI_SHA }}
- name: extension_tests::download_zed_extension_cli
if: steps.cache-zed-extension-cli.outputs.cache-hit != 'true'
run: |
wget --quiet "https://zed-extension-cli.nyc3.digitaloceanspaces.com/$ZED_EXTENSION_CLI_SHA/x86_64-unknown-linux-gnu/zed-extension"
chmod +x zed-extension
shell: bash -euxo pipefail {0}
- name: extension_tests::check
run: |
mkdir -p /tmp/ext-scratch
mkdir -p /tmp/ext-output
./zed-extension --source-dir . --scratch-dir /tmp/ext-scratch --output-dir /tmp/ext-output
shell: bash -euxo pipefail {0}
timeout-minutes: 1
check_bump_needed:
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')
runs-on: namespace-profile-2x4-ubuntu-2404
steps:
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
fetch-depth: 10
- id: compare-versions-check
name: extension_bump::compare_versions
run: |+
CURRENT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
git checkout "$(git log -1 --format=%H)"~1
PREV_COMMIT_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
[[ "$CURRENT_VERSION" == "$PREV_COMMIT_VERSION" ]] && \
echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
echo "needs_bump=false" >> "$GITHUB_OUTPUT"
shell: bash -euxo pipefail {0}
outputs:
needs_bump: ${{ steps.compare-versions-check.outputs.needs_bump }}
timeout-minutes: 1
bump_extension_version:
needs:
- check_extension
- check_bump_needed
if: (github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions') && needs.check_bump_needed.outputs.needs_bump == 'true'
runs-on: namespace-profile-8x16-ubuntu-2204
steps:
- id: generate-token
name: extension_bump::generate_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.app-id }}
private-key: ${{ secrets.app-secret }}
- name: steps::checkout_repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
clean: false
- name: extension_bump::install_bump_2_version
run: pip install bump2version
shell: bash -euxo pipefail {0}
- id: bump-version
name: extension_bump::bump_version
run: |
OLD_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
cat <<EOF > .bumpversion.cfg
[bumpversion]
current_version = "$OLD_VERSION"
[bumpversion:file:Cargo.toml]
[bumpversion:file:extension.toml]
EOF
bump2version --verbose ${{ inputs.bump-type }}
NEW_VERSION="$(sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml)"
cargo update --workspace
rm .bumpversion.cfg
echo "old_version=${OLD_VERSION}" >> "$GITHUB_OUTPUT"
echo "new_version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
shell: bash -euxo pipefail {0}
- name: extension_bump::create_pull_request
uses: peter-evans/create-pull-request@v7
with:
title: Bump version to ${{ steps.bump-version.outputs.new_version }}
body: This PR bumps the version of this extension to v${{ steps.bump-version.outputs.new_version }}
commit-message: Bump version to v${{ steps.bump-version.outputs.new_version }}
branch: bump-from-${{ steps.bump-version.outputs.old_version }}
committer: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
base: main
delete-branch: true
token: ${{ steps.generate-token.outputs.token }}
sign-commits: true
timeout-minutes: 1
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true

2
Cargo.lock generated
View File

@@ -21192,7 +21192,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.215.0"
version = "0.216.0"
dependencies = [
"acp_tools",
"activity_indicator",

View File

@@ -528,7 +528,7 @@ impl Render for AcpTools {
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.size_full(),
)
.vertical_scrollbar_for(connection.list_state.clone(), window, cx)
.vertical_scrollbar_for(&connection.list_state, window, cx)
.into_any()
}
}

View File

@@ -424,4 +424,20 @@ impl ThreadsDatabase {
Ok(())
})
}
pub fn delete_threads(&self) -> Task<Result<()>> {
let connection = self.connection.clone();
self.executor.spawn(async move {
let connection = connection.lock();
let mut delete = connection.exec_bound::<()>(indoc! {"
DELETE FROM threads
"})?;
delete(())?;
Ok(())
})
}
}

View File

@@ -188,6 +188,15 @@ impl HistoryStore {
})
}
pub fn delete_threads(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let database_future = ThreadsDatabase::connect(cx);
cx.spawn(async move |this, cx| {
let database = database_future.await.map_err(|err| anyhow!(err))?;
database.delete_threads().await?;
this.update(cx, |this, cx| this.reload(cx))
})
}
pub fn delete_text_thread(
&mut self,
path: Arc<Path>,

View File

@@ -1423,7 +1423,7 @@ mod tests {
rel_path("b/eight.txt"),
];
let slash = PathStyle::local().separator();
let slash = PathStyle::local().primary_separator();
let mut opened_editors = Vec::new();
for path in paths {

View File

@@ -1,5 +1,5 @@
use crate::acp::AcpThreadView;
use crate::{AgentPanel, RemoveSelectedThread};
use crate::{AgentPanel, RemoveHistory, RemoveSelectedThread};
use agent::{HistoryEntry, HistoryStore};
use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
use editor::{Editor, EditorEvent};
@@ -12,7 +12,7 @@ use std::{fmt::Display, ops::Range};
use text::Bias;
use time::{OffsetDateTime, UtcOffset};
use ui::{
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tooltip, WithScrollbar,
HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Tab, Tooltip, WithScrollbar,
prelude::*,
};
@@ -25,6 +25,7 @@ pub struct AcpThreadHistory {
search_query: SharedString,
visible_items: Vec<ListItemType>,
local_timezone: UtcOffset,
confirming_delete_history: bool,
_update_task: Task<()>,
_subscriptions: Vec<gpui::Subscription>,
}
@@ -98,6 +99,7 @@ impl AcpThreadHistory {
)
.unwrap(),
search_query: SharedString::default(),
confirming_delete_history: false,
_subscriptions: vec![search_editor_subscription, history_store_subscription],
_update_task: Task::ready(()),
};
@@ -331,6 +333,24 @@ impl AcpThreadHistory {
task.detach_and_log_err(cx);
}
fn remove_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.history_store.update(cx, |store, cx| {
store.delete_threads(cx).detach_and_log_err(cx)
});
self.confirming_delete_history = false;
cx.notify();
}
fn prompt_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.confirming_delete_history = true;
cx.notify();
}
fn cancel_delete_history(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.confirming_delete_history = false;
cx.notify();
}
fn render_list_items(
&mut self,
range: Range<usize>,
@@ -447,6 +467,8 @@ impl Focusable for AcpThreadHistory {
impl Render for AcpThreadHistory {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let has_no_history = self.history_store.read(cx).is_empty(cx);
v_flex()
.key_context("ThreadHistory")
.size_full()
@@ -457,9 +479,12 @@ impl Render for AcpThreadHistory {
.on_action(cx.listener(Self::select_last))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::remove_selected_thread))
.on_action(cx.listener(|this, _: &RemoveHistory, window, cx| {
this.remove_history(window, cx);
}))
.child(
h_flex()
.h(px(41.)) // Match the toolbar perfectly
.h(Tab::container_height(cx))
.w_full()
.py_1()
.px_2()
@@ -481,7 +506,7 @@ impl Render for AcpThreadHistory {
.overflow_hidden()
.flex_grow();
if self.history_store.read(cx).is_empty(cx) {
if has_no_history {
view.justify_center().items_center().child(
Label::new("You don't have any past threads yet.")
.size(LabelSize::Small)
@@ -502,16 +527,74 @@ impl Render for AcpThreadHistory {
)
.p_1()
.pr_4()
.track_scroll(self.scroll_handle.clone())
.track_scroll(&self.scroll_handle)
.flex_grow(),
)
.vertical_scrollbar_for(
self.scroll_handle.clone(),
window,
cx,
)
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
}
})
.when(!has_no_history, |this| {
this.child(
h_flex()
.p_2()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.when(!self.confirming_delete_history, |this| {
this.child(
Button::new("delete_history", "Delete All History")
.full_width()
.style(ButtonStyle::Outlined)
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.prompt_delete_history(window, cx);
})),
)
})
.when(self.confirming_delete_history, |this| {
this.w_full()
.gap_2()
.flex_wrap()
.justify_between()
.child(
h_flex()
.flex_wrap()
.gap_1()
.child(
Label::new("Delete all threads?")
.size(LabelSize::Small),
)
.child(
Label::new("You won't be able to recover them later.")
.size(LabelSize::Small)
.color(Color::Muted),
),
)
.child(
h_flex()
.gap_1()
.child(
Button::new("cancel_delete", "Cancel")
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.cancel_delete_history(window, cx);
})),
)
.child(
Button::new("confirm_delete", "Delete")
.style(ButtonStyle::Tinted(ui::TintColor::Error))
.color(Color::Error)
.label_size(LabelSize::Small)
.on_click(cx.listener(|_, _, window, cx| {
window.dispatch_action(
Box::new(RemoveHistory),
cx,
);
})),
),
)
}),
)
})
}
}

View File

@@ -3989,7 +3989,7 @@ impl AcpThreadView {
let file = buffer.read(cx).file()?;
let path = file.path();
let path_style = file.path_style(cx);
let separator = file.path_style(cx).separator();
let separator = file.path_style(cx).primary_separator();
let file_path = path.parent().and_then(|parent| {
if parent.is_empty() {
@@ -5896,7 +5896,7 @@ impl Render for AcpThreadView {
.flex_grow()
.into_any(),
)
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.vertical_scrollbar_for(&self.list_state, window, cx)
.into_any()
} else {
this.child(self.render_recent_history(cx)).into_any()

View File

@@ -1209,7 +1209,7 @@ impl Render for AgentConfiguration {
.child(self.render_context_servers_section(window, cx))
.child(self.render_provider_configuration_section(cx)),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
)
}
}

View File

@@ -516,7 +516,7 @@ impl Render for AddLlmProviderModal {
.child(
div()
.size_full()
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
.child(
v_flex()
.id("modal_content")

View File

@@ -821,7 +821,6 @@ impl ConfigureContextServerModal {
impl Render for ConfigureContextServerModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let scroll_handle = self.scroll_handle.clone();
div()
.elevation_3(cx)
.w(rems(34.))
@@ -849,7 +848,7 @@ impl Render for ConfigureContextServerModal {
.id("modal-content")
.max_h(vh(0.7, window))
.overflow_y_scroll()
.track_scroll(&scroll_handle)
.track_scroll(&self.scroll_handle)
.child(self.render_modal_description(window, cx))
.child(self.render_modal_content(cx))
.child(match &self.state {
@@ -862,7 +861,7 @@ impl Render for ConfigureContextServerModal {
}
}),
)
.vertical_scrollbar_for(scroll_handle, window, cx),
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
),
)
.footer(self.render_modal_footer(cx)),

View File

@@ -138,7 +138,7 @@ impl ConfigureContextServerToolsModal {
items
})),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
.into_any_element()
}
}

View File

@@ -20,10 +20,9 @@ use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
use crate::ManageProfiles;
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
use crate::{
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, Follow, InlineAssistant,
NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory,
ResetTrialEndUpsell, ResetTrialUpsell, ToggleNavigationMenu, ToggleNewThreadMenu,
ToggleOptionsMenu,
AddContextServer, AgentDiffPane, Follow, InlineAssistant, NewTextThread, NewThread,
OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
acp::AcpThreadView,
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
slash_command::SlashCommandCompletionProvider,
@@ -614,11 +613,14 @@ impl AgentPanel {
if let Some(panel) = panel.upgrade() {
menu = Self::populate_recently_opened_menu_section(menu, panel, cx);
}
menu.action("View All", Box::new(OpenHistory))
.end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
menu = menu
.action("View All", Box::new(OpenHistory))
.fixed_width(px(320.).into())
.keep_open_on_confirm(false)
.key_context("NavigationMenu")
.key_context("NavigationMenu");
menu
});
weak_panel
.update(cx, |panel, cx| {

View File

@@ -69,6 +69,8 @@ actions!(
CycleModeSelector,
/// Expands the message editor to full size.
ExpandMessageEditor,
/// Removes all thread history.
RemoveHistory,
/// Opens the conversation history view.
OpenHistory,
/// Adds a context server to the configuration.

View File

@@ -64,6 +64,16 @@ async fn check_is_contributor(
}));
}
if ZedZippyBot::is_zed_zippy_bot(&params) {
return Ok(Json(CheckIsContributorResponse {
signed_at: Some(
ZedZippyBot::created_at()
.and_utc()
.to_rfc3339_opts(SecondsFormat::Millis, true),
),
}));
}
Ok(Json(CheckIsContributorResponse {
signed_at: app
.db
@@ -103,6 +113,36 @@ impl RenovateBot {
}
}
/// The Zed Zippy bot GitHub user (`zed-zippy[bot]`).
///
/// https://api.github.com/users/zed-zippy[bot]
struct ZedZippyBot;
impl ZedZippyBot {
const LOGIN: &'static str = "zed-zippy[bot]";
const USER_ID: i32 = 234243425;
/// Returns the `created_at` timestamp for the Zed Zippy bot user.
fn created_at() -> &'static NaiveDateTime {
static CREATED_AT: OnceLock<NaiveDateTime> = OnceLock::new();
CREATED_AT.get_or_init(|| {
chrono::DateTime::parse_from_rfc3339("2025-09-24T17:00:11Z")
.expect("failed to parse 'created_at' for 'zed-zippy[bot]'")
.naive_utc()
})
}
/// Returns whether the given contributor selector corresponds to the Zed Zippy bot user.
fn is_zed_zippy_bot(contributor: &ContributorSelector) -> bool {
match contributor {
ContributorSelector::GitHubLogin { github_login } => github_login == Self::LOGIN,
ContributorSelector::GitHubUserId { github_user_id } => {
github_user_id == &Self::USER_ID
}
}
}
}
#[derive(Debug, Deserialize)]
struct AddContributorBody {
github_user_id: i32,

View File

@@ -575,7 +575,7 @@ impl BreakpointList {
)
.with_horizontal_sizing_behavior(gpui::ListHorizontalSizingBehavior::Unconstrained)
.with_width_from_item(self.max_width_index)
.track_scroll(self.scroll_handle.clone())
.track_scroll(&self.scroll_handle)
.flex_1()
}
@@ -776,7 +776,7 @@ impl Render for BreakpointList {
.child(self.render_list(cx))
.custom_scrollbars(
ui::Scrollbars::new(ScrollAxes::Both)
.tracked_scroll_handle(self.scroll_handle.clone())
.tracked_scroll_handle(&self.scroll_handle)
.with_track_along(ScrollAxes::Both, cx.theme().colors().panel_background)
.tracked_entity(cx.entity_id()),
window,

View File

@@ -229,7 +229,7 @@ impl MemoryView {
rows
},
)
.track_scroll(view_state.scroll_handle)
.track_scroll(&view_state.scroll_handle)
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
.on_scroll_wheel(cx.listener(|this, evt: &ScrollWheelEvent, window, _| {
let mut view_state = this.view_state();
@@ -921,7 +921,7 @@ impl Render for MemoryView {
}))
.custom_scrollbars(
ui::Scrollbars::new(ui::ScrollAxes::Both)
.tracked_scroll_handle(self.view_state_handle.clone())
.tracked_scroll_handle(&self.view_state_handle)
.with_track_along(
ui::ScrollAxes::Both,
cx.theme().colors().panel_background,

View File

@@ -253,7 +253,7 @@ impl ModuleList {
range.map(|ix| this.render_entry(ix, cx)).collect()
}),
)
.track_scroll(self.scroll_handle.clone())
.track_scroll(&self.scroll_handle)
.size_full()
}
}
@@ -279,6 +279,6 @@ impl Render for ModuleList {
.size_full()
.p_1()
.child(self.render_list(window, cx))
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.vertical_scrollbar_for(&self.scroll_handle, window, cx)
}
}

View File

@@ -913,7 +913,7 @@ impl Render for StackFrameList {
)
})
.child(self.render_list(window, cx))
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.vertical_scrollbar_for(&self.list_state, window, cx)
}
}

View File

@@ -1557,7 +1557,7 @@ impl Render for VariableList {
this.render_entries(range, window, cx)
}),
)
.track_scroll(self.list_handle.clone())
.track_scroll(&self.list_handle)
.with_width_from_item(self.max_width_index)
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.with_horizontal_sizing_behavior(gpui::ListHorizontalSizingBehavior::Unconstrained)
@@ -1574,10 +1574,10 @@ impl Render for VariableList {
)
.with_priority(1)
}))
// .vertical_scrollbar_for(self.list_handle.clone(), window, cx)
// .vertical_scrollbar_for(&self.list_handle, window, cx)
.custom_scrollbars(
ui::Scrollbars::new(ScrollAxes::Both)
.tracked_scroll_handle(self.list_handle.clone())
.tracked_scroll_handle(&self.list_handle)
.with_track_along(ScrollAxes::Both, cx.theme().colors().panel_background)
.tracked_entity(cx.entity_id()),
window,

View File

@@ -933,7 +933,7 @@ impl CompletionsMenu {
)
.occlude()
.max_h(max_height_in_lines as f32 * window.line_height())
.track_scroll(self.scroll_handle.clone())
.track_scroll(&self.scroll_handle)
.with_sizing_behavior(ListSizingBehavior::Infer)
.map(|this| {
if self.display_options.dynamic_width {
@@ -948,7 +948,7 @@ impl CompletionsMenu {
div().child(list).custom_scrollbars(
Scrollbars::for_settings::<CompletionMenuScrollBarSetting>()
.show_along(ScrollAxes::Vertical)
.tracked_scroll_handle(self.scroll_handle.clone()),
.tracked_scroll_handle(&self.scroll_handle),
window,
cx,
),
@@ -1599,7 +1599,7 @@ impl CodeActionsMenu {
)
.occlude()
.max_h(max_height_in_lines as f32 * window.line_height())
.track_scroll(self.scroll_handle.clone())
.track_scroll(&self.scroll_handle)
.with_width_from_item(
self.actions
.iter()

View File

@@ -28198,3 +28198,87 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
3
"});
}
#[gpui::test]
async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (editor, cx) = cx.add_window_view(|window, cx| {
let multi_buffer = MultiBuffer::build_multi(
[
("1\n2\n3\n", vec![Point::row_range(0..3)]),
("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
],
cx,
);
Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
});
let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
cx.assert_excerpts_with_selections(indoc! {"
[EXCERPT]
ˇ1
2
3
[EXCERPT]
1
2
3
4
5
6
7
8
9
"});
cx.update_editor(|editor, window, cx| {
editor.change_selections(None.into(), window, cx, |s| {
s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
});
});
cx.assert_excerpts_with_selections(indoc! {"
[EXCERPT]
1
2
3
[EXCERPT]
1
2
3
4
5
6
ˇ7
8
9
"});
cx.update_editor(|editor, _window, cx| {
editor.set_vertical_scroll_margin(0, cx);
});
cx.update_editor(|editor, window, cx| {
assert_eq!(editor.vertical_scroll_margin(), 0);
editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 12.0)
);
});
cx.update_editor(|editor, _window, cx| {
editor.set_vertical_scroll_margin(3, cx);
});
cx.update_editor(|editor, window, cx| {
assert_eq!(editor.vertical_scroll_margin(), 3);
editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
assert_eq!(
editor.snapshot(window, cx).scroll_position(),
gpui::Point::new(0., 9.0)
);
});
}

View File

@@ -10712,9 +10712,9 @@ impl ScrollbarLayout {
show_thumb: bool,
axis: ScrollbarAxis,
) -> Self {
let text_units_per_page = f64::from(viewport_size / glyph_space);
let text_units_per_page = viewport_size.to_f64() / glyph_space.to_f64();
let visible_range = scroll_position..scroll_position + text_units_per_page;
let total_text_units = scroll_range / f64::from(glyph_space);
let total_text_units = scroll_range / glyph_space.to_f64();
let thumb_percentage = text_units_per_page / total_text_units;
let thumb_size = Pixels::from(ScrollOffset::from(track_length) * thumb_percentage)

View File

@@ -914,7 +914,7 @@ impl InfoPopover {
)
.custom_scrollbars(
Scrollbars::for_settings::<EditorSettings>()
.tracked_scroll_handle(self.scroll_handle.clone()),
.tracked_scroll_handle(&self.scroll_handle),
window,
cx,
)
@@ -1012,7 +1012,7 @@ impl DiagnosticPopover {
)
.custom_scrollbars(
Scrollbars::for_settings::<EditorSettings>()
.tracked_scroll_handle(self.scroll_handle.clone()),
.tracked_scroll_handle(&self.scroll_handle),
window,
cx,
),

View File

@@ -71,14 +71,20 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Editor>,
) {
let display_snapshot = self.display_snapshot(cx);
let scroll_margin_rows = self.vertical_scroll_margin() as u32;
let new_screen_top = self
.selections
.newest_display(&self.display_snapshot(cx))
.newest_display(&display_snapshot)
.head()
.row()
.0;
let new_screen_top = new_screen_top.saturating_sub(scroll_margin_rows);
let header_offset = display_snapshot
.buffer_snapshot()
.show_headers()
.then(|| display_snapshot.buffer_header_height())
.unwrap_or(0);
let new_screen_top = new_screen_top.saturating_sub(scroll_margin_rows + header_offset);
self.set_scroll_top_row(DisplayRow(new_screen_top), window, cx);
}

View File

@@ -391,7 +391,7 @@ impl SignatureHelpPopover {
)
}),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx);
.vertical_scrollbar_for(&self.scroll_handle, window, cx);
let controls = if self.signatures.len() > 1 {
let prev_button = IconButton::new("signature_help_prev", IconName::ChevronUp)

View File

@@ -1704,12 +1704,12 @@ impl Render for ExtensionsPage {
if count == 0 {
this.child(self.render_empty_state(cx)).into_any_element()
} else {
let scroll_handle = self.list.clone();
let scroll_handle = &self.list;
this.child(
uniform_list("entries", count, cx.processor(Self::render_extensions))
.flex_grow()
.pb_4()
.track_scroll(scroll_handle.clone()),
.track_scroll(scroll_handle),
)
.vertical_scrollbar_for(scroll_handle, window, cx)
.into_any_element()

View File

@@ -1060,7 +1060,7 @@ impl FileFinderDelegate {
(
filename.to_string(),
Vec::new(),
prefix.display(path_style).to_string() + path_style.separator(),
prefix.display(path_style).to_string() + path_style.primary_separator(),
Vec::new(),
)
} else {
@@ -1071,7 +1071,7 @@ impl FileFinderDelegate {
.map_or(String::new(), |f| f.to_string_lossy().into_owned()),
Vec::new(),
entry_path.absolute.parent().map_or(String::new(), |path| {
path.to_string_lossy().into_owned() + path_style.separator()
path.to_string_lossy().into_owned() + path_style.primary_separator()
}),
Vec::new(),
)

View File

@@ -1598,7 +1598,7 @@ async fn test_history_match_positions(cx: &mut gpui::TestAppContext) {
assert_eq!(file_label.highlight_indices(), &[0, 1, 2]);
assert_eq!(
path_label.text(),
format!("test{}", PathStyle::local().separator())
format!("test{}", PathStyle::local().primary_separator())
);
assert_eq!(path_label.highlight_indices(), &[] as &[usize]);
});

View File

@@ -559,7 +559,7 @@ impl PickerDelegate for OpenPathDelegate {
parent_path,
candidate.path.string,
if candidate.is_dir {
path_style.separator()
path_style.primary_separator()
} else {
""
}
@@ -569,7 +569,7 @@ impl PickerDelegate for OpenPathDelegate {
parent_path,
candidate.path.string,
if candidate.is_dir {
path_style.separator()
path_style.primary_separator()
} else {
""
}
@@ -826,7 +826,13 @@ impl PickerDelegate for OpenPathDelegate {
}
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
Arc::from(format!("[directory{}]filename.ext", self.path_style.separator()).as_str())
Arc::from(
format!(
"[directory{}]filename.ext",
self.path_style.primary_separator()
)
.as_str(),
)
}
fn separators_after_indices(&self) -> Vec<usize> {

View File

@@ -107,7 +107,7 @@ pub fn match_fixed_path_set(
.display(path_style)
.chars()
.collect::<Vec<_>>();
path_prefix_chars.extend(path_style.separator().chars());
path_prefix_chars.extend(path_style.primary_separator().chars());
let lowercase_pfx = path_prefix_chars
.iter()
.map(|c| c.to_ascii_lowercase())

View File

@@ -3939,7 +3939,7 @@ impl GitPanel {
ListHorizontalSizingBehavior::Unconstrained,
)
.with_width_from_item(self.max_width_item_index)
.track_scroll(self.scroll_handle.clone()),
.track_scroll(&self.scroll_handle),
)
.on_mouse_down(
MouseButton::Right,
@@ -3949,7 +3949,7 @@ impl GitPanel {
)
.custom_scrollbars(
Scrollbars::for_settings::<GitPanelSettings>()
.tracked_scroll_handle(self.scroll_handle.clone())
.tracked_scroll_handle(&self.scroll_handle)
.with_track_along(
ScrollAxes::Horizontal,
cx.theme().colors().panel_background,
@@ -4351,8 +4351,11 @@ impl GitPanel {
.when(strikethrough, Label::strikethrough),
),
(true, false) => this.child(
self.entry_label(format!("{dir}{}", path_style.separator()), path_color)
.when(strikethrough, Label::strikethrough),
self.entry_label(
format!("{dir}{}", path_style.primary_separator()),
path_color,
)
.when(strikethrough, Label::strikethrough),
),
_ => this,
}

View File

@@ -438,7 +438,7 @@ impl Render for DataTable {
}),
)
.size_full()
.track_scroll(self.scroll_handle.clone()),
.track_scroll(&self.scroll_handle),
)
.child(self.render_scrollbar(window, cx)),
),

View File

@@ -668,9 +668,9 @@ impl UniformList {
}
/// Track and render scroll state of this list with reference to the given scroll handle.
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
pub fn track_scroll(mut self, handle: &UniformListScrollHandle) -> Self {
self.interactivity.tracked_scroll_handle = Some(handle.0.borrow().base_handle.clone());
self.scroll_handle = Some(handle);
self.scroll_handle = Some(handle.clone());
self
}
@@ -780,7 +780,7 @@ mod test {
.collect()
}),
)
.track_scroll(self.scroll_handle.clone())
.track_scroll(&self.scroll_handle)
.h(px(200.0)),
)
}

View File

@@ -507,11 +507,11 @@ impl Render for SyntaxTreeView {
}),
)
.size_full()
.track_scroll(self.list_scroll_handle.clone())
.track_scroll(&self.list_scroll_handle)
.text_bg(cx.theme().colors().background)
.into_any_element(),
)
.vertical_scrollbar_for(self.list_scroll_handle.clone(), window, cx)
.vertical_scrollbar_for(&self.list_scroll_handle, window, cx)
.into_any_element()
} else {
let inner_content = v_flex()

View File

@@ -1,6 +1,6 @@
name = "YAML"
grammar = "yaml"
path_suffixes = ["yml", "yaml", "pixi.lock", "clang-format"]
path_suffixes = ["yml", "yaml", "pixi.lock", "clang-format", "clangd"]
line_comments = ["# "]
autoclose_before = ",]}"
brackets = [

View File

@@ -889,7 +889,7 @@ impl Element for MarkdownElement {
{
let scrollbars = Scrollbars::new(ScrollAxes::Horizontal)
.id(("markdown-code-block-scrollbar", range.start))
.tracked_scroll_handle(scroll_handle.clone())
.tracked_scroll_handle(scroll_handle)
.with_track_along(
ScrollAxes::Horizontal,
cx.theme().colors().editor_background,

View File

@@ -611,6 +611,6 @@ impl Render for MarkdownPreviewView {
.size_full(),
)
}))
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.vertical_scrollbar_for(&self.list_state, window, cx)
}
}

View File

@@ -400,10 +400,10 @@ impl Render for ProfilerWindow {
this.autoscroll = false;
cx.notify();
}))
.track_scroll(self.scroll_handle.clone())
.track_scroll(&self.scroll_handle)
.size_full(),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
)
})
}

View File

@@ -350,7 +350,7 @@ impl Render for Onboarding {
.child(self.render_page(cx))
.track_scroll(&self.scroll_handle),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
)
}
}

View File

@@ -4639,7 +4639,7 @@ impl OutlinePanel {
.with_sizing_behavior(ListSizingBehavior::Infer)
.with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
.with_width_from_item(self.max_width_item_index)
.track_scroll(self.scroll_handle.clone())
.track_scroll(&self.scroll_handle)
.when(show_indent_guides, |list| {
list.with_decoration(
ui::indent_guides(px(indent_size), IndentGuideColors::panel(cx))
@@ -4692,7 +4692,7 @@ impl OutlinePanel {
.child(list_contents.size_full().flex_shrink())
.custom_scrollbars(
Scrollbars::for_settings::<OutlinePanelSettings>()
.tracked_scroll_handle(self.scroll_handle.clone())
.tracked_scroll_handle(&self.scroll_handle.clone())
.with_track_along(
ScrollAxes::Horizontal,
cx.theme().colors().panel_background,

View File

@@ -780,7 +780,7 @@ impl<D: PickerDelegate> Picker<D> {
})
.flex_grow()
.py_1()
.track_scroll(scroll_handle.clone())
.track_scroll(&scroll_handle)
.into_any_element(),
ElementContainer::List(state) => list(
state.clone(),
@@ -866,12 +866,12 @@ impl<D: PickerDelegate> Render for Picker<D> {
this.map(|this| match &self.element_container {
ElementContainer::List(state) => this.custom_scrollbars(
base_scrollbar_config.tracked_scroll_handle(state.clone()),
base_scrollbar_config.tracked_scroll_handle(state),
window,
cx,
),
ElementContainer::UniformList(state) => this.custom_scrollbars(
base_scrollbar_config.tracked_scroll_handle(state.clone()),
base_scrollbar_config.tracked_scroll_handle(state),
window,
cx,
),

View File

@@ -3222,10 +3222,8 @@ impl RepositorySnapshot {
abs_path: &Path,
path_style: PathStyle,
) -> Option<RepoPath> {
abs_path
.strip_prefix(&work_directory_abs_path)
.ok()
.and_then(|path| RepoPath::from_std_path(path, path_style).ok())
let rel_path = path_style.strip_prefix(abs_path, work_directory_abs_path)?;
Some(RepoPath::from_rel_path(&rel_path))
}
pub fn had_conflict_on_last_merge_head_change(&self, repo_path: &RepoPath) -> bool {

View File

@@ -927,7 +927,7 @@ impl DirectoryLister {
.map(|worktree| worktree.read(cx).abs_path().to_string_lossy().into_owned())
.or_else(|| std::env::home_dir().map(|dir| dir.to_string_lossy().into_owned()))
.map(|mut s| {
s.push_str(path_style.separator());
s.push_str(path_style.primary_separator());
s
})
.unwrap_or_else(|| {

View File

@@ -168,6 +168,7 @@ impl Search {
unnamed_buffers.push(handle)
};
}
let open_buffers = Arc::new(open_buffers);
let executor = cx.background_executor().clone();
let (tx, rx) = unbounded();
let (grab_buffer_snapshot_tx, grab_buffer_snapshot_rx) = unbounded();
@@ -296,24 +297,22 @@ impl Search {
};
let should_find_all_matches = !tx.is_closed();
let num_cpus = executor.num_cpus();
let worker_pool = executor.scoped(|scope| {
let num_cpus = executor.num_cpus();
assert!(num_cpus > 0);
for _ in 0..executor.num_cpus() - 1 {
assert!(num_cpus > 0);
let worker_pool = (0..num_cpus - 1)
.map(|_| {
let worker = Worker {
query: &query,
open_buffers: &open_buffers,
query: query.clone(),
open_buffers: open_buffers.clone(),
candidates: candidate_searcher.clone(),
find_all_matches_rx: find_all_matches_rx.clone(),
};
scope.spawn(worker.run());
}
drop(find_all_matches_rx);
drop(candidate_searcher);
});
executor.spawn(worker.run()).boxed_local()
})
.collect::<Vec<_>>();
drop(find_all_matches_rx);
drop(candidate_searcher);
let (sorted_matches_tx, sorted_matches_rx) = unbounded();
// The caller of `into_handle` decides whether they're interested in all matches (files that matched + all matching ranges) or
@@ -348,7 +347,7 @@ impl Search {
};
futures::future::join_all(
[worker_pool.boxed_local()]
worker_pool
.into_iter()
.chain(buffer_snapshots)
.chain(ensure_matches_are_reported_in_order)
@@ -578,9 +577,9 @@ impl Search {
}
}
struct Worker<'search> {
query: &'search SearchQuery,
open_buffers: &'search HashSet<ProjectEntryId>,
struct Worker {
query: Arc<SearchQuery>,
open_buffers: Arc<HashSet<ProjectEntryId>>,
candidates: FindSearchCandidates,
/// Ok, we're back in background: run full scan & find all matches in a given buffer snapshot.
/// Then, when you're done, share them via the channel you were given.
@@ -591,7 +590,7 @@ struct Worker<'search> {
)>,
}
impl Worker<'_> {
impl Worker {
async fn run(self) {
let (
input_paths_rx,
@@ -629,7 +628,7 @@ impl Worker<'_> {
loop {
let handler = RequestHandler {
query: self.query,
query: &self.query,
open_entries: &self.open_buffers,
fs: fs.as_deref(),
confirm_contents_will_match_tx: &confirm_contents_will_match_tx,

View File

@@ -4837,7 +4837,7 @@ impl ProjectPanel {
.collect::<Vec<_>>();
let active_index = folded_ancestors.active_index();
let components_len = components.len();
let delimiter = SharedString::new(path_style.separator());
let delimiter = SharedString::new(path_style.primary_separator());
for (index, component) in components.iter().enumerate() {
if index != 0 {
let delimiter_target_index = index - 1;
@@ -5765,7 +5765,7 @@ impl Render for ProjectPanel {
ListHorizontalSizingBehavior::Unconstrained,
)
.with_width_from_item(self.state.max_width_item_index)
.track_scroll(self.scroll_handle.clone()),
.track_scroll(&self.scroll_handle),
)
.child(
div()
@@ -5908,7 +5908,7 @@ impl Render for ProjectPanel {
)
.custom_scrollbars(
Scrollbars::for_settings::<ProjectPanelSettings>()
.tracked_scroll_handle(self.scroll_handle.clone())
.tracked_scroll_handle(&self.scroll_handle)
.with_track_along(
ScrollAxes::Horizontal,
cx.theme().colors().panel_background,

View File

@@ -2160,7 +2160,7 @@ impl RemoteServerProjects {
)
.size_full(),
)
.vertical_scrollbar_for(state.scroll_handle, window, cx),
.vertical_scrollbar_for(&state.scroll_handle, window, cx),
),
)
.into_any_element()

View File

@@ -607,7 +607,10 @@ pub fn open_settings_editor(
window_background: cx.theme().window_background_appearance(),
app_id: Some(app_id.to_owned()),
window_decorations: Some(window_decorations),
window_min_size: Some(scaled_bounds),
window_min_size: Some(gpui::Size {
width: px(360.0),
height: px(240.0),
}),
window_bounds: Some(WindowBounds::centered(scaled_bounds, cx)),
..Default::default()
},
@@ -2189,7 +2192,7 @@ impl SettingsWindow {
format!(
"{}{}{}",
directory_name,
path_style.separator(),
path_style.primary_separator(),
path.display(path_style)
)
}
@@ -2452,9 +2455,9 @@ impl SettingsWindow {
}),
)
.size_full()
.track_scroll(self.navbar_scroll_handle.clone()),
.track_scroll(&self.navbar_scroll_handle),
)
.vertical_scrollbar_for(self.navbar_scroll_handle.clone(), window, cx),
.vertical_scrollbar_for(&self.navbar_scroll_handle, window, cx),
)
.child(
h_flex()
@@ -3009,10 +3012,10 @@ impl SettingsWindow {
window.focus_prev();
}))
.when(sub_page_stack().is_empty(), |this| {
this.vertical_scrollbar_for(self.list_state.clone(), window, cx)
this.vertical_scrollbar_for(&self.list_state, window, cx)
})
.when(!sub_page_stack().is_empty(), |this| {
this.vertical_scrollbar_for(self.sub_page_scroll_handle.clone(), window, cx)
this.vertical_scrollbar_for(&self.sub_page_scroll_handle, window, cx)
})
.track_focus(&self.content_focus_handle.focus_handle(cx))
.pt_6()

View File

@@ -1118,7 +1118,7 @@ impl Render for TerminalView {
ScrollAxes::Vertical,
cx.theme().colors().editor_background,
)
.tracked_scroll_handle(self.scroll_handle.clone()),
.tracked_scroll_handle(&self.scroll_handle),
window,
cx,
)

View File

@@ -876,7 +876,7 @@ impl ToolchainSelectorDelegate {
.strip_prefix(&worktree_root)
.ok()
.and_then(|suffix| suffix.to_str())
.map(|suffix| format!(".{}{suffix}", path_style.separator()).into())
.map(|suffix| format!(".{}{suffix}", path_style.primary_separator()).into())
.unwrap_or(path)
}
}

View File

@@ -872,7 +872,7 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
interaction_state.as_ref(),
|this, state| {
this.track_scroll(
state.read_with(cx, |s, _| s.scroll_handle.clone()),
&state.read_with(cx, |s, _| s.scroll_handle.clone()),
)
},
),
@@ -906,7 +906,7 @@ impl<const COLS: usize> RenderOnce for Table<COLS> {
.unwrap_or_else(|| Scrollbars::new(super::ScrollAxes::Both));
content
.custom_scrollbars(
scrollbars.tracked_scroll_handle(state.read(cx).scroll_handle.clone()),
scrollbars.tracked_scroll_handle(&state.read(cx).scroll_handle),
window,
cx,
)

View File

@@ -150,9 +150,9 @@ pub trait WithScrollbar: Sized {
// }
#[track_caller]
fn vertical_scrollbar_for<ScrollHandle: ScrollableHandle>(
fn vertical_scrollbar_for<ScrollHandle: ScrollableHandle + Clone>(
self,
scroll_handle: ScrollHandle,
scroll_handle: &ScrollHandle,
window: &mut Window,
cx: &mut App,
) -> Self::Output {
@@ -441,7 +441,7 @@ impl<ScrollHandle: ScrollableHandle> Scrollbars<ScrollHandle> {
pub fn tracked_scroll_handle<TrackedHandle: ScrollableHandle>(
self,
tracked_scroll_handle: TrackedHandle,
tracked_scroll_handle: &TrackedHandle,
) -> Scrollbars<TrackedHandle> {
let Self {
id,
@@ -454,7 +454,7 @@ impl<ScrollHandle: ScrollableHandle> Scrollbars<ScrollHandle> {
} = self;
Scrollbars {
scrollable_handle: Handle::Tracked(tracked_scroll_handle),
scrollable_handle: Handle::Tracked(tracked_scroll_handle.clone()),
id,
tracked_entity: tracked_entity_id,
visibility,
@@ -968,7 +968,7 @@ impl ScrollableHandle for ScrollHandle {
}
}
pub trait ScrollableHandle: 'static + Any + Sized {
pub trait ScrollableHandle: 'static + Any + Sized + Clone {
fn max_offset(&self) -> Size<Pixels>;
fn set_offset(&self, point: Point<Pixels>);
fn offset(&self) -> Point<Pixels>;

View File

@@ -24,8 +24,8 @@ impl TabBar {
}
}
pub fn track_scroll(mut self, scroll_handle: ScrollHandle) -> Self {
self.scroll_handle = Some(scroll_handle);
pub fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
self.scroll_handle = Some(scroll_handle.clone());
self
}

View File

@@ -3,6 +3,7 @@ use globset::{Glob, GlobSet, GlobSetBuilder};
use itertools::Itertools;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::cmp::Ordering;
use std::error::Error;
use std::fmt::{Display, Formatter};
@@ -331,13 +332,20 @@ impl PathStyle {
}
#[inline]
pub fn separator(&self) -> &'static str {
pub fn primary_separator(&self) -> &'static str {
match self {
PathStyle::Posix => "/",
PathStyle::Windows => "\\",
}
}
pub fn separators(&self) -> &'static [&'static str] {
match self {
PathStyle::Posix => &["/"],
PathStyle::Windows => &["\\", "/"],
}
}
pub fn is_windows(&self) -> bool {
*self == PathStyle::Windows
}
@@ -353,25 +361,54 @@ impl PathStyle {
} else {
Some(format!(
"{left}{}{right}",
if left.ends_with(self.separator()) {
if left.ends_with(self.primary_separator()) {
""
} else {
self.separator()
self.primary_separator()
}
))
}
}
pub fn split(self, path_like: &str) -> (Option<&str>, &str) {
let Some(pos) = path_like.rfind(self.separator()) else {
let Some(pos) = path_like.rfind(self.primary_separator()) else {
return (None, path_like);
};
let filename_start = pos + self.separator().len();
let filename_start = pos + self.primary_separator().len();
(
Some(&path_like[..filename_start]),
&path_like[filename_start..],
)
}
pub fn strip_prefix<'a>(
&self,
child: &'a Path,
parent: &'a Path,
) -> Option<std::borrow::Cow<'a, RelPath>> {
let parent = parent.to_str()?;
if parent.is_empty() {
return RelPath::new(child, *self).ok();
}
let parent = self
.separators()
.iter()
.find_map(|sep| parent.strip_suffix(sep))
.unwrap_or(parent);
let child = child.to_str()?;
let stripped = child.strip_prefix(parent)?;
if let Some(relative) = self
.separators()
.iter()
.find_map(|sep| stripped.strip_prefix(sep))
{
RelPath::new(relative.as_ref(), *self).ok()
} else if stripped.is_empty() {
Some(Cow::Borrowed(RelPath::empty()))
} else {
None
}
}
}
#[derive(Debug, Clone)]
@@ -788,7 +825,7 @@ impl PathMatcher {
fn check_with_end_separator(&self, path: &Path) -> bool {
let path_str = path.to_string_lossy();
let separator = self.path_style.separator();
let separator = self.path_style.primary_separator();
if path_str.ends_with(separator) {
false
} else {
@@ -1311,6 +1348,8 @@ impl WslPath {
#[cfg(test)]
mod tests {
use crate::rel_path::rel_path;
use super::*;
use util_macros::perf;
@@ -2480,6 +2519,89 @@ mod tests {
assert_eq!(strip_path_suffix(base, suffix), None);
}
#[test]
fn test_strip_prefix() {
let expected = [
(
PathStyle::Posix,
"/a/b/c",
"/a/b",
Some(rel_path("c").into_arc()),
),
(
PathStyle::Posix,
"/a/b/c",
"/a/b/",
Some(rel_path("c").into_arc()),
),
(
PathStyle::Posix,
"/a/b/c",
"/",
Some(rel_path("a/b/c").into_arc()),
),
(PathStyle::Posix, "/a/b/c", "", None),
(PathStyle::Posix, "/a/b//c", "/a/b/", None),
(PathStyle::Posix, "/a/bc", "/a/b", None),
(
PathStyle::Posix,
"/a/b/c",
"/a/b/c",
Some(rel_path("").into_arc()),
),
(
PathStyle::Windows,
"C:\\a\\b\\c",
"C:\\a\\b",
Some(rel_path("c").into_arc()),
),
(
PathStyle::Windows,
"C:\\a\\b\\c",
"C:\\a\\b\\",
Some(rel_path("c").into_arc()),
),
(
PathStyle::Windows,
"C:\\a\\b\\c",
"C:\\",
Some(rel_path("a/b/c").into_arc()),
),
(PathStyle::Windows, "C:\\a\\b\\c", "", None),
(PathStyle::Windows, "C:\\a\\b\\\\c", "C:\\a\\b\\", None),
(PathStyle::Windows, "C:\\a\\bc", "C:\\a\\b", None),
(
PathStyle::Windows,
"C:\\a\\b/c",
"C:\\a\\b",
Some(rel_path("c").into_arc()),
),
(
PathStyle::Windows,
"C:\\a\\b/c",
"C:\\a\\b\\",
Some(rel_path("c").into_arc()),
),
(
PathStyle::Windows,
"C:\\a\\b/c",
"C:\\a\\b/",
Some(rel_path("c").into_arc()),
),
];
let actual = expected.clone().map(|(style, child, parent, _)| {
(
style,
child,
parent,
style
.strip_prefix(child.as_ref(), parent.as_ref())
.map(|rel_path| rel_path.into_arc()),
)
});
pretty_assertions::assert_eq!(actual, expected);
}
#[cfg(target_os = "windows")]
#[test]
fn test_wsl_path() {

View File

@@ -965,7 +965,7 @@ impl VimCommand {
}
};
let rel_path = if args.ends_with(PathStyle::local().separator()) {
let rel_path = if args.ends_with(PathStyle::local().primary_separator()) {
rel_path
} else {
rel_path
@@ -998,7 +998,7 @@ impl VimCommand {
.display(PathStyle::local())
.to_string();
if dir.is_dir {
path_string.push_str(PathStyle::local().separator());
path_string.push_str(PathStyle::local().primary_separator());
}
path_string
})

View File

@@ -789,7 +789,7 @@ pub mod simple_message_notification {
.track_scroll(&self.scroll_handle.clone())
.child((self.build_content)(window, cx)),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
.vertical_scrollbar_for(&self.scroll_handle, window, cx),
)
.show_close_button(self.show_close_button)
.show_suppress_button(self.show_suppress_button)

View File

@@ -999,7 +999,7 @@ impl Worktree {
};
if worktree_relative_path.components().next().is_some() {
full_path_string.push_str(self.path_style.separator());
full_path_string.push_str(self.path_style.primary_separator());
full_path_string.push_str(&worktree_relative_path.display(self.path_style));
}
@@ -2108,8 +2108,8 @@ impl Snapshot {
if path.file_name().is_some() {
let mut abs_path = self.abs_path.to_string();
for component in path.components() {
if !abs_path.ends_with(self.path_style.separator()) {
abs_path.push_str(self.path_style.separator());
if !abs_path.ends_with(self.path_style.primary_separator()) {
abs_path.push_str(self.path_style.primary_separator());
}
abs_path.push_str(component);
}

View File

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

View File

@@ -627,7 +627,7 @@ impl Render for ComponentPreview {
.collect()
}),
)
.track_scroll(self.nav_scroll_handle.clone())
.track_scroll(&self.nav_scroll_handle)
.p_2p5()
.w(px(231.)) // Matches perfectly with the size of the "Component Preview" tab, if that's the first one in the pane
.h_full()

View File

@@ -11,6 +11,7 @@ C support is available natively in Zed.
Clangd out of the box assumes mixed C++/C projects. If you have a C-only project you may wish to instruct clangd to treat all files as C using the `-xc` flag. To do this, create a `.clangd` file in the root of your project with the following:
```yaml
# yaml-language-server: $schema=https://json.schemastore.org/clangd.json
CompileFlags:
Add: [-xc]
```

View File

@@ -78,6 +78,7 @@ You can pass any number of arguments to clangd. To see a full set of available o
By default Zed will use the `clangd` language server for formatting C++ code. The Clangd is the same as the `clang-format` CLI tool. To configure this you can add a `.clang-format` file. For example:
```yaml
# yaml-language-server: $schema=https://json.schemastore.org/clang-format-21.x.json
---
BasedOnStyle: LLVM
IndentWidth: 4
@@ -106,7 +107,8 @@ You can trigger formatting via {#kb editor::Format} or the `editor: format` acti
In the root of your project, it is generally common to create a `.clangd` file to set extra configuration.
```text
```yaml
# yaml-language-server: $schema=https://json.schemastore.org/clangd.json
CompileFlags:
Add:
- "--include-directory=/path/to/include"

View File

@@ -4,7 +4,13 @@ How to use our internal tools to profile and keep Zed fast.
See what the CPU spends the most time on. Strongly recommend you use
[samply](https://github.com/mstange/samply). It opens an interactive profile in
the browser. See its README on how to install and run.
the browser (specifically a local instance of [firefox_profiler](https://profiler.firefox.com/)).
See [samply](https://github.com/mstange/samply)'s README on how to install and run.
The profile.json does not contain any symbols. Firefox profiler can add the local symbols to the profile for for. To do that hit the upload local profile button in the top right corner.
<img width="851" height="613" alt="image" src="https://github.com/user-attachments/assets/cbef2b51-0442-4ee9-bc5c-95f6ccf9be2c" />
# Task/Async profiling

View File

@@ -7,6 +7,7 @@ mod after_release;
mod cherry_pick;
mod compare_perf;
mod danger;
mod extension_bump;
mod extension_tests;
mod nix_build;
mod release_nightly;
@@ -44,6 +45,7 @@ pub fn run_workflows(_: GenerateWorkflowArgs) -> Result<()> {
("run_agent_evals.yml", run_agent_evals::run_agent_evals()),
("after_release.yml", after_release::after_release()),
("extension_tests.yml", extension_tests::extension_tests()),
("extension_bump.yml", extension_bump::extension_bump()),
];
fs::create_dir_all(dir)
.with_context(|| format!("Failed to create directory: {}", dir.display()))?;

View File

@@ -3,14 +3,14 @@ use gh_workflow::*;
use crate::tasks::workflows::{
runners,
steps::{self, NamedJob, named},
vars::{self, Input, StepOutput},
vars::{self, StepOutput, WorkflowInput},
};
pub fn cherry_pick() -> Workflow {
let branch = Input::string("branch", None);
let commit = Input::string("commit", None);
let channel = Input::string("channel", None);
let pr_number = Input::string("pr_number", None);
let branch = WorkflowInput::string("branch", None);
let commit = WorkflowInput::string("commit", None);
let channel = WorkflowInput::string("channel", None);
let pr_number = WorkflowInput::string("pr_number", None);
let cherry_pick = run_cherry_pick(&branch, &commit, &channel);
named::workflow()
.run_name(format!("cherry_pick to {channel} #{pr_number}"))
@@ -24,7 +24,11 @@ pub fn cherry_pick() -> Workflow {
.add_job(cherry_pick.name, cherry_pick.job)
}
fn run_cherry_pick(branch: &Input, commit: &Input, channel: &Input) -> NamedJob {
fn run_cherry_pick(
branch: &WorkflowInput,
commit: &WorkflowInput,
channel: &WorkflowInput,
) -> NamedJob {
fn authenticate_as_zippy() -> (Step<Use>, StepOutput) {
let step = named::uses(
"actions",
@@ -39,9 +43,9 @@ fn run_cherry_pick(branch: &Input, commit: &Input, channel: &Input) -> NamedJob
}
fn cherry_pick(
branch: &Input,
commit: &Input,
channel: &Input,
branch: &WorkflowInput,
commit: &WorkflowInput,
channel: &WorkflowInput,
token: &StepOutput,
) -> Step<Run> {
named::bash(&format!("./script/cherry-pick {branch} {commit} {channel}"))

View File

@@ -5,13 +5,13 @@ use crate::tasks::workflows::steps::FluentBuilder;
use crate::tasks::workflows::{
runners,
steps::{self, NamedJob, named},
vars::Input,
vars::WorkflowInput,
};
pub fn compare_perf() -> Workflow {
let head = Input::string("head", None);
let base = Input::string("base", None);
let crate_name = Input::string("crate_name", Some("".to_owned()));
let head = WorkflowInput::string("head", None);
let base = WorkflowInput::string("base", None);
let crate_name = WorkflowInput::string("crate_name", Some("".to_owned()));
let run_perf = run_perf(&base, &head, &crate_name);
named::workflow()
.on(Event::default().workflow_dispatch(
@@ -23,8 +23,12 @@ pub fn compare_perf() -> Workflow {
.add_job(run_perf.name, run_perf.job)
}
pub fn run_perf(base: &Input, head: &Input, crate_name: &Input) -> NamedJob {
fn cargo_perf_test(ref_name: &Input, crate_name: &Input) -> Step<Run> {
pub fn run_perf(
base: &WorkflowInput,
head: &WorkflowInput,
crate_name: &WorkflowInput,
) -> NamedJob {
fn cargo_perf_test(ref_name: &WorkflowInput, crate_name: &WorkflowInput) -> Step<Run> {
named::bash(&format!(
"
if [ -n \"{crate_name}\" ]; then
@@ -39,7 +43,7 @@ pub fn run_perf(base: &Input, head: &Input, crate_name: &Input) -> NamedJob {
named::uses("taiki-e", "install-action", "hyperfine")
}
fn compare_runs(head: &Input, base: &Input) -> Step<Run> {
fn compare_runs(head: &WorkflowInput, base: &WorkflowInput) -> Step<Run> {
named::bash(&format!(
"cargo perf-compare --save=results.md {base} {head}"
))

View File

@@ -0,0 +1,217 @@
use gh_workflow::*;
use indoc::indoc;
use crate::tasks::workflows::{
extension_tests::{self},
runners,
steps::{self, CommonJobConditions, DEFAULT_REPOSITORY_OWNER_GUARD, NamedJob, named},
vars::{
JobOutput, StepOutput, WorkflowInput, WorkflowSecret, one_workflow_per_non_main_branch,
},
};
const BUMPVERSION_CONFIG: &str = indoc! {r#"
[bumpversion]
current_version = "$OLD_VERSION"
[bumpversion:file:Cargo.toml]
[bumpversion:file:extension.toml]
"#
};
const VERSION_CHECK: &str = r#"sed -n 's/version = \"\(.*\)\"/\1/p' < extension.toml"#;
// This is used by various extensions repos in the zed-extensions org to bump extension versions.
pub(crate) fn extension_bump() -> Workflow {
let bump_type = WorkflowInput::string("bump-type", Some("patch".to_owned()));
let app_id = WorkflowSecret::new("app-id", "The app ID used to create the PR");
let app_secret =
WorkflowSecret::new("app-secret", "The app secret for the corresponding app ID");
let test_extension = extension_tests::check_extension();
let (check_bump_needed, needs_bump) = check_bump_needed();
let bump_version = bump_extension_version(
&[&test_extension, &check_bump_needed],
&bump_type,
needs_bump.as_job_output(&check_bump_needed),
&app_id,
&app_secret,
);
named::workflow()
.add_event(
Event::default().workflow_call(
WorkflowCall::default()
.add_input(bump_type.name, bump_type.call_input())
.secrets([
(app_id.name.to_owned(), app_id.secret_configuration()),
(
app_secret.name.to_owned(),
app_secret.secret_configuration(),
),
]),
),
)
.concurrency(one_workflow_per_non_main_branch())
.add_env(("CARGO_TERM_COLOR", "always"))
.add_env(("RUST_BACKTRACE", 1))
.add_env(("CARGO_INCREMENTAL", 0))
.add_env((
"ZED_EXTENSION_CLI_SHA",
extension_tests::ZED_EXTENSION_CLI_SHA,
))
.add_job(test_extension.name, test_extension.job)
.add_job(check_bump_needed.name, check_bump_needed.job)
.add_job(bump_version.name, bump_version.job)
}
fn check_bump_needed() -> (NamedJob, StepOutput) {
let (compare_versions, version_changed) = compare_versions();
let job = Job::default()
.with_repository_owner_guard()
.outputs([(version_changed.name.to_owned(), version_changed.to_string())])
.runs_on(runners::LINUX_SMALL)
.timeout_minutes(1u32)
.add_step(steps::checkout_repo().add_with(("fetch-depth", 10)))
.add_step(compare_versions);
(named::job(job), version_changed)
}
/// Compares the current and previous commit and checks whether versions changed inbetween.
fn compare_versions() -> (Step<Run>, StepOutput) {
let check_needs_bump = named::bash(format!(
indoc! {
r#"
CURRENT_VERSION="$({})"
git checkout "$(git log -1 --format=%H)"~1
PREV_COMMIT_VERSION="$({})"
[[ "$CURRENT_VERSION" == "$PREV_COMMIT_VERSION" ]] && \
echo "needs_bump=true" >> "$GITHUB_OUTPUT" || \
echo "needs_bump=false" >> "$GITHUB_OUTPUT"
"#
},
VERSION_CHECK, VERSION_CHECK
))
.id("compare-versions-check");
let needs_bump = StepOutput::new(&check_needs_bump, "needs_bump");
(check_needs_bump, needs_bump)
}
fn bump_extension_version(
dependencies: &[&NamedJob],
bump_type: &WorkflowInput,
needs_bump: JobOutput,
app_id: &WorkflowSecret,
app_secret: &WorkflowSecret,
) -> NamedJob {
let (generate_token, generated_token) = generate_token(app_id, app_secret);
let (bump_version, old_version, new_version) = bump_version(bump_type);
let job = steps::dependant_job(dependencies)
.cond(Expression::new(format!(
"{DEFAULT_REPOSITORY_OWNER_GUARD} && {} == 'true'",
needs_bump.expr(),
)))
.runs_on(runners::LINUX_LARGE)
.timeout_minutes(1u32)
.add_step(generate_token)
.add_step(steps::checkout_repo())
.add_step(install_bump_2_version())
.add_step(bump_version)
.add_step(create_pull_request(
old_version,
new_version,
generated_token,
));
named::job(job)
}
fn generate_token(app_id: &WorkflowSecret, app_secret: &WorkflowSecret) -> (Step<Use>, StepOutput) {
let step = named::uses("actions", "create-github-app-token", "v2")
.id("generate-token")
.add_with(
Input::default()
.add("app-id", app_id.to_string())
.add("private-key", app_secret.to_string()),
);
let generated_token = StepOutput::new(&step, "token");
(step, generated_token)
}
fn install_bump_2_version() -> Step<Run> {
named::run(runners::Platform::Linux, "pip install bump2version")
}
fn bump_version(bump_type: &WorkflowInput) -> (Step<Run>, StepOutput, StepOutput) {
let step = named::bash(format!(
indoc! {r#"
OLD_VERSION="$({})"
cat <<EOF > .bumpversion.cfg
{}
EOF
bump2version --verbose {}
NEW_VERSION="$({})"
cargo update --workspace
rm .bumpversion.cfg
echo "old_version=${{OLD_VERSION}}" >> "$GITHUB_OUTPUT"
echo "new_version=${{NEW_VERSION}}" >> "$GITHUB_OUTPUT"
"#
},
VERSION_CHECK, BUMPVERSION_CONFIG, bump_type, VERSION_CHECK
))
.id("bump-version");
let old_version = StepOutput::new(&step, "old_version");
let new_version = StepOutput::new(&step, "new_version");
(step, old_version, new_version)
}
fn create_pull_request(
old_version: StepOutput,
new_version: StepOutput,
generated_token: StepOutput,
) -> Step<Use> {
let formatted_version = format!("v{}", new_version);
named::uses("peter-evans", "create-pull-request", "v7").with(
Input::default()
.add("title", format!("Bump version to {}", new_version))
.add(
"body",
format!(
"This PR bumps the version of this extension to {}",
formatted_version
),
)
.add(
"commit-message",
format!("Bump version to {}", formatted_version),
)
.add("branch", format!("bump-from-{}", old_version))
.add(
"committer",
"zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>",
)
.add("base", "main")
.add("delete-branch", true)
.add("token", generated_token.to_string())
.add("sign-commits", true),
)
}

View File

@@ -9,7 +9,7 @@ use crate::tasks::workflows::{
};
const RUN_TESTS_INPUT: &str = "run_tests";
const ZED_EXTENSION_CLI_SHA: &str = "7cfce605704d41ca247e3f84804bf323f6c6caaf";
pub(crate) const ZED_EXTENSION_CLI_SHA: &str = "7cfce605704d41ca247e3f84804bf323f6c6caaf";
// This is used by various extensions repos in the zed-extensions org to run automated tests.
pub(crate) fn extension_tests() -> Workflow {
@@ -77,7 +77,7 @@ fn check_rust() -> NamedJob {
named::job(job)
}
fn check_extension() -> NamedJob {
pub(crate) fn check_extension() -> NamedJob {
let (cache_download, cache_hit) = cache_zed_extension_cli();
let job = Job::default()
.with_repository_owner_guard()

View File

@@ -3,12 +3,12 @@ use gh_workflow::{Event, Expression, Job, Run, Schedule, Step, Use, Workflow, Wo
use crate::tasks::workflows::{
runners::{self, Platform},
steps::{self, FluentBuilder as _, NamedJob, named, setup_cargo_config},
vars::{self, Input},
vars::{self, WorkflowInput},
};
pub(crate) fn run_agent_evals() -> Workflow {
let agent_evals = agent_evals();
let model_name = Input::string("model_name", None);
let model_name = WorkflowInput::string("model_name", None);
named::workflow()
.on(Event::default().workflow_dispatch(
@@ -29,8 +29,8 @@ pub(crate) fn run_agent_evals() -> Workflow {
}
pub(crate) fn run_unit_evals() -> Workflow {
let model_name = Input::string("model_name", None);
let commit_sha = Input::string("commit_sha", None);
let model_name = WorkflowInput::string("model_name", None);
let commit_sha = WorkflowInput::string("commit_sha", None);
let unit_evals = named::job(unit_evals(Some(&commit_sha)));
@@ -117,7 +117,7 @@ fn cron_unit_evals() -> NamedJob {
named::job(unit_evals(None).add_step(send_failure_to_slack()))
}
fn unit_evals(commit: Option<&Input>) -> Job {
fn unit_evals(commit: Option<&WorkflowInput>) -> Job {
let script_step = add_api_keys(steps::script("./script/run-unit-evals"));
Job::default()

View File

@@ -142,9 +142,13 @@ pub struct NamedJob {
// }
// }
pub(crate) const DEFAULT_REPOSITORY_OWNER_GUARD: &str =
"(github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions')";
pub fn repository_owner_guard_expression(trigger_always: bool) -> Expression {
Expression::new(format!(
"(github.repository_owner == 'zed-industries' || github.repository_owner == 'zed-extensions'){}",
"{}{}",
DEFAULT_REPOSITORY_OWNER_GUARD,
trigger_always.then_some(" && always()").unwrap_or_default()
))
}
@@ -248,8 +252,10 @@ pub mod named {
/// Returns a bash-script step with the same name as the enclosing function.
/// (You shouldn't inline this function into the workflow definition, you must
/// wrap it in a new function.)
pub fn bash(script: &str) -> Step<Run> {
Step::new(function_name(1)).run(script).shell(BASH_SHELL)
pub fn bash(script: impl AsRef<str>) -> Step<Run> {
Step::new(function_name(1))
.run(script.as_ref())
.shell(BASH_SHELL)
}
/// Returns a pwsh-script step with the same name as the enclosing function.

View File

@@ -1,6 +1,9 @@
use std::cell::RefCell;
use gh_workflow::{Concurrency, Env, Expression, Step, WorkflowDispatchInput};
use gh_workflow::{
Concurrency, Env, Expression, Step, WorkflowCallInput, WorkflowCallSecret,
WorkflowDispatchInput,
};
use crate::tasks::workflows::{runners::Platform, steps::NamedJob};
@@ -132,7 +135,7 @@ impl PathCondition {
}
pub(crate) struct StepOutput {
name: &'static str,
pub name: &'static str,
step_id: String,
}
@@ -151,6 +154,13 @@ impl StepOutput {
pub fn expr(&self) -> String {
format!("steps.{}.outputs.{}", self.step_id, self.name)
}
pub fn as_job_output(self, job: &NamedJob) -> JobOutput {
JobOutput {
job_name: job.name.clone(),
name: self.name,
}
}
}
impl serde::Serialize for StepOutput {
@@ -164,17 +174,43 @@ impl serde::Serialize for StepOutput {
impl std::fmt::Display for StepOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "${{{{ steps.{}.outputs.{} }}}}", self.step_id, self.name)
write!(f, "${{{{ {} }}}}", self.expr())
}
}
pub struct Input {
pub(crate) struct JobOutput {
job_name: String,
name: &'static str,
}
impl JobOutput {
pub fn expr(&self) -> String {
format!("needs.{}.outputs.{}", self.job_name, self.name)
}
}
impl serde::Serialize for JobOutput {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl std::fmt::Display for JobOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "${{{{ {} }}}}", self.expr())
}
}
pub struct WorkflowInput {
pub input_type: &'static str,
pub name: &'static str,
pub default: Option<String>,
}
impl Input {
impl WorkflowInput {
pub fn string(name: &'static str, default: Option<String>) -> Self {
Self {
input_type: "string",
@@ -191,15 +227,62 @@ impl Input {
default: self.default.clone(),
}
}
pub fn call_input(&self) -> WorkflowCallInput {
WorkflowCallInput {
description: self.name.to_owned(),
required: self.default.is_none(),
input_type: self.input_type.to_owned(),
default: self.default.clone(),
}
}
}
impl std::fmt::Display for Input {
impl std::fmt::Display for WorkflowInput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "${{{{ inputs.{} }}}}", self.name)
}
}
impl serde::Serialize for Input {
impl serde::Serialize for WorkflowInput {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
pub(crate) struct WorkflowSecret {
pub name: &'static str,
description: String,
required: bool,
}
impl WorkflowSecret {
pub fn new(name: &'static str, description: impl ToString) -> Self {
Self {
name,
description: description.to_string(),
required: true,
}
}
pub fn secret_configuration(&self) -> WorkflowCallSecret {
WorkflowCallSecret {
description: self.description.clone(),
required: self.required,
}
}
}
impl std::fmt::Display for WorkflowSecret {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "${{{{ secrets.{} }}}}", self.name)
}
}
impl serde::Serialize for WorkflowSecret {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,