Compare commits

..

8 Commits

Author SHA1 Message Date
Cole Miller
7a49611b0e Don't abort in panic hook 2024-12-22 06:20:12 -05:00
Cole Miller
6b92e0b5da In terminal context, open new terminals with cmd-n instead of new files (#22253)
Release Notes:

- In terminal context, `cmd-n` and `ctrl-n` now open a new terminal
instead of a new file
2024-12-21 19:03:58 +00:00
Kirill Bulatov
2930211af9 Allow disabling editor scrollbars programmatically (#22333)
Disable them in the diff editors

Closes https://github.com/zed-industries/zed/issues/22271

Release Notes:

- N/A
2024-12-21 16:58:26 +00:00
Marshall Bowers
1449377278 assistant2: Fix panics when confirming nonexistent entries in the context picker (#22332)
This PR fixes a panic that could occur when confirming when the context
picker had no matching entries.

Release Notes:

- N/A
2024-12-21 16:11:29 +00:00
Kirill Bulatov
831930aad0 Make git panel entries clickable (#22329)
Makes a first pass over git panel UI, making it more interactive.


![image](https://github.com/user-attachments/assets/4d43b086-4ef2-4913-9783-2b9467d99c9a)


* every item can be selected, the selection is shown in the panel
* every item can be clicked, which changes the selection and
creates/focuses the editor with a project changes multi buffer
* the editor is scrolled so that the clicked item is in the center
* it's possible to nagivate up and down the panel, selecting
next/previous items in it, triggering the editor scroll

Known issues:

* entries are updated oddly sometimes (should become better after
DiffMap improvements land?)
* only unstaged diffs are shown currently (entry status storage should
help with this)
* no deleted files are displayed (the underlying work is done by others
now)
* added files have no diff hunks shown (DiffMap will have it?)
* performance story has not improved (again, DiffMap and status storage
should help with this)

Release Notes:

- N/A
2024-12-21 14:20:08 +00:00
Piotr Osiewicz
bc32b4d016 zeta: Compute diff on background thread (#22328)
@iamnbutler noticed slowness in assistant panel which I've pinned down
to the fact that we're calculating buffer diff on foreground thread.
This PR moves this computation into the background; I don't know much
about Zeta but it seems fine to do, as the call-site is asynchronous
anyways.

Closes #ISSUE

Release Notes:

- N/A
2024-12-21 13:44:18 +00:00
Piotr Osiewicz
fac5118f10 python: Fix decorated test detection (#22327)
Follow-up to #22325, where I missed a couple points in the review that
were actually quite relevant.

Closes #ISSUE

Release Notes:

- N/A
2024-12-21 13:11:52 +00:00
Thomas
7184b15f48 Add decorated function to pytest runnables (#22325)
Fixes: #22324

Closes #ISSUE

Release Notes:

- Fixed pytest decoracted function discovery
2024-12-21 11:31:35 +00:00
21 changed files with 654 additions and 1014 deletions

4
Cargo.lock generated
View File

@@ -5170,8 +5170,12 @@ dependencies = [
"anyhow",
"collections",
"db",
"editor",
"futures 0.3.31",
"git",
"gpui",
"language",
"menu",
"project",
"schemars",
"serde",

View File

@@ -448,7 +448,6 @@
"cmd-s": "workspace::Save",
"cmd-k s": "workspace::SaveWithoutFormat",
"cmd-shift-s": "workspace::SaveAs",
"cmd-n": "workspace::NewFile",
"cmd-shift-n": "workspace::NewWindow",
"ctrl-`": "terminal_panel::ToggleFocus",
"cmd-1": ["workspace::ActivatePane", 0],
@@ -495,6 +494,7 @@
"context": "Workspace && !Terminal",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "workspace::NewFile",
"cmd-shift-r": "task::Spawn",
"cmd-alt-r": "task::Rerun",
"ctrl-alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
@@ -761,6 +761,7 @@
"cmd-v": "terminal::Paste",
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal",
"ctrl-enter": "assistant::InlineAssist",
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"],

View File

@@ -1556,6 +1556,7 @@ impl ContextEditor {
let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
editor.set_show_scrollbars(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);

View File

@@ -178,7 +178,9 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let mat = &self.matches[self.selected_index];
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
let workspace = self.workspace.clone();
let Some(project) = workspace

View File

@@ -192,7 +192,9 @@ impl PickerDelegate for FileContextPickerDelegate {
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let mat = &self.matches[self.selected_index];
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
let workspace = self.workspace.clone();
let Some(project) = workspace

View File

@@ -154,7 +154,9 @@ impl PickerDelegate for ThreadContextPickerDelegate {
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let entry = &self.matches[self.selected_index];
let Some(entry) = self.matches.get(self.selected_index) else {
return;
};
let Some(thread_store) = self.thread_store.upgrade() else {
return;

View File

@@ -128,6 +128,7 @@ use multi_buffer::{
ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
};
use project::{
buffer_store::BufferChangeSet,
lsp_store::{FormatTarget, FormatTrigger, OpenLspBufferHandle},
project_settings::{GitGutterSetting, ProjectSettings},
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
@@ -605,6 +606,7 @@ pub struct Editor {
mode: EditorMode,
show_breadcrumbs: bool,
show_gutter: bool,
show_scrollbars: bool,
show_line_numbers: Option<bool>,
use_relative_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
@@ -1234,6 +1236,7 @@ impl Editor {
project,
blink_manager: blink_manager.clone(),
show_local_selections: true,
show_scrollbars: true,
mode,
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
show_gutter: mode == EditorMode::Full,
@@ -11259,6 +11262,11 @@ impl Editor {
cx.notify();
}
pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut ViewContext<Self>) {
self.show_scrollbars = show_scrollbars;
cx.notify();
}
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut ViewContext<Self>) {
self.show_line_numbers = Some(show_line_numbers);
cx.notify();
@@ -12950,6 +12958,14 @@ impl Editor {
.and_then(|item| item.to_any().downcast_ref::<T>())
}
pub fn add_change_set(
&mut self,
change_set: Model<BufferChangeSet>,
cx: &mut ViewContext<Self>,
) {
self.diff_map.add_change_set(change_set, cx);
}
fn character_size(&self, cx: &mut ViewContext<Self>) -> gpui::Point<Pixels> {
let text_layout_details = self.text_layout_details(cx);
let style = &text_layout_details.editor_style;

View File

@@ -1192,12 +1192,13 @@ impl EditorElement {
);
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
let show_scrollbars = match scrollbar_settings.show {
ShowScrollbar::Auto => {
let editor = self.editor.read(cx);
let is_singleton = editor.is_singleton(cx);
// Git
(is_singleton && scrollbar_settings.git_diff && !snapshot.diff_map.is_empty())
let show_scrollbars = self.editor.read(cx).show_scrollbars
&& match scrollbar_settings.show {
ShowScrollbar::Auto => {
let editor = self.editor.read(cx);
let is_singleton = editor.is_singleton(cx);
// Git
(is_singleton && scrollbar_settings.git_diff && !snapshot.diff_map.is_empty())
||
// Buffer Search Results
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
@@ -1213,11 +1214,11 @@ impl EditorElement {
||
// Scrollmanager
editor.scroll_manager.scrollbars_visible()
}
ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
};
}
ShowScrollbar::System => self.editor.read(cx).scroll_manager.scrollbars_visible(),
ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
};
let axes: AxisPair<bool> = scrollbar_settings.axes.into();

View File

@@ -1155,6 +1155,11 @@ fn editor_with_deleted_text(
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_gutter(false, cx);
editor.set_show_line_numbers(false, cx);
editor.set_show_scrollbars(false, cx);
editor.set_show_runnables(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
@@ -1166,7 +1171,7 @@ fn editor_with_deleted_text(
false,
cx,
);
editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); //
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
editor
._subscriptions
.extend([cx.on_blur(&editor.focus_handle, |editor, cx| {

View File

@@ -15,7 +15,11 @@ path = "src/git_ui.rs"
[dependencies]
anyhow.workspace = true
db.workspace = true
editor.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
menu.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true

View File

@@ -1,13 +1,19 @@
use anyhow::Result;
use anyhow::{Context as _, Result};
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use git::repository::GitFileStatus;
use editor::{
scroll::{Autoscroll, AutoscrollStrategy},
Editor, MultiBuffer, DEFAULT_MULTIBUFFER_CONTEXT,
};
use git::{diff::DiffHunk, repository::GitFileStatus};
use gpui::{
actions, prelude::*, uniform_list, Action, AppContext, AsyncWindowContext, ClickEvent,
CursorStyle, EventEmitter, FocusHandle, FocusableView, KeyContext,
ListHorizontalSizingBehavior, ListSizingBehavior, Model, Modifiers, ModifiersChangedEvent,
MouseButton, Stateful, Task, UniformListScrollHandle, View, WeakView,
MouseButton, ScrollStrategy, Stateful, Task, UniformListScrollHandle, View, WeakView,
};
use language::{Buffer, BufferRow, OffsetRangeExt};
use menu::{SelectNext, SelectPrev};
use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, WorktreeId};
use serde::{Deserialize, Serialize};
use settings::Settings as _;
@@ -15,17 +21,22 @@ use std::{
cell::OnceCell,
collections::HashSet,
ffi::OsStr,
ops::Range,
ops::{Deref, Range},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
time::Duration,
usize,
};
use ui::{
prelude::*, Checkbox, Divider, DividerColor, ElevationIndex, Scrollbar, ScrollbarState, Tooltip,
prelude::*, Checkbox, Divider, DividerColor, ElevationIndex, ListItem, Scrollbar,
ScrollbarState, Tooltip,
};
use util::{ResultExt, TryFutureExt};
use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
ItemHandle, Workspace,
};
use crate::{git_status_icon, settings::GitPanelSettings};
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
@@ -34,6 +45,8 @@ actions!(git_panel, [ToggleFocus]);
const GIT_PANEL_KEY: &str = "GitPanel";
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
@@ -61,6 +74,8 @@ struct EntryDetails {
depth: usize,
is_expanded: bool,
status: Option<GitFileStatus>,
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
index: usize,
}
impl EntryDetails {
@@ -75,7 +90,7 @@ struct SerializedGitPanel {
}
pub struct GitPanel {
_workspace: WeakView<Workspace>,
workspace: WeakView<Workspace>,
current_modifiers: Modifiers,
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
@@ -90,8 +105,43 @@ pub struct GitPanel {
// The entries that are currently shown in the panel, aka
// not hidden by folding or such
visible_entries: Vec<(WorktreeId, Vec<Entry>, OnceCell<HashSet<Arc<Path>>>)>,
visible_entries: Vec<WorktreeEntries>,
width: Option<Pixels>,
git_diff_editor: View<Editor>,
git_diff_editor_updates: Task<()>,
reveal_in_editor: Task<()>,
}
#[derive(Debug, Clone)]
struct WorktreeEntries {
worktree_id: WorktreeId,
visible_entries: Vec<GitPanelEntry>,
paths: Rc<OnceCell<HashSet<Arc<Path>>>>,
}
#[derive(Debug, Clone)]
struct GitPanelEntry {
entry: Entry,
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
}
impl Deref for GitPanelEntry {
type Target = Entry;
fn deref(&self) -> &Self::Target {
&self.entry
}
}
impl WorktreeEntries {
fn paths(&self) -> &HashSet<Arc<Path>> {
self.paths.get_or_init(|| {
self.visible_entries
.iter()
.map(|e| (e.entry.path.clone()))
.collect()
})
}
}
impl GitPanel {
@@ -118,18 +168,28 @@ impl GitPanel {
this.hide_scrollbar(cx);
})
.detach();
cx.subscribe(&project, |this, _project, event, cx| match event {
cx.subscribe(&project, |this, project, event, cx| match event {
project::Event::WorktreeRemoved(id) => {
this.expanded_dir_ids.remove(id);
this.update_visible_entries(None, cx);
this.update_visible_entries(None, None, cx);
cx.notify();
}
project::Event::WorktreeUpdatedEntries(_, _)
| project::Event::WorktreeAdded(_)
| project::Event::WorktreeOrderChanged => {
this.update_visible_entries(None, cx);
project::Event::WorktreeOrderChanged => {
this.update_visible_entries(None, None, cx);
cx.notify();
}
project::Event::WorktreeUpdatedEntries(id, _)
| project::Event::WorktreeAdded(id)
| project::Event::WorktreeUpdatedGitRepositories(id) => {
this.update_visible_entries(Some(*id), None, cx);
cx.notify();
}
project::Event::Closed => {
this.git_diff_editor_updates = Task::ready(());
this.expanded_dir_ids.clear();
this.visible_entries.clear();
this.git_diff_editor = diff_display_editor(project.clone(), cx);
}
_ => {}
})
.detach();
@@ -137,11 +197,10 @@ impl GitPanel {
let scroll_handle = UniformListScrollHandle::new();
let mut this = Self {
_workspace: weak_workspace,
workspace: weak_workspace,
focus_handle: cx.focus_handle(),
fs,
pending_serialization: Task::ready(None),
project,
visible_entries: Vec::new(),
current_modifiers: cx.modifiers(),
expanded_dir_ids: Default::default(),
@@ -152,8 +211,12 @@ impl GitPanel {
selected_item: None,
show_scrollbar: !Self::should_autohide_scrollbar(cx),
hide_scrollbar_task: None,
git_diff_editor: diff_display_editor(project.clone(), cx),
git_diff_editor_updates: Task::ready(()),
reveal_in_editor: Task::ready(()),
project,
};
this.update_visible_entries(None, cx);
this.update_visible_entries(None, None, cx);
this
});
@@ -254,6 +317,82 @@ impl GitPanel {
(depth, difference)
}
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
let item_count = self
.visible_entries
.iter()
.map(|worktree_entries| worktree_entries.visible_entries.len())
.sum::<usize>();
if item_count == 0 {
return;
}
let selection = match self.selected_item {
Some(i) => {
if i < item_count - 1 {
self.selected_item = Some(i + 1);
i + 1
} else {
self.selected_item = Some(0);
0
}
}
None => {
self.selected_item = Some(0);
0
}
};
self.scroll_handle
.scroll_to_item(selection, ScrollStrategy::Center);
let mut hunks = None;
self.for_each_visible_entry(selection..selection + 1, cx, |_, entry, _| {
hunks = Some(entry.hunks.clone());
});
if let Some(hunks) = hunks {
self.reveal_entry_in_git_editor(hunks, false, Some(UPDATE_DEBOUNCE), cx);
}
cx.notify();
}
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
let item_count = self
.visible_entries
.iter()
.map(|worktree_entries| worktree_entries.visible_entries.len())
.sum::<usize>();
if item_count == 0 {
return;
}
let selection = match self.selected_item {
Some(i) => {
if i > 0 {
self.selected_item = Some(i - 1);
i - 1
} else {
self.selected_item = Some(item_count - 1);
item_count - 1
}
}
None => {
self.selected_item = Some(0);
0
}
};
self.scroll_handle
.scroll_to_item(selection, ScrollStrategy::Center);
let mut hunks = None;
self.for_each_visible_entry(selection..selection + 1, cx, |_, entry, _| {
hunks = Some(entry.hunks.clone());
});
if let Some(hunks) = hunks {
self.reveal_entry_in_git_editor(hunks, false, Some(UPDATE_DEBOUNCE), cx);
}
cx.notify();
}
}
impl GitPanel {
@@ -296,8 +435,9 @@ impl GitPanel {
fn entry_count(&self) -> usize {
self.visible_entries
.iter()
.map(|(_, entries, _)| {
entries
.map(|worktree_entries| {
worktree_entries
.visible_entries
.iter()
.filter(|entry| entry.git_status.is_some())
.count()
@@ -312,19 +452,23 @@ impl GitPanel {
mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<Self>),
) {
let mut ix = 0;
for (worktree_id, visible_worktree_entries, entries_paths) in &self.visible_entries {
for worktree_entries in &self.visible_entries {
if ix >= range.end {
return;
}
if ix + visible_worktree_entries.len() <= range.start {
ix += visible_worktree_entries.len();
if ix + worktree_entries.visible_entries.len() <= range.start {
ix += worktree_entries.visible_entries.len();
continue;
}
let end_ix = range.end.min(ix + visible_worktree_entries.len());
let end_ix = range.end.min(ix + worktree_entries.visible_entries.len());
// let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) {
if let Some(worktree) = self
.project
.read(cx)
.worktree_for_id(worktree_entries.worktree_id, cx)
{
let snapshot = worktree.read(cx).snapshot();
let root_name = OsStr::new(snapshot.root_name());
let expanded_entry_ids = self
@@ -334,14 +478,14 @@ impl GitPanel {
.unwrap_or(&[]);
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
let entries = entries_paths.get_or_init(|| {
visible_worktree_entries
.iter()
.map(|e| (e.path.clone()))
.collect()
});
let entries = worktree_entries.paths();
for entry in visible_worktree_entries[entry_range].iter() {
let index_start = entry_range.start;
for (i, entry) in worktree_entries.visible_entries[entry_range]
.iter()
.enumerate()
{
let index = index_start + i;
let status = entry.git_status;
let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
@@ -363,16 +507,16 @@ impl GitPanel {
.unwrap_or_else(|| root_name.to_string_lossy().to_string()),
};
let display_name = entry.path.to_string_lossy().into_owned();
let details = EntryDetails {
filename,
display_name,
display_name: entry.path.to_string_lossy().into_owned(),
kind: entry.kind,
is_expanded,
path: entry.path.clone(),
status,
hunks: entry.hunks.clone(),
depth,
index,
};
callback(entry.id, details, cx);
}
@@ -382,44 +526,75 @@ impl GitPanel {
}
// TODO: Update expanded directory state
// TODO: Updates happen in the main loop, could be long for large workspaces
fn update_visible_entries(
&mut self,
for_worktree: Option<WorktreeId>,
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
cx: &mut ViewContext<Self>,
) {
let project = self.project.read(cx);
self.visible_entries.clear();
for worktree in project.visible_worktrees(cx) {
let snapshot = worktree.read(cx).snapshot();
let worktree_id = snapshot.id();
let mut visible_worktree_entries = Vec::new();
let mut entry_iter = snapshot.entries(true, 0);
while let Some(entry) = entry_iter.entry() {
// Only include entries with a git status
if entry.git_status.is_some() {
visible_worktree_entries.push(entry.clone());
let mut old_entries_removed = false;
let mut after_update = Vec::new();
self.visible_entries
.retain(|worktree_entries| match for_worktree {
Some(for_worktree) => {
if worktree_entries.worktree_id == for_worktree {
old_entries_removed = true;
false
} else if old_entries_removed {
after_update.push(worktree_entries.clone());
false
} else {
true
}
}
entry_iter.advance();
None => false,
});
for worktree in project.visible_worktrees(cx) {
let worktree_id = worktree.read(cx).id();
if for_worktree.is_some() && for_worktree != Some(worktree_id) {
continue;
}
let snapshot = worktree.read(cx).snapshot();
let mut visible_worktree_entries = snapshot
.entries(false, 0)
.filter(|entry| !entry.is_external)
.filter(|entry| entry.git_status.is_some())
.cloned()
.collect::<Vec<_>>();
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
project::sort_worktree_entries(&mut visible_worktree_entries);
if !visible_worktree_entries.is_empty() {
self.visible_entries
.push((worktree_id, visible_worktree_entries, OnceCell::new()));
self.visible_entries.push(WorktreeEntries {
worktree_id,
visible_entries: visible_worktree_entries
.into_iter()
.map(|entry| GitPanelEntry {
entry,
hunks: Rc::default(),
})
.collect(),
paths: Rc::default(),
});
}
}
self.visible_entries.extend(after_update);
if let Some((worktree_id, entry_id)) = new_selected_entry {
self.selected_item = self.visible_entries.iter().enumerate().find_map(
|(worktree_index, (id, entries, _))| {
if *id == worktree_id {
entries
|(worktree_index, worktree_entries)| {
if worktree_entries.worktree_id == worktree_id {
worktree_entries
.visible_entries
.iter()
.position(|entry| entry.id == entry_id)
.map(|entry_index| worktree_index * entries.len() + entry_index)
.map(|entry_index| {
worktree_index * worktree_entries.visible_entries.len()
+ entry_index
})
} else {
None
}
@@ -427,6 +602,163 @@ impl GitPanel {
);
}
let project = self.project.clone();
self.git_diff_editor_updates = cx.spawn(|git_panel, mut cx| async move {
cx.background_executor()
.timer(UPDATE_DEBOUNCE)
.await;
let Some(project_buffers) = git_panel
.update(&mut cx, |git_panel, cx| {
futures::future::join_all(git_panel.visible_entries.iter_mut().flat_map(
move |worktree_entries| {
worktree_entries
.visible_entries
.iter()
.filter_map(|entry| {
let git_status = entry.git_status()?;
let entry_hunks = entry.hunks.clone();
let (entry_path, unstaged_changes_task) =
project.update(cx, |project, cx| {
let entry_path =
project.path_for_entry(entry.id, cx)?;
let open_task =
project.open_path(entry_path.clone(), cx);
let unstaged_changes_task =
cx.spawn(|project, mut cx| async move {
let (_, opened_model) = open_task
.await
.context("opening buffer")?;
let buffer = opened_model
.downcast::<Buffer>()
.map_err(|_| {
anyhow::anyhow!(
"accessing buffer for entry"
)
})?;
// TODO added files have noop changes and those are not expanded properly in the multi buffer
let unstaged_changes = project
.update(&mut cx, |project, cx| {
project.open_unstaged_changes(
buffer.clone(),
cx,
)
})?
.await
.context("opening unstaged changes")?;
let hunks = cx.update(|cx| {
entry_hunks
.get_or_init(|| {
match git_status {
GitFileStatus::Added => {
let buffer_snapshot = buffer.read(cx).snapshot();
let entire_buffer_range =
buffer_snapshot.anchor_after(0)
..buffer_snapshot
.anchor_before(
buffer_snapshot.len(),
);
let entire_buffer_point_range =
entire_buffer_range
.clone()
.to_point(&buffer_snapshot);
vec![DiffHunk {
row_range: entire_buffer_point_range
.start
.row
..entire_buffer_point_range
.end
.row,
buffer_range: entire_buffer_range,
diff_base_byte_range: 0..0,
}]
}
GitFileStatus::Modified => {
let buffer_snapshot =
buffer.read(cx).snapshot();
unstaged_changes.read(cx)
.diff_to_buffer
.hunks_in_row_range(
0..BufferRow::MAX,
&buffer_snapshot,
)
.collect()
}
// TODO support conflicts display
GitFileStatus::Conflict => Vec::new(),
}
}).clone()
})?;
anyhow::Ok((buffer, unstaged_changes, hunks))
});
Some((entry_path, unstaged_changes_task))
})?;
Some((entry_path, unstaged_changes_task))
})
.map(|(entry_path, open_task)| async move {
(entry_path, open_task.await)
})
.collect::<Vec<_>>()
},
))
})
.ok()
else {
return;
};
let project_buffers = project_buffers.await;
if project_buffers.is_empty() {
return;
}
let mut change_sets = Vec::with_capacity(project_buffers.len());
if let Some(buffer_update_task) = git_panel
.update(&mut cx, |git_panel, cx| {
let editor = git_panel.git_diff_editor.clone();
let multi_buffer = editor.read(cx).buffer().clone();
let mut buffers_with_ranges = Vec::with_capacity(project_buffers.len());
for (buffer_path, open_result) in project_buffers {
if let Some((buffer, unstaged_changes, diff_hunks)) = open_result
.with_context(|| format!("opening buffer {buffer_path:?}"))
.log_err()
{
change_sets.push(unstaged_changes);
buffers_with_ranges.push((
buffer,
diff_hunks
.into_iter()
.map(|hunk| hunk.buffer_range)
.collect(),
));
}
}
multi_buffer.update(cx, |multi_buffer, cx| {
multi_buffer.clear(cx);
multi_buffer.push_multiple_excerpts_with_context_lines(
buffers_with_ranges,
DEFAULT_MULTIBUFFER_CONTEXT,
cx,
)
})
})
.ok()
{
buffer_update_task.await;
git_panel
.update(&mut cx, |git_panel, cx| {
git_panel.git_diff_editor.update(cx, |editor, cx| {
for change_set in change_sets {
editor.add_change_set(change_set, cx);
}
})
})
.ok();
}
});
cx.notify();
}
}
@@ -629,17 +961,23 @@ impl GitPanel {
let item_count = self
.visible_entries
.iter()
.map(|(_, worktree_entries, _)| worktree_entries.len())
.map(|worktree_entries| worktree_entries.visible_entries.len())
.sum();
let selected_entry = self.selected_item;
h_flex()
.size_full()
.overflow_hidden()
.child(
uniform_list(cx.view().clone(), "entries", item_count, {
|this, range, cx| {
move |git_panel, range, cx| {
let mut items = Vec::with_capacity(range.end - range.start);
this.for_each_visible_entry(range, cx, |id, details, cx| {
items.push(this.render_entry(id, details, cx));
git_panel.for_each_visible_entry(range, cx, |id, details, cx| {
items.push(git_panel.render_entry(
id,
Some(details.index) == selected_entry,
details,
cx,
));
});
items
}
@@ -656,12 +994,14 @@ impl GitPanel {
fn render_entry(
&self,
id: ProjectEntryId,
selected: bool,
details: EntryDetails,
cx: &ViewContext<Self>,
) -> impl IntoElement {
let id = id.to_proto() as usize;
let checkbox_id = ElementId::Name(format!("checkbox_{}", id).into());
let is_staged = ToggleState::Selected;
let handle = cx.view().clone();
h_flex()
.id(id)
@@ -679,7 +1019,113 @@ impl GitPanel {
.when_some(details.status, |this, status| {
this.child(git_status_icon(status))
})
.child(h_flex().gap_1p5().child(details.display_name.clone()))
.child(
ListItem::new(("label", id))
.toggle_state(selected)
.child(h_flex().gap_1p5().child(details.display_name.clone()))
.on_click(move |e, cx| {
handle.update(cx, |git_panel, cx| {
git_panel.selected_item = Some(details.index);
let change_focus = e.down.click_count > 1;
git_panel.reveal_entry_in_git_editor(
details.hunks.clone(),
change_focus,
None,
cx,
);
});
}),
)
}
fn reveal_entry_in_git_editor(
&mut self,
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
change_focus: bool,
debounce: Option<Duration>,
cx: &mut ViewContext<'_, Self>,
) {
let workspace = self.workspace.clone();
let diff_editor = self.git_diff_editor.clone();
self.reveal_in_editor = cx.spawn(|_, mut cx| async move {
if let Some(debounce) = debounce {
cx.background_executor().timer(debounce).await;
}
let Some(editor) = workspace
.update(&mut cx, |workspace, cx| {
let git_diff_editor = workspace
.items_of_type::<Editor>(cx)
.find(|editor| &diff_editor == editor);
match git_diff_editor {
Some(existing_editor) => {
workspace.activate_item(&existing_editor, true, change_focus, cx);
existing_editor
}
None => {
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(
diff_editor.boxed_clone(),
true,
change_focus,
None,
cx,
)
});
diff_editor.clone()
}
}
})
.ok()
else {
return;
};
if let Some(first_hunk) = hunks.get().and_then(|hunks| hunks.first()) {
let hunk_buffer_range = &first_hunk.buffer_range;
if let Some(buffer_id) = hunk_buffer_range
.start
.buffer_id
.or_else(|| first_hunk.buffer_range.end.buffer_id)
{
editor
.update(&mut cx, |editor, cx| {
let multi_buffer = editor.buffer().read(cx);
let buffer = multi_buffer.buffer(buffer_id)?;
let buffer_snapshot = buffer.read(cx).snapshot();
let (excerpt_id, _) = multi_buffer
.excerpts_for_buffer(&buffer, cx)
.into_iter()
.find(|(_, excerpt)| {
hunk_buffer_range
.start
.cmp(&excerpt.context.start, &buffer_snapshot)
.is_ge()
&& hunk_buffer_range
.end
.cmp(&excerpt.context.end, &buffer_snapshot)
.is_le()
})?;
let multi_buffer_hunk_start = multi_buffer
.snapshot(cx)
.anchor_in_excerpt(excerpt_id, hunk_buffer_range.start)?;
editor.change_selections(
Some(Autoscroll::Strategy(AutoscrollStrategy::Center)),
cx,
|s| {
s.select_ranges(Some(
multi_buffer_hunk_start..multi_buffer_hunk_start,
))
},
);
cx.notify();
Some(())
})
.ok()
.flatten();
}
}
});
}
}
@@ -707,6 +1153,8 @@ impl Render for GitPanel {
this.commit_all_changes(&CommitAllChanges, cx)
}))
})
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_prev))
.on_hover(cx.listener(|this, hovered, cx| {
if *hovered {
this.show_scrollbar = true;
@@ -787,3 +1235,14 @@ impl Panel for GitPanel {
Box::new(ToggleFocus)
}
}
fn diff_display_editor(project: Model<Project>, cx: &mut WindowContext) -> View<Editor> {
cx.new_view(|cx| {
let multi_buffer = cx.new_model(|cx| {
MultiBuffer::new(project.read(cx).capability()).with_title("Project diff".to_string())
});
let mut editor = Editor::for_multibuffer(multi_buffer, Some(project), true, cx);
editor.set_expand_all_diff_hunks();
editor
})
}

View File

@@ -1,574 +1,25 @@
use gpui::{
div, hsla, point, prelude::*, px, relative, rgb, size, App, AppContext, Bounds, BoxShadow, Div,
SharedString, ViewContext, WindowBounds, WindowOptions,
div, prelude::*, px, rgb, size, App, AppContext, Bounds, ViewContext, WindowBounds,
WindowOptions,
};
use smallvec::smallvec;
struct Shadow {}
impl Shadow {
fn base() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.rounded_full()
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
fn square() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
fn rounded_small() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.rounded(px(4.))
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
fn rounded_medium() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.rounded(px(8.))
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
fn rounded_large() -> Div {
div()
.size_16()
.bg(rgb(0xffffff))
.rounded(px(12.))
.border_1()
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
}
}
fn example(label: impl Into<SharedString>, example: impl IntoElement) -> impl IntoElement {
let label = label.into();
div()
.flex()
.flex_col()
.justify_center()
.items_center()
.w(relative(1. / 6.))
.border_r_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.child(
div()
.flex()
.items_center()
.justify_center()
.flex_1()
.py_12()
.child(example),
)
.child(
div()
.w_full()
.border_t_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.p_1()
.flex()
.items_center()
.child(label),
)
}
impl Render for Shadow {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.id("shadow-example")
.overflow_y_scroll()
.flex()
.bg(rgb(0xffffff))
.size_full()
.text_xs()
.child(div().flex().flex_col().w_full().children(vec![
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.flex_row()
.children(vec![
example(
"Square",
Shadow::square()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded 4",
Shadow::rounded_small()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded 8",
Shadow::rounded_medium()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded 16",
Shadow::rounded_large()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Circle",
Shadow::base()
.shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
]),
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.w_full()
.children(vec![
example("None", Shadow::base()),
// Small shadow
example("Small", Shadow::base().shadow_sm()),
// Medium shadow
example("Medium", Shadow::base().shadow_md()),
// Large shadow
example("Large", Shadow::base().shadow_lg()),
example("Extra Large", Shadow::base().shadow_xl()),
example("2X Large", Shadow::base().shadow_2xl()),
]),
// Horizontal list of increasing blur radii
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Blur 0",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(0.),
spread_radius: px(0.),
}]),
),
example(
"Blur 2",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(2.),
spread_radius: px(0.),
}]),
),
example(
"Blur 4",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(4.),
spread_radius: px(0.),
}]),
),
example(
"Blur 8",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Blur 16",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(16.),
spread_radius: px(0.),
}]),
),
]),
// Horizontal list of increasing spread radii
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Spread 0",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Spread 2",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(2.),
}]),
),
example(
"Spread 4",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(4.),
}]),
),
example(
"Spread 8",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(8.),
}]),
),
example(
"Spread 16",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(16.),
}]),
),
]),
// Square spread examples
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Square Spread 0",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Square Spread 8",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(8.),
}]),
),
example(
"Square Spread 16",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(16.),
}]),
),
]),
// Rounded large spread examples
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Rounded Large Spread 0",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded Large Spread 8",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(8.),
}]),
),
example(
"Rounded Large Spread 16",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(16.),
}]),
),
]),
// Directional shadows
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Left",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Right",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Top",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Bottom",
Shadow::base().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
]),
// Square directional shadows
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Square Left",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Square Right",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Square Top",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Square Bottom",
Shadow::square().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
]),
// Rounded large directional shadows
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Rounded Large Left",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded Large Right",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded Large Top",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
example(
"Rounded Large Bottom",
Shadow::rounded_large().shadow(smallvec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
spread_radius: px(0.),
}]),
),
]),
// Multiple shadows for different shapes
div()
.border_b_1()
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
.flex()
.children(vec![
example(
"Circle Multiple",
Shadow::base().shadow(smallvec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
offset: point(px(12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
offset: point(px(0.), px(12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
offset: point(px(-12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
]),
),
example(
"Square Multiple",
Shadow::square().shadow(smallvec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
offset: point(px(12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
offset: point(px(0.), px(12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
offset: point(px(-12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
]),
),
example(
"Rounded Large Multiple",
Shadow::rounded_large().shadow(smallvec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
offset: point(px(12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
offset: point(px(0.), px(12.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
BoxShadow {
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
offset: point(px(-12.), px(0.)),
blur_radius: px(8.),
spread_radius: px(2.),
},
]),
),
]),
]))
.justify_center()
.items_center()
.child(div().size_8().shadow_sm())
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(1000.0), px(800.0)), cx);
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
@@ -577,7 +28,5 @@ fn main() {
|cx| cx.new_view(|_cx| Shadow {}),
)
.unwrap();
cx.activate(true);
});
}

View File

@@ -1,337 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Shadow Examples</title>
<style>
body {
font-family: Arial, sans-serif;
font-size: 12px;
margin: 0;
padding: 0;
background-color: #ffffff;
}
.container {
display: flex;
flex-direction: column;
height: 100vh;
overflow-y: scroll;
}
.row {
display: flex;
border-bottom: 1px solid rgba(0, 0, 0, 1);
}
.example {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: calc(100% / 6);
border-right: 1px solid rgba(0, 0, 0, 1);
}
.box {
width: 64px;
height: 64px;
background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.1);
margin: 48px 0;
}
.label {
width: 100%;
border-top: 1px solid rgba(0, 0, 0, 1);
padding: 4px;
text-align: center;
}
/* Shapes */
.square {
}
.rounded-small {
border-radius: 4px;
}
.rounded-medium {
border-radius: 8px;
}
.rounded-large {
border-radius: 12px;
}
.circle {
border-radius: 50%;
}
/* Shadows */
.shadow-sm {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.shadow-md {
box-shadow:
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.shadow-lg {
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.shadow-xl {
box-shadow:
0 20px 25px -5px rgba(0, 0, 0, 0.1),
0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.shadow-2xl {
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
/* Blur radii */
.blur-0 {
box-shadow: 0 8px 0 0 rgba(0, 0, 0, 0.3);
}
.blur-2 {
box-shadow: 0 8px 2px 0 rgba(0, 0, 0, 0.3);
}
.blur-4 {
box-shadow: 0 8px 4px 0 rgba(0, 0, 0, 0.3);
}
.blur-8 {
box-shadow: 0 8px 8px 0 rgba(0, 0, 0, 0.3);
}
.blur-16 {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.3);
}
/* Spread radii */
.spread-0 {
box-shadow: 0 8px 8px 0 rgba(0, 0, 0, 0.3);
}
.spread-2 {
box-shadow: 0 8px 8px 2px rgba(0, 0, 0, 0.3);
}
.spread-4 {
box-shadow: 0 8px 8px 4px rgba(0, 0, 0, 0.3);
}
.spread-8 {
box-shadow: 0 8px 8px 8px rgba(0, 0, 0, 0.3);
}
.spread-16 {
box-shadow: 0 8px 8px 16px rgba(0, 0, 0, 0.3);
}
/* Directional shadows */
.shadow-left {
box-shadow: -8px 0 8px 0 rgba(128, 0, 128, 0.3);
}
.shadow-right {
box-shadow: 8px 0 8px 0 rgba(128, 0, 128, 0.3);
}
.shadow-top {
box-shadow: 0 -8px 8px 0 rgba(128, 0, 128, 0.3);
}
.shadow-bottom {
box-shadow: 0 8px 8px 0 rgba(128, 0, 128, 0.3);
}
/* Multiple shadows */
.shadow-multiple {
box-shadow:
0 -12px 8px 2px rgba(255, 0, 0, 0.3),
12px 0 8px 2px rgba(255, 255, 0, 0.3),
0 12px 8px 2px rgba(0, 255, 0, 0.3),
-12px 0 8px 2px rgba(0, 0, 255, 0.3);
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="example">
<div class="box square shadow-bottom"></div>
<div class="label">Square</div>
</div>
<div class="example">
<div class="box rounded-small shadow-bottom"></div>
<div class="label">Rounded 4</div>
</div>
<div class="example">
<div class="box rounded-medium shadow-bottom"></div>
<div class="label">Rounded 8</div>
</div>
<div class="example">
<div class="box rounded-large shadow-bottom"></div>
<div class="label">Rounded 16</div>
</div>
<div class="example">
<div class="box circle shadow-bottom"></div>
<div class="label">Circle</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box circle"></div>
<div class="label">None</div>
</div>
<div class="example">
<div class="box circle shadow-sm"></div>
<div class="label">Small</div>
</div>
<div class="example">
<div class="box circle shadow-md"></div>
<div class="label">Medium</div>
</div>
<div class="example">
<div class="box circle shadow-lg"></div>
<div class="label">Large</div>
</div>
<div class="example">
<div class="box circle shadow-xl"></div>
<div class="label">Extra Large</div>
</div>
<div class="example">
<div class="box circle shadow-2xl"></div>
<div class="label">2X Large</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box circle blur-0"></div>
<div class="label">Blur 0</div>
</div>
<div class="example">
<div class="box circle blur-2"></div>
<div class="label">Blur 2</div>
</div>
<div class="example">
<div class="box circle blur-4"></div>
<div class="label">Blur 4</div>
</div>
<div class="example">
<div class="box circle blur-8"></div>
<div class="label">Blur 8</div>
</div>
<div class="example">
<div class="box circle blur-16"></div>
<div class="label">Blur 16</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box circle spread-0"></div>
<div class="label">Spread 0</div>
</div>
<div class="example">
<div class="box circle spread-2"></div>
<div class="label">Spread 2</div>
</div>
<div class="example">
<div class="box circle spread-4"></div>
<div class="label">Spread 4</div>
</div>
<div class="example">
<div class="box circle spread-8"></div>
<div class="label">Spread 8</div>
</div>
<div class="example">
<div class="box circle spread-16"></div>
<div class="label">Spread 16</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box square spread-0"></div>
<div class="label">Square Spread 0</div>
</div>
<div class="example">
<div class="box square spread-8"></div>
<div class="label">Square Spread 8</div>
</div>
<div class="example">
<div class="box square spread-16"></div>
<div class="label">Square Spread 16</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box rounded-large spread-0"></div>
<div class="label">Rounded Large Spread 0</div>
</div>
<div class="example">
<div class="box rounded-large spread-8"></div>
<div class="label">Rounded Large Spread 8</div>
</div>
<div class="example">
<div class="box rounded-large spread-16"></div>
<div class="label">Rounded Large Spread 16</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box circle shadow-left"></div>
<div class="label">Left</div>
</div>
<div class="example">
<div class="box circle shadow-right"></div>
<div class="label">Right</div>
</div>
<div class="example">
<div class="box circle shadow-top"></div>
<div class="label">Top</div>
</div>
<div class="example">
<div class="box circle shadow-bottom"></div>
<div class="label">Bottom</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box square shadow-left"></div>
<div class="label">Square Left</div>
</div>
<div class="example">
<div class="box square shadow-right"></div>
<div class="label">Square Right</div>
</div>
<div class="example">
<div class="box square shadow-top"></div>
<div class="label">Square Top</div>
</div>
<div class="example">
<div class="box square shadow-bottom"></div>
<div class="label">Square Bottom</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box rounded-large shadow-left"></div>
<div class="label">Rounded Large Left</div>
</div>
<div class="example">
<div class="box rounded-large shadow-right"></div>
<div class="label">Rounded Large Right</div>
</div>
<div class="example">
<div class="box rounded-large shadow-top"></div>
<div class="label">Rounded Large Top</div>
</div>
<div class="example">
<div class="box rounded-large shadow-bottom"></div>
<div class="label">Rounded Large Bottom</div>
</div>
</div>
<div class="row">
<div class="example">
<div class="box circle shadow-multiple"></div>
<div class="label">Circle Multiple</div>
</div>
<div class="example">
<div class="box square shadow-multiple"></div>
<div class="label">Square Multiple</div>
</div>
<div class="example">
<div class="box rounded-large shadow-multiple"></div>
<div class="label">Rounded Large Multiple</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -180,13 +180,14 @@ vertex ShadowVertexOutput shadow_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id];
Shadow shadow = shadows[shadow_id];
// Calculate the expanded bounds
float expansion = max(3. * shadow.blur_radius, 1.0);
float margin = 3. * shadow.blur_radius;
// Set the bounds of the shadow and adjust its size based on the shadow's
// spread radius to achieve the spreading effect
Bounds_ScaledPixels bounds = shadow.bounds;
bounds.origin.x -= expansion;
bounds.origin.y -= expansion;
bounds.size.width += 2. * expansion;
bounds.size.height += 2. * expansion;
bounds.origin.x -= margin;
bounds.origin.y -= margin;
bounds.size.width += 2. * margin;
bounds.size.height += 2. * margin;
float4 device_position =
to_device_position(unit_vertex, bounds, viewport_size);
@@ -204,32 +205,46 @@ vertex ShadowVertexOutput shadow_vertex(
fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
constant Shadow *shadows
[[buffer(ShadowInputIndex_Shadows)]]) {
Shadow shadow = shadows[input.shadow_id];
Shadow shadow = shadows[input.shadow_id];
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
float2 half_size = size / 2.;
float2 center = origin + half_size;
float2 point = input.position.xy - center;
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
float2 half_size = size / 2.;
float2 center = origin + half_size;
float2 point = input.position.xy - center;
float corner_radius;
if (point.x < 0.) {
if (point.y < 0.) {
corner_radius = shadow.corner_radii.top_left;
} else {
corner_radius = shadow.corner_radii.bottom_left;
}
} else {
if (point.y < 0.) {
corner_radius = shadow.corner_radii.top_right;
} else {
corner_radius = shadow.corner_radii.bottom_right;
}
}
// Calculate distance from the edge of the shape
float2 d = abs(point) - half_size + shadow.corner_radii.top_left;
float corner_distance = length(max(d, 0.)) + min(max(d.x, d.y), 0.);
float distance = corner_distance - shadow.corner_radii.top_left;
// The signal is only non-zero in a limited range, so don't waste samples
float low = point.y - half_size.y;
float high = point.y + half_size.y;
float start = clamp(-3. * shadow.blur_radius, low, high);
float end = clamp(3. * shadow.blur_radius, low, high);
// Apply spread (reduced effect and maintaining circular shape)
float spread_factor = 0.5; // Adjust this to fine-tune the spread effect
distance -= shadow.spread_radius * spread_factor;
distance = length(max(float2(distance, 0.), 0.)) + min(distance, 0.);
// Accumulate samples (we can get away with surprisingly few samples)
float step = (end - start) / 4.;
float y = start + step * 0.5;
float alpha = 0.;
for (int i = 0; i < 4; i++) {
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
corner_radius, half_size) *
gaussian(y, shadow.blur_radius) * step;
y += step;
}
// Improved blur calculation
float blur_amount = shadow.blur_radius * 0.5;
float alpha = smoothstep(blur_amount, -blur_amount, distance);
// Apply a smoother falloff
alpha = pow(alpha, 1.3);
return input.color * float4(1., 1., 1., alpha);
return input.color * float4(1., 1., 1., alpha);
}
struct UnderlineVertexOutput {

View File

@@ -493,12 +493,10 @@ impl From<Underline> for Primitive {
pub(crate) struct Shadow {
pub order: DrawOrder,
pub blur_radius: ScaledPixels,
pub spread_radius: ScaledPixels,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub offset: Point<ScaledPixels>,
}
impl From<Shadow> for Primitive {

View File

@@ -2286,8 +2286,6 @@ impl<'a> WindowContext<'a> {
content_mask: content_mask.scale(scale_factor),
corner_radii: corner_radii.scale(scale_factor),
color: shadow.color.opacity(opacity),
spread_radius: shadow.spread_radius.scale(scale_factor),
offset: shadow.offset.scale(scale_factor),
});
}
}

View File

@@ -421,7 +421,7 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
color: hsla(0.5, 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-1.),

View File

@@ -36,11 +36,25 @@
(function_definition
name: (identifier) @run @_pytest_method_name
(#match? @_pytest_method_name "^test_")
) @python-pytest-method
) @_python-pytest-method
)
(#set! tag python-pytest-method)
)
; decorated pytest functions
(
(module
(decorated_definition
(decorator)+ @_decorator
definition: (function_definition
name: (identifier) @run @_pytest_method_name
(#match? @_pytest_method_name "^test_")
)
) @_python-pytest-method
)
(#set! tag python-pytest-method)
)
; pytest classes
(
(module
@@ -62,7 +76,7 @@
(function_definition
name: (identifier) @run @_pytest_method_name
(#match? @_pytest_method_name "^test")
) @python-pytest-method
) @_python-pytest-method
(#set! tag python-pytest-method)
)
)

View File

@@ -128,8 +128,6 @@ pub fn init_panic_hook(
}
}
}
std::process::abort();
}));
}

View File

@@ -269,6 +269,7 @@ impl RateCompletionModal {
let mut editor = Editor::multi_line(cx);
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
editor.set_show_scrollbars(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);

View File

@@ -298,14 +298,21 @@ impl Zeta {
cx.spawn(|this, mut cx| async move {
let request_sent_at = Instant::now();
let mut input_events = String::new();
for event in events {
if !input_events.is_empty() {
input_events.push('\n');
input_events.push('\n');
}
input_events.push_str(&event.to_prompt());
}
let input_events = cx
.background_executor()
.spawn(async move {
let mut input_events = String::new();
for event in events {
if !input_events.is_empty() {
input_events.push('\n');
input_events.push('\n');
}
input_events.push_str(&event.to_prompt());
}
input_events
})
.await;
let input_excerpt = prompt_for_excerpt(&snapshot, &excerpt_range, offset);
let input_outline = prompt_for_outline(&snapshot);