Merge branch 'main' into edit-file-tool

This commit is contained in:
Agus Zubiaga 2025-08-08 02:34:11 -03:00
commit d52e0f47b5
30 changed files with 320 additions and 104 deletions

View file

@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.89-bookworm as builder
FROM rust:1.88-bookworm as builder
WORKDIR app
COPY . .

View file

@ -596,6 +596,8 @@
// when a corresponding project entry becomes active.
// Gitignored entries are never auto revealed.
"auto_reveal_entries": true,
// Whether the project panel should open on startup.
"starts_open": true,
// Whether to fold directories automatically and show compact folders
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
"auto_fold_dirs": true,
@ -1171,6 +1173,9 @@
// Sets a delay after which the inline blame information is shown.
// Delay is restarted with every cursor movement.
"delay_ms": 0,
// The amount of padding between the end of the source line and the start
// of the inline blame in units of em widths.
"padding": 7,
// Whether or not to display the git commit summary on the same line.
"show_commit_summary": false,
// The minimum column number to show the inline blame information at
@ -1233,6 +1238,11 @@
// 2. hour24
"hour_format": "hour12"
},
// Status bar-related settings.
"status_bar": {
// Whether to show the active language button in the status bar.
"active_language_button": true
},
// Settings specific to the terminal
"terminal": {
// What shell to use when opening a terminal. May take 3 values:

View file

@ -8,7 +8,6 @@ use agent_client_protocol as acp;
use anyhow::{Context as _, Result};
use assistant_tool::ActionLog;
use editor::Bias;
use futures::future::{Fuse, FusedFuture};
use futures::{FutureExt, channel::oneshot, future::BoxFuture};
use gpui::{AppContext, Context, Entity, EventEmitter, SharedString, Task};
use itertools::Itertools;
@ -482,7 +481,7 @@ pub struct AcpThread {
project: Entity<Project>,
action_log: Entity<ActionLog>,
shared_buffers: HashMap<Entity<Buffer>, BufferSnapshot>,
send_task: Option<Fuse<Task<()>>>,
send_task: Option<Task<()>>,
connection: Rc<dyn AgentConnection>,
session_id: acp::SessionId,
}
@ -572,11 +571,7 @@ impl AcpThread {
}
pub fn status(&self) -> ThreadStatus {
if self
.send_task
.as_ref()
.map_or(false, |t| !t.is_terminated())
{
if self.send_task.is_some() {
if self.waiting_for_tool_confirmation() {
ThreadStatus::WaitingForToolConfirmation
} else {
@ -966,31 +961,29 @@ impl AcpThread {
let (tx, rx) = oneshot::channel();
let cancel_task = self.cancel(cx);
self.send_task = Some(
cx.spawn(async move |this, cx| {
async {
cancel_task.await;
self.send_task = Some(cx.spawn(async move |this, cx| {
async {
cancel_task.await;
let result = this
.update(cx, |this, cx| {
this.connection.prompt(
acp::PromptRequest {
prompt: message,
session_id: this.session_id.clone(),
},
cx,
)
})?
.await;
let result = this
.update(cx, |this, cx| {
this.connection.prompt(
acp::PromptRequest {
prompt: message,
session_id: this.session_id.clone(),
},
cx,
)
})?
.await;
tx.send(result).log_err();
anyhow::Ok(())
}
.await
.log_err();
})
.fuse(),
);
tx.send(result).log_err();
anyhow::Ok(())
}
.await
.log_err();
}));
cx.spawn(async move |this, cx| match rx.await {
Ok(Err(e)) => {
@ -998,7 +991,23 @@ impl AcpThread {
.log_err();
Err(e)?
}
_ => {
result => {
let cancelled = matches!(
result,
Ok(Ok(acp::PromptResponse {
stop_reason: acp::StopReason::Cancelled
}))
);
// We only take the task if the current prompt wasn't cancelled.
//
// This prompt may have been cancelled because another one was sent
// while it was still generating. In these cases, dropping `send_task`
// would cause the next generation to be cancelled.
if !cancelled {
this.update(cx, |this, _cx| this.send_task.take()).ok();
}
this.update(cx, |_, cx| cx.emit(AcpThreadEvent::Stopped))
.log_err();
Ok(())

View file

@ -3101,9 +3101,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
// Turn inline-blame-off by default so no state is transferred without us explicitly doing so
let inline_blame_off_settings = Some(InlineBlameSettings {
enabled: false,
delay_ms: None,
min_column: None,
show_commit_summary: false,
..Default::default()
});
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {

View file

@ -20,6 +20,7 @@ pub struct EditorSettings {
pub lsp_highlight_debounce: u64,
pub hover_popover_enabled: bool,
pub hover_popover_delay: u64,
pub status_bar: StatusBar,
pub toolbar: Toolbar,
pub scrollbar: Scrollbar,
pub minimap: Minimap,
@ -125,6 +126,14 @@ pub struct JupyterContent {
pub enabled: Option<bool>,
}
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct StatusBar {
/// Whether to display the active language button in the status bar.
///
/// Default: true
pub active_language_button: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct Toolbar {
pub breadcrumbs: bool,
@ -440,6 +449,8 @@ pub struct EditorSettingsContent {
///
/// Default: 300
pub hover_popover_delay: Option<u64>,
/// Status bar related settings
pub status_bar: Option<StatusBarContent>,
/// Toolbar related settings
pub toolbar: Option<ToolbarContent>,
/// Scrollbar related settings
@ -567,6 +578,15 @@ pub struct EditorSettingsContent {
pub lsp_document_colors: Option<DocumentColorsRenderMode>,
}
// Status bar related settings
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct StatusBarContent {
/// Whether to display the active language button in the status bar.
///
/// Default: true
pub active_language_button: Option<bool>,
}
// Toolbar related settings
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ToolbarContent {

View file

@ -86,8 +86,6 @@ use util::post_inc;
use util::{RangeExt, ResultExt, debug_panic};
use workspace::{CollaboratorId, Workspace, item::Item, notifications::NotifyTaskExt};
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
/// Determines what kinds of highlights should be applied to a lines background.
#[derive(Clone, Copy, Default)]
struct LineHighlightSpec {
@ -2428,10 +2426,13 @@ impl EditorElement {
let editor = self.editor.read(cx);
let blame = editor.blame.clone()?;
let padding = {
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
const INLINE_ACCEPT_SUGGESTION_EM_WIDTHS: f32 = 14.;
let mut padding = INLINE_BLAME_PADDING_EM_WIDTHS;
let mut padding = ProjectSettings::get_global(cx)
.git
.inline_blame
.unwrap_or_default()
.padding as f32;
if let Some(edit_prediction) = editor.active_edit_prediction.as_ref() {
match &edit_prediction.completion {
@ -2469,7 +2470,7 @@ impl EditorElement {
let min_column_in_pixels = ProjectSettings::get_global(cx)
.git
.inline_blame
.and_then(|settings| settings.min_column)
.map(|settings| settings.min_column)
.map(|col| self.column_pixels(col as usize, window))
.unwrap_or(px(0.));
let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
@ -8030,12 +8031,20 @@ impl Element for EditorElement {
autoscroll_containing_element,
needs_horizontal_autoscroll,
) = self.editor.update(cx, |editor, cx| {
let autoscroll_request = editor.autoscroll_request();
let autoscroll_request = editor.scroll_manager.take_autoscroll_request();
let autoscroll_containing_element =
autoscroll_request.is_some() || editor.has_pending_selection();
let (needs_horizontal_autoscroll, was_scrolled) = editor
.autoscroll_vertically(bounds, line_height, max_scroll_top, window, cx);
.autoscroll_vertically(
bounds,
line_height,
max_scroll_top,
autoscroll_request,
window,
cx,
);
if was_scrolled.0 {
snapshot = editor.snapshot(window, cx);
}
@ -8357,7 +8366,13 @@ impl Element for EditorElement {
})
.flatten()?;
let mut element = render_inline_blame_entry(blame_entry, &style, cx)?;
let inline_blame_padding = INLINE_BLAME_PADDING_EM_WIDTHS * em_advance;
let inline_blame_padding = ProjectSettings::get_global(cx)
.git
.inline_blame
.unwrap_or_default()
.padding
as f32
* em_advance;
Some(
element
.layout_as_root(AvailableSpace::min_size(), window, cx)
@ -8425,7 +8440,11 @@ impl Element for EditorElement {
Ok(blocks) => blocks,
Err(resized_blocks) => {
self.editor.update(cx, |editor, cx| {
editor.resize_blocks(resized_blocks, autoscroll_request, cx)
editor.resize_blocks(
resized_blocks,
autoscroll_request.map(|(autoscroll, _)| autoscroll),
cx,
)
});
return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
}
@ -8470,6 +8489,7 @@ impl Element for EditorElement {
scroll_width,
em_advance,
&line_layouts,
autoscroll_request,
window,
cx,
)

View file

@ -348,8 +348,8 @@ impl ScrollManager {
self.show_scrollbars
}
pub fn autoscroll_request(&self) -> Option<Autoscroll> {
self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
self.autoscroll_request.take()
}
pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {

View file

@ -102,15 +102,12 @@ impl AutoscrollStrategy {
pub(crate) struct NeedsHorizontalAutoscroll(pub(crate) bool);
impl Editor {
pub fn autoscroll_request(&self) -> Option<Autoscroll> {
self.scroll_manager.autoscroll_request()
}
pub(crate) fn autoscroll_vertically(
&mut self,
bounds: Bounds<Pixels>,
line_height: Pixels,
max_scroll_top: f32,
autoscroll_request: Option<(Autoscroll, bool)>,
window: &mut Window,
cx: &mut Context<Editor>,
) -> (NeedsHorizontalAutoscroll, WasScrolled) {
@ -137,7 +134,7 @@ impl Editor {
WasScrolled(false)
};
let Some((autoscroll, local)) = self.scroll_manager.autoscroll_request.take() else {
let Some((autoscroll, local)) = autoscroll_request else {
return (NeedsHorizontalAutoscroll(false), editor_was_scrolled);
};
@ -284,9 +281,12 @@ impl Editor {
scroll_width: Pixels,
em_advance: Pixels,
layouts: &[LineWithInvisibles],
autoscroll_request: Option<(Autoscroll, bool)>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<gpui::Point<f32>> {
let (_, local) = autoscroll_request?;
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(cx);
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
@ -335,10 +335,10 @@ impl Editor {
let was_scrolled = if target_left < scroll_left {
scroll_position.x = target_left / em_advance;
self.set_scroll_position_internal(scroll_position, true, true, window, cx)
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
} else if target_right > scroll_right {
scroll_position.x = (target_right - viewport_width) / em_advance;
self.set_scroll_position_internal(scroll_position, true, true, window, cx)
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
} else {
WasScrolled(false)
};

View file

@ -402,11 +402,11 @@ impl GitRepository for FakeGitRepository {
&self,
_paths: Vec<RepoPath>,
_env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
) -> BoxFuture<Result<()>> {
unimplemented!()
}
fn stash_pop(&self, _env: Arc<HashMap<String, String>>) -> BoxFuture<'_, Result<()>> {
fn stash_pop(&self, _env: Arc<HashMap<String, String>>) -> BoxFuture<Result<()>> {
unimplemented!()
}

View file

@ -399,9 +399,9 @@ pub trait GitRepository: Send + Sync {
&self,
paths: Vec<RepoPath>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>>;
) -> BoxFuture<Result<()>>;
fn stash_pop(&self, env: Arc<HashMap<String, String>>) -> BoxFuture<'_, Result<()>>;
fn stash_pop(&self, env: Arc<HashMap<String, String>>) -> BoxFuture<Result<()>>;
fn push(
&self,
@ -1203,7 +1203,7 @@ impl GitRepository for RealGitRepository {
&self,
paths: Vec<RepoPath>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
) -> BoxFuture<Result<()>> {
let working_directory = self.working_directory();
self.executor
.spawn(async move {
@ -1227,7 +1227,7 @@ impl GitRepository for RealGitRepository {
.boxed()
}
fn stash_pop(&self, env: Arc<HashMap<String, String>>) -> BoxFuture<'_, Result<()>> {
fn stash_pop(&self, env: Arc<HashMap<String, String>>) -> BoxFuture<Result<()>> {
let working_directory = self.working_directory();
self.executor
.spawn(async move {

View file

@ -1,12 +1,22 @@
use std::str::FromStr;
use std::sync::LazyLock;
use regex::Regex;
use url::Url;
use git::{
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
RemoteUrl,
PullRequest, RemoteUrl,
};
fn pull_request_regex() -> &'static Regex {
static PULL_REQUEST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
// This matches Bitbucket PR reference pattern: (pull request #xxx)
Regex::new(r"\(pull request #(\d+)\)").unwrap()
});
&PULL_REQUEST_REGEX
}
pub struct Bitbucket {
name: String,
base_url: Url,
@ -96,6 +106,22 @@ impl GitHostingProvider for Bitbucket {
);
permalink
}
fn extract_pull_request(&self, remote: &ParsedGitRemote, message: &str) -> Option<PullRequest> {
// Check first line of commit message for PR references
let first_line = message.lines().next()?;
// Try to match against our PR patterns
let capture = pull_request_regex().captures(first_line)?;
let number = capture.get(1)?.as_str().parse::<u32>().ok()?;
// Construct the PR URL in Bitbucket format
let mut url = self.base_url();
let path = format!("/{}/{}/pull-requests/{}", remote.owner, remote.repo, number);
url.set_path(&path);
Some(PullRequest { number, url })
}
}
#[cfg(test)]
@ -203,4 +229,34 @@ mod tests {
"https://bitbucket.org/zed-industries/zed/src/f00b4r/main.rs#lines-24:48";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_bitbucket_pull_requests() {
use indoc::indoc;
let remote = ParsedGitRemote {
owner: "zed-industries".into(),
repo: "zed".into(),
};
let bitbucket = Bitbucket::public_instance();
// Test message without PR reference
let message = "This does not contain a pull request";
assert!(bitbucket.extract_pull_request(&remote, message).is_none());
// Pull request number at end of first line
let message = indoc! {r#"
Merged in feature-branch (pull request #123)
Some detailed description of the changes.
"#};
let pr = bitbucket.extract_pull_request(&remote, message).unwrap();
assert_eq!(pr.number, 123);
assert_eq!(
pr.url.as_str(),
"https://bitbucket.org/zed-industries/zed/pull-requests/123"
);
}
}

View file

@ -461,8 +461,6 @@ fn skip_whitespace(source: &str) -> &str {
#[cfg(test)]
mod tests {
use core::slice;
use super::*;
use crate as gpui;
use KeyBindingContextPredicate::*;
@ -676,11 +674,11 @@ mod tests {
assert!(predicate.eval(&contexts));
assert!(!predicate.eval(&[]));
assert!(!predicate.eval(slice::from_ref(&child_context)));
assert!(!predicate.eval(&[child_context.clone()]));
assert!(!predicate.eval(&[parent_context]));
let zany_predicate = KeyBindingContextPredicate::parse("child > child").unwrap();
assert!(!zany_predicate.eval(slice::from_ref(&child_context)));
assert!(!zany_predicate.eval(&[child_context.clone()]));
assert!(zany_predicate.eval(&[child_context.clone(), child_context.clone()]));
}
@ -692,13 +690,13 @@ mod tests {
let parent_context = KeyContext::try_from("parent").unwrap();
let child_context = KeyContext::try_from("child").unwrap();
assert!(not_predicate.eval(slice::from_ref(&workspace_context)));
assert!(!not_predicate.eval(slice::from_ref(&editor_context)));
assert!(not_predicate.eval(&[workspace_context.clone()]));
assert!(!not_predicate.eval(&[editor_context.clone()]));
assert!(!not_predicate.eval(&[editor_context.clone(), workspace_context.clone()]));
assert!(!not_predicate.eval(&[workspace_context.clone(), editor_context.clone()]));
let complex_not = KeyBindingContextPredicate::parse("!editor && workspace").unwrap();
assert!(complex_not.eval(slice::from_ref(&workspace_context)));
assert!(complex_not.eval(&[workspace_context.clone()]));
assert!(!complex_not.eval(&[editor_context.clone(), workspace_context.clone()]));
let not_mode_predicate = KeyBindingContextPredicate::parse("!(mode == full)").unwrap();
@ -711,18 +709,18 @@ mod tests {
assert!(not_mode_predicate.eval(&[other_mode_context]));
let not_descendant = KeyBindingContextPredicate::parse("!(parent > child)").unwrap();
assert!(not_descendant.eval(slice::from_ref(&parent_context)));
assert!(not_descendant.eval(slice::from_ref(&child_context)));
assert!(not_descendant.eval(&[parent_context.clone()]));
assert!(not_descendant.eval(&[child_context.clone()]));
assert!(!not_descendant.eval(&[parent_context.clone(), child_context.clone()]));
let not_descendant = KeyBindingContextPredicate::parse("parent > !child").unwrap();
assert!(!not_descendant.eval(slice::from_ref(&parent_context)));
assert!(!not_descendant.eval(slice::from_ref(&child_context)));
assert!(!not_descendant.eval(&[parent_context.clone()]));
assert!(!not_descendant.eval(&[child_context.clone()]));
assert!(!not_descendant.eval(&[parent_context.clone(), child_context.clone()]));
let double_not = KeyBindingContextPredicate::parse("!!editor").unwrap();
assert!(double_not.eval(slice::from_ref(&editor_context)));
assert!(!double_not.eval(slice::from_ref(&workspace_context)));
assert!(double_not.eval(&[editor_context.clone()]));
assert!(!double_not.eval(&[workspace_context.clone()]));
// Test complex descendant cases
let workspace_context = KeyContext::try_from("Workspace").unwrap();
@ -756,9 +754,9 @@ mod tests {
// !Workspace - shouldn't match when Workspace is in the context
let not_workspace = KeyBindingContextPredicate::parse("!Workspace").unwrap();
assert!(!not_workspace.eval(slice::from_ref(&workspace_context)));
assert!(not_workspace.eval(slice::from_ref(&pane_context)));
assert!(not_workspace.eval(slice::from_ref(&editor_context)));
assert!(!not_workspace.eval(&[workspace_context.clone()]));
assert!(not_workspace.eval(&[pane_context.clone()]));
assert!(not_workspace.eval(&[editor_context.clone()]));
assert!(!not_workspace.eval(&workspace_pane_editor));
}
}

View file

@ -1,6 +1,28 @@
use std::ops::Deref;
use windows::Win32::UI::WindowsAndMessaging::HCURSOR;
use windows::Win32::{Foundation::HANDLE, UI::WindowsAndMessaging::HCURSOR};
#[derive(Debug, Clone, Copy)]
pub(crate) struct SafeHandle {
raw: HANDLE,
}
unsafe impl Send for SafeHandle {}
unsafe impl Sync for SafeHandle {}
impl From<HANDLE> for SafeHandle {
fn from(value: HANDLE) -> Self {
SafeHandle { raw: value }
}
}
impl Deref for SafeHandle {
type Target = HANDLE;
fn deref(&self) -> &Self::Target {
&self.raw
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SafeCursor {

View file

@ -1,8 +1,9 @@
use editor::Editor;
use editor::{Editor, EditorSettings};
use gpui::{
Context, Entity, IntoElement, ParentElement, Render, Subscription, WeakEntity, Window, div,
};
use language::LanguageName;
use settings::Settings as _;
use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip};
use workspace::{StatusItemView, Workspace, item::ItemHandle};
@ -39,6 +40,13 @@ impl ActiveBufferLanguage {
impl Render for ActiveBufferLanguage {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if !EditorSettings::get_global(cx)
.status_bar
.active_language_button
{
return div();
}
div().when_some(self.active_language.as_ref(), |el, active_language| {
let active_language_text = if let Some(active_language_text) = active_language {
active_language_text.to_string()

View file

@ -1,6 +1,6 @@
name = "C++"
grammar = "cpp"
path_suffixes = ["cc", "hh", "cpp", "h", "hpp", "cxx", "hxx", "c++", "ipp", "inl", "ixx", "cu", "cuh", "C", "H"]
path_suffixes = ["cc", "hh", "cpp", "h", "hpp", "cxx", "hxx", "c++", "ipp", "inl", "ino", "ixx", "cu", "cuh", "C", "H"]
line_comments = ["// ", "/// ", "//! "]
decrease_indent_patterns = [
{ pattern = "^\\s*\\{.*\\}?\\s*$", valid_after = ["if", "for", "while", "do", "switch", "else"] },

View file

@ -146,6 +146,7 @@
"&&="
"||="
"??="
"..."
] @operator
(regex "/" @string.regex)

View file

@ -146,6 +146,7 @@
"&&="
"||="
"??="
"..."
] @operator
(regex "/" @string.regex)

View file

@ -167,6 +167,7 @@
"&&="
"||="
"??="
"..."
] @operator
(regex "/" @string.regex)

View file

@ -431,10 +431,9 @@ impl GitSettings {
pub fn inline_blame_delay(&self) -> Option<Duration> {
match self.inline_blame {
Some(InlineBlameSettings {
delay_ms: Some(delay_ms),
..
}) if delay_ms > 0 => Some(Duration::from_millis(delay_ms)),
Some(InlineBlameSettings { delay_ms, .. }) if delay_ms > 0 => {
Some(Duration::from_millis(delay_ms))
}
_ => None,
}
}
@ -470,7 +469,7 @@ pub enum GitGutterSetting {
Hide,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InlineBlameSettings {
/// Whether or not to show git blame data inline in
@ -483,11 +482,19 @@ pub struct InlineBlameSettings {
/// after a delay once the cursor stops moving.
///
/// Default: 0
pub delay_ms: Option<u64>,
#[serde(default)]
pub delay_ms: u64,
/// The amount of padding between the end of the source line and the start
/// of the inline blame in units of columns.
///
/// Default: 7
#[serde(default = "default_inline_blame_padding")]
pub padding: u32,
/// The minimum column number to show the inline blame information at
///
/// Default: 0
pub min_column: Option<u32>,
#[serde(default)]
pub min_column: u32,
/// Whether to show commit summary as part of the inline blame.
///
/// Default: false
@ -495,6 +502,22 @@ pub struct InlineBlameSettings {
pub show_commit_summary: bool,
}
fn default_inline_blame_padding() -> u32 {
7
}
impl Default for InlineBlameSettings {
fn default() -> Self {
Self {
enabled: true,
delay_ms: 0,
padding: default_inline_blame_padding(),
min_column: 0,
show_commit_summary: false,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct BinarySettings {
pub path: Option<String>,

View file

@ -5625,6 +5625,10 @@ impl Panel for ProjectPanel {
}
fn starts_open(&self, _: &Window, cx: &App) -> bool {
if !ProjectPanelSettings::get_global(cx).starts_open {
return false;
}
let project = &self.project.read(cx);
project.visible_worktrees(cx).any(|tree| {
tree.read(cx)

View file

@ -43,6 +43,7 @@ pub struct ProjectPanelSettings {
pub sticky_scroll: bool,
pub auto_reveal_entries: bool,
pub auto_fold_dirs: bool,
pub starts_open: bool,
pub scrollbar: ScrollbarSettings,
pub show_diagnostics: ShowDiagnostics,
pub hide_root: bool,
@ -139,6 +140,10 @@ pub struct ProjectPanelSettingsContent {
///
/// Default: true
pub auto_fold_dirs: Option<bool>,
/// Whether the project panel should open on startup.
///
/// Default: true
pub starts_open: Option<bool>,
/// Scrollbar-related settings
pub scrollbar: Option<ScrollbarSettingsContent>,
/// Which files containing diagnostic errors/warnings to mark in the project panel.

View file

@ -136,7 +136,7 @@ impl BatchedTextRun {
.shape_line(
self.text.clone().into(),
self.font_size.to_pixels(window.rem_size()),
std::slice::from_ref(&self.style),
&[self.style.clone()],
Some(dimensions.cell_width),
)
.paint(pos, dimensions.line_height, window, cx);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Before After
Before After

View file

@ -1275,6 +1275,18 @@ Each option controls displaying of a particular toolbar element. If all elements
`boolean` values
## Status Bar
- Description: Control various elements in the status bar. Note that some items in the status bar have their own settings set elsewhere.
- Setting: `status_bar`
- Default:
```json
"status_bar": {
"active_language_button": true,
},
```
## LSP
- Description: Configuration for language servers.
@ -1795,7 +1807,6 @@ Example:
{
"git": {
"inline_blame": {
"enabled": true,
"delay_ms": 500
}
}
@ -1808,7 +1819,6 @@ Example:
{
"git": {
"inline_blame": {
"enabled": true,
"show_commit_summary": true
}
}
@ -1821,13 +1831,24 @@ Example:
{
"git": {
"inline_blame": {
"enabled": true,
"min_column": 80
}
}
}
```
5. Set the padding between the end of the line and the inline blame hint, in ems:
```json
{
"git": {
"inline_blame": {
"padding": 10
}
}
}
```
### Hunk Style
- Description: What styling we should use for the diff hunks.
@ -3204,7 +3225,8 @@ Run the `theme selector: toggle` action in the command palette to see a current
"indent_guides": {
"show": "always"
},
"hide_root": false
"hide_root": false,
"starts_open": true
}
}
```

View file

@ -223,6 +223,7 @@ TBD: Centered layout related settings
"enabled": true, // Show/hide inline blame
"delay": 0, // Show after delay (ms)
"min_column": 0, // Minimum column to inline display blame
"padding": 7, // Padding between code and inline blame (em)
"show_commit_summary": false // Show/hide commit summary
},
"hunk_style": "staged_hollow" // staged_hollow, unstaged_hollow
@ -305,6 +306,17 @@ TBD: Centered layout related settings
}
```
### Status Bar
```json
"status_bar": {
// Show/hide a button that displays the active buffer's language.
// Clicking the button brings up the language selector.
// Defaults to true.
"active_language_button": true,
},
```
### Multibuffer
```json

View file

@ -9,7 +9,7 @@ repository = "https://github.com/zed-industries/zed"
[language_servers.emmet-language-server]
name = "Emmet Language Server"
language = "HTML"
languages = ["HTML", "PHP", "ERB", "HTML/ERB", "JavaScript", "TSX", "CSS", "HEEX", "Elixir"]
languages = ["HTML", "PHP", "ERB", "HTML/ERB", "JavaScript", "TSX", "CSS", "HEEX", "Elixir", "Vue.js"]
[language_servers.emmet-language-server.language_ids]
"HTML" = "html"
@ -21,3 +21,4 @@ languages = ["HTML", "PHP", "ERB", "HTML/ERB", "JavaScript", "TSX", "CSS", "HEEX
"CSS" = "css"
"HEEX" = "heex"
"Elixir" = "heex"
"Vue.js" = "vue"

18
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": {
"crane": {
"locked": {
"lastModified": 1754269165,
"narHash": "sha256-0tcS8FHd4QjbCVoxN9jI+PjHgA4vc/IjkUSp+N3zy0U=",
"lastModified": 1750266157,
"narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "444e81206df3f7d92780680e45858e31d2f07a08",
"rev": "e37c943371b73ed87faf33f7583860f81f1d5a48",
"type": "github"
},
"original": {
@ -33,10 +33,10 @@
"nixpkgs": {
"locked": {
"lastModified": 315532800,
"narHash": "sha256-5VYevX3GccubYeccRGAXvCPA1ktrGmIX1IFC0icX07g=",
"rev": "a683adc19ff5228af548c6539dbc3440509bfed3",
"narHash": "sha256-j+zO+IHQ7VwEam0pjPExdbLT2rVioyVS3iq4bLO3GEc=",
"rev": "61c0f513911459945e2cb8bf333dc849f1b976ff",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre840248.a683adc19ff5/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre821324.61c0f5139114/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
@ -58,11 +58,11 @@
]
},
"locked": {
"lastModified": 1754575663,
"narHash": "sha256-afOx8AG0KYtw7mlt6s6ahBBy7eEHZwws3iCRoiuRQS4=",
"lastModified": 1750964660,
"narHash": "sha256-YQ6EyFetjH1uy5JhdhRdPe6cuNXlYpMAQePFfZj4W7M=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "6db0fb0e9cec2e9729dc52bf4898e6c135bb8a0f",
"rev": "04f0fcfb1a50c63529805a798b4b5c21610ff390",
"type": "github"
},
"original": {

View file

@ -1,5 +1,5 @@
[toolchain]
channel = "1.89"
channel = "1.88"
profile = "minimal"
components = [ "rustfmt", "clippy" ]
targets = [

View file

@ -9,7 +9,12 @@ main() {
platform="$(uname -s)"
arch="$(uname -m)"
channel="${ZED_CHANNEL:-stable}"
temp="$(mktemp -d "/tmp/zed-XXXXXX")"
# Use TMPDIR if available (for environments with non-standard temp directories)
if [ -n "${TMPDIR:-}" ] && [ -d "${TMPDIR}" ]; then
temp="$(mktemp -d "$TMPDIR/zed-XXXXXX")"
else
temp="$(mktemp -d "/tmp/zed-XXXXXX")"
fi
if [ "$platform" = "Darwin" ]; then
platform="macos"

View file

@ -5,7 +5,7 @@ function export_vars_for_environment {
echo "Invalid environment name '${environment}'" >&2
exit 1
fi
export $(cat $env_file)
export $(grep -v '^#' $env_file | grep -v '^[[:space:]]*$')
}
function target_zed_kube_cluster {