Compare commits

...

15 Commits

Author SHA1 Message Date
David
8e79ab6f6a lazy scroll start 2025-11-25 12:36:24 +01:00
David
ecddbd470f load only on click and first n items 2025-11-24 11:21:44 +01:00
David
42787904b0 do not greedly load multibuff if paths > 100 2025-11-21 12:58:42 +01:00
David
2bdc385fdf some notes where to add thigns 2025-11-21 12:40:39 +01:00
David
615803646f many much wip 2025-11-19 18:11:22 +01:00
David
101bbebe52 unfreezes diff panel at multibuffer loading speed cost 2025-11-19 11:27:04 +01:00
David
d6af4d3cdd restore more things 2025-11-18 17:32:39 +01:00
David
a32319374d cant type anymore? 2025-11-18 17:26:14 +01:00
David
716937c9c9 Merge branch 'main' into perf/project-diff2 2025-11-18 16:12:50 +01:00
David
6ee35cb43e rewrote project diff buffer loading. Now responsive on start get slow after a while as the mb loads full 2025-11-18 13:29:45 +01:00
David
d76c326ff5 somewhat responsive! omg 2025-11-17 14:43:01 +01:00
David
6effe1f48e sleepy time 2025-11-12 12:18:19 +01:00
David
8d3153abd4 ugly code protoyping only dont get this into prod 2025-11-12 11:30:52 +01:00
Jakub Konka
3a301afbc6 Enable all debug info for easier profiling 2025-11-10 22:38:40 +01:00
Jakub Konka
78add792c7 project_diff: Load buffers in the background 2025-11-10 22:01:38 +01:00
17 changed files with 404 additions and 129 deletions

View File

@@ -16,9 +16,7 @@ rustflags = ["-D", "warnings"]
debug = "limited"
# Use Mold on Linux, because it's faster than GNU ld and LLD.
#
# We no longer set this in the default `config.toml` so that developers can opt in to Wild, which
# is faster than Mold, in their own ~/.cargo/config.toml.
# We dont use wild in CI as its not production ready.
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

View File

@@ -8,6 +8,14 @@ perf-test = ["test", "--profile", "release-fast", "--lib", "--bins", "--tests",
# Keep similar flags here to share some ccache
perf-compare = ["run", "--profile", "release-fast", "-p", "perf", "--config", "target.'cfg(true)'.rustflags=[\"--cfg\", \"perf_enabled\"]", "--", "compare"]
# [target.x86_64-unknown-linux-gnu]
# linker = "clang"
# rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[target.aarch64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[target.'cfg(target_os = "windows")']
rustflags = [
"--cfg",

16
Cargo.lock generated
View File

@@ -2617,26 +2617,23 @@ dependencies = [
[[package]]
name = "calloop"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
version = "0.14.3"
dependencies = [
"bitflags 2.9.4",
"log",
"polling",
"rustix 0.38.44",
"rustix 1.1.2",
"slab",
"thiserror 1.0.69",
"tracing",
]
[[package]]
name = "calloop-wayland-source"
version = "0.3.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
dependencies = [
"calloop",
"rustix 0.38.44",
"rustix 1.1.2",
"wayland-backend",
"wayland-client",
]
@@ -10030,6 +10027,7 @@ name = "miniprofiler_ui"
version = "0.1.0"
dependencies = [
"gpui",
"log",
"serde_json",
"smol",
"util",

View File

@@ -784,6 +784,7 @@ features = [
notify = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" }
calloop = { path = "/home/davidsk/tmp/calloop" }
[profile.dev]
split-debuginfo = "unpacked"
@@ -860,7 +861,7 @@ ui_input = { codegen-units = 1 }
zed_actions = { codegen-units = 1 }
[profile.release]
debug = "limited"
debug = "full"
lto = "thin"
codegen-units = 1

View File

@@ -23907,6 +23907,10 @@ impl EditorSnapshot {
self.scroll_anchor.scroll_position(&self.display_snapshot)
}
pub fn scroll_near_end(&self) -> bool {
self.scroll_anchor.near_end(&self.display_snapshot)
}
fn gutter_dimensions(
&self,
font_id: FontId,

View File

@@ -9055,6 +9055,9 @@ impl Element for EditorElement {
)
});
if snapshot.scroll_near_end() {
dbg!("near end!");
}
let mut scroll_position = snapshot.scroll_position();
// The scroll position is a fractional point, the whole number of which represents
// the top of the window in terms of display rows.

View File

@@ -46,12 +46,20 @@ impl ScrollAnchor {
}
}
pub fn near_end(&self, snapshot: &DisplaySnapshot) -> bool {
let editor_length = snapshot.max_point().row().as_f64();
let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
(scroll_top - editor_length).abs() < 300.0
}
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
self.offset.apply_along(Axis::Vertical, |offset| {
if self.anchor == Anchor::min() {
0.
} else {
dbg!(snapshot.max_point().row().as_f64());
let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
dbg!(scroll_top, offset);
(offset + scroll_top).max(0.)
}
})
@@ -243,6 +251,11 @@ impl ScrollManager {
}
}
};
let near_end = self.anchor.near_end(map);
// TODO decounce here
if near_end {
cx.read();
}
let scroll_top_row = DisplayRow(scroll_top as u32);
let scroll_top_buffer_point = map

View File

@@ -30,10 +30,11 @@ use git::{
TrashUntrackedFiles, UnstageAll,
};
use gpui::{
Action, AsyncApp, AsyncWindowContext, ClickEvent, Corner, DismissEvent, Entity, EventEmitter,
FocusHandle, Focusable, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior,
MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy, Subscription, Task,
UniformListScrollHandle, WeakEntity, actions, anchored, deferred, uniform_list,
Action, AppContext, AsyncApp, AsyncWindowContext, ClickEvent, Corner, DismissEvent, Entity,
EventEmitter, FocusHandle, Focusable, KeyContext, ListHorizontalSizingBehavior,
ListSizingBehavior, MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy,
Subscription, Task, UniformListScrollHandle, WeakEntity, actions, anchored, deferred,
uniform_list,
};
use itertools::Itertools;
use language::{Buffer, File};
@@ -311,6 +312,9 @@ pub struct GitPanel {
bulk_staging: Option<BulkStaging>,
stash_entries: GitStash,
_settings_subscription: Subscription,
/// On clicking an entry in a the git_panel this will
/// trigger loading it
open_diff_task: Option<Task<()>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -471,6 +475,7 @@ impl GitPanel {
bulk_staging: None,
stash_entries: Default::default(),
_settings_subscription,
open_diff_task: None,
};
this.schedule_update(window, cx);
@@ -750,11 +755,25 @@ impl GitPanel {
fn open_diff(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
maybe!({
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
let entry = self
.entries
.get(self.selected_entry?)?
.status_entry()?
.clone();
let workspace = self.workspace.upgrade()?;
let git_repo = self.active_repository.as_ref()?;
let git_repo = self.active_repository.as_ref()?.clone();
let focus_handle = self.focus_handle.clone();
if let Some(project_diff) = workspace.read(cx).active_item_as::<ProjectDiff>(cx)
// let panel = panel.upgrade().unwrap(); // TODO FIXME
// cx.read_entity(&panel, |panel, cx| {
// panel
// })
// .unwrap(); // TODO FIXME
// how do we get the projectdiff here?
let project_diff = if let Some(project_diff) =
workspace.read(cx).active_item_as::<ProjectDiff>(cx)
&& let Some(project_path) = project_diff.read(cx).active_path(cx)
&& Some(&entry.repo_path)
== git_repo
@@ -764,16 +783,20 @@ impl GitPanel {
{
project_diff.focus_handle(cx).focus(window);
project_diff.update(cx, |project_diff, cx| project_diff.autoscroll(cx));
return None;
};
self.workspace
.update(cx, |workspace, cx| {
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
project_diff
} else {
workspace.update(cx, |workspace, cx| {
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx)
})
.ok();
self.focus_handle.focus(window);
};
focus_handle.focus(window); // TODO: should we focus before the file is loaded or wait for that?
let project_diff = project_diff.downgrade();
self.open_diff_task = Some(cx.spawn_in(window, async move |_, cx| {
ProjectDiff::refresh_one(project_diff, entry.repo_path, entry.status, cx)
.await
.unwrap(); // TODO FIXME
}));
Some(())
});
}
@@ -3860,6 +3883,7 @@ impl GitPanel {
})
}
// context menu
fn deploy_entry_context_menu(
&mut self,
position: Point<Pixels>,
@@ -4085,6 +4109,7 @@ impl GitPanel {
this.selected_entry = Some(ix);
cx.notify();
if event.modifiers().secondary() {
// the click handler
this.open_file(&Default::default(), window, cx)
} else {
this.open_diff(&Default::default(), window, cx);

View File

@@ -7,19 +7,21 @@ use crate::{
use anyhow::{Context as _, Result, anyhow};
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus};
use collections::{HashMap, HashSet};
use db::smol::stream::StreamExt;
use editor::{
Addon, Editor, EditorEvent, SelectionEffects,
actions::{GoToHunk, GoToPreviousHunk},
multibuffer_context_lines,
scroll::Autoscroll,
};
use futures::stream::FuturesUnordered;
use git::{
Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
repository::{Branch, RepoPath, Upstream, UpstreamTracking, UpstreamTrackingStatus},
status::FileStatus,
};
use gpui::{
Action, AnyElement, AnyView, App, AppContext as _, AsyncWindowContext, Entity, EventEmitter,
Action, AnyElement, AnyView, App, AppContext, AsyncWindowContext, Entity, EventEmitter,
FocusHandle, Focusable, Render, Subscription, Task, WeakEntity, actions,
};
use language::{Anchor, Buffer, Capability, OffsetRangeExt};
@@ -27,17 +29,20 @@ use multi_buffer::{MultiBuffer, PathKey};
use project::{
Project, ProjectPath,
git_store::{
Repository,
self, Repository, StatusEntry,
branch_diff::{self, BranchDiffEvent, DiffBase},
},
};
use settings::{Settings, SettingsStore};
use std::any::{Any, TypeId};
use std::ops::Range;
use std::sync::Arc;
use std::{
any::{Any, TypeId},
sync::Arc,
};
use theme::ActiveTheme;
use ui::{KeyBinding, Tooltip, prelude::*, vertical_divider};
use util::{ResultExt as _, rel_path::RelPath};
use util::{ResultExt, rel_path::RelPath};
use workspace::{
CloseActiveItem, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation,
ToolbarItemView, Workspace,
@@ -92,7 +97,7 @@ impl ProjectDiff {
window: &mut Window,
cx: &mut Context<Workspace>,
) {
Self::deploy_at(workspace, None, window, cx)
Self::deploy_at(workspace, None, window, cx);
}
fn deploy_branch_diff(
@@ -134,7 +139,7 @@ impl ProjectDiff {
entry: Option<GitStatusEntry>,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
) -> Entity<ProjectDiff> {
telemetry::event!(
"Git Diff Opened",
source = if entry.is_some() {
@@ -166,7 +171,8 @@ impl ProjectDiff {
project_diff.update(cx, |project_diff, cx| {
project_diff.move_to_entry(entry, window, cx);
})
}
};
project_diff
}
pub fn autoscroll(&self, cx: &mut Context<Self>) {
@@ -272,9 +278,13 @@ impl ProjectDiff {
window,
move |this, _git_store, event, window, cx| match event {
BranchDiffEvent::FileListChanged => {
// TODO this does not account for size of paths
// maybe a quick fs metadata could get us info on that?
// would make number of paths async but thats fine here
let entries = this.first_n_entries(cx, 100);
this._task = window.spawn(cx, {
let this = cx.weak_entity();
async |cx| Self::refresh(this, cx).await
async |cx| Self::refresh(this, entries, cx).await
})
}
},
@@ -290,21 +300,31 @@ impl ProjectDiff {
if is_sort_by_path != was_sort_by_path
|| is_collapse_untracked_diff != was_collapse_untracked_diff
{
this._task = {
window.spawn(cx, {
let this = cx.weak_entity();
async |cx| Self::refresh(this, cx).await
})
}
todo!();
// this._task = {
// window.spawn(cx, {
// let this = cx.weak_entity();
// async |cx| Self::refresh(this, cx).await
// })
// }
}
was_sort_by_path = is_sort_by_path;
was_collapse_untracked_diff = is_collapse_untracked_diff;
})
.detach();
// let entries = cx.read_entity(&cx.entity(), |project_diff, cx| {
// project_diff.first_n_entries(cx, 100)
// });
let task = window.spawn(cx, {
let this = cx.weak_entity();
async |cx| Self::refresh(this, cx).await
async |cx| {
let entries = this
.read_with(cx, |project_diff, cx| project_diff.first_n_entries(cx, 100))
.unwrap();
Self::refresh(this, entries, cx).await
}
});
Self {
@@ -471,10 +491,11 @@ impl ProjectDiff {
cx: &mut Context<Self>,
) {
let subscription = cx.subscribe_in(&diff, window, move |this, _, _, window, cx| {
this._task = window.spawn(cx, {
let this = cx.weak_entity();
async |cx| Self::refresh(this, cx).await
})
// TODO fix this
// this._task = window.spawn(cx, {
// let this = cx.weak_entity();
// async |cx| Self::refresh(this, cx).await
// })
});
self.buffer_diff_subscriptions
.insert(path_key.path.clone(), (diff.clone(), subscription));
@@ -550,51 +571,203 @@ impl ProjectDiff {
}
}
pub async fn refresh(this: WeakEntity<Self>, cx: &mut AsyncWindowContext) -> Result<()> {
let mut path_keys = Vec::new();
let buffers_to_load = this.update(cx, |this, cx| {
let (repo, buffers_to_load) = this.branch_diff.update(cx, |branch_diff, cx| {
let load_buffers = branch_diff.load_buffers(cx);
(branch_diff.repo().cloned(), load_buffers)
pub fn first_n_entries(&self, cx: &App, n: usize) -> Vec<StatusEntry> {
let Some(ref repo) = self.branch_diff.read(cx).repo else {
return Vec::new();
};
repo.read(cx).cached_status().take(n).collect()
}
pub async fn refresh_one(
this: WeakEntity<Self>,
repo_path: RepoPath,
status: FileStatus,
cx: &mut AsyncWindowContext,
) -> Result<()> {
use git_store::branch_diff::BranchDiff;
let Some(this) = this.upgrade() else {
return Ok(());
};
let multibuffer = cx.read_entity(&this, |this, _| this.multibuffer.clone())?;
let branch_diff = cx.read_entity(&this, |pd, _| pd.branch_diff.clone())?;
let Some(repo) = cx.read_entity(&branch_diff, |bd, _| bd.repo.clone())? else {
return Ok(());
};
let project = cx.read_entity(&branch_diff, |bd, _| bd.project.clone())?;
let mut previous_paths =
cx.read_entity(&multibuffer, |mb, _| mb.paths().collect::<HashSet<_>>())?;
let tree_diff_status = cx.read_entity(&branch_diff, |branch_diff, _| {
branch_diff
.tree_diff
.as_ref()
.and_then(|t| t.entries.get(&repo_path))
.cloned()
})?;
let Some(status) = cx.read_entity(&branch_diff, |bd, _| {
bd.merge_statuses(Some(status), tree_diff_status.as_ref())
})?
else {
return Ok(());
};
if !status.has_changes() {
return Ok(());
}
let Some(project_path) = cx.read_entity(&repo, |repo, cx| {
repo.repo_path_to_project_path(&repo_path, cx)
})?
else {
return Ok(());
};
let sort_prefix =
cx.read_entity(&repo, |repo, cx| sort_prefix(repo, &repo_path, status, cx))?;
let path_key = PathKey::with_sort_prefix(sort_prefix, repo_path.into_arc());
previous_paths.remove(&path_key);
let repo = repo.clone();
let Some((buffer, diff)) = BranchDiff::load_buffer(
tree_diff_status,
project_path,
repo,
project.downgrade(),
&mut cx.to_app(),
)
.await
.log_err() else {
return Ok(());
};
cx.update(|window, cx| {
this.update(cx, |this, cx| {
this.register_buffer(path_key, status, buffer, diff, window, cx)
});
let mut previous_paths = this.multibuffer.read(cx).paths().collect::<HashSet<_>>();
})?;
if let Some(repo) = repo {
let repo = repo.read(cx);
// TODO LL clear multibuff on open?
// // remove anything not part of the diff in the multibuffer
// this.update(cx, |this, cx| {
// multibuffer.update(cx, |multibuffer, cx| {
// for path in previous_paths {
// this.buffer_diff_subscriptions.remove(&path.path);
// multibuffer.remove_excerpts_for_path(path, cx);
// }
// });
// })?;
path_keys = Vec::with_capacity(buffers_to_load.len());
for entry in buffers_to_load.iter() {
let sort_prefix = sort_prefix(&repo, &entry.repo_path, entry.file_status, cx);
let path_key =
PathKey::with_sort_prefix(sort_prefix, entry.repo_path.as_ref().clone());
previous_paths.remove(&path_key);
path_keys.push(path_key)
}
Ok(())
}
pub async fn refresh(
this: WeakEntity<Self>,
cached_status: Vec<StatusEntry>,
cx: &mut AsyncWindowContext,
) -> Result<()> {
dbg!("refreshing all");
use git_store::branch_diff::BranchDiff;
let Some(this) = this.upgrade() else {
return Ok(());
};
let multibuffer = cx.read_entity(&this, |this, _| this.multibuffer.clone())?;
let branch_diff = cx.read_entity(&this, |pd, _| pd.branch_diff.clone())?;
let Some(repo) = cx.read_entity(&branch_diff, |bd, _| bd.repo.clone())? else {
return Ok(());
};
let project = cx.read_entity(&branch_diff, |bd, _| bd.project.clone())?;
let mut previous_paths =
cx.read_entity(&multibuffer, |mb, _| mb.paths().collect::<HashSet<_>>())?;
// Idea: on click in git panel prioritize task for that file in some way ...
// could have a hashmap of futures here
// - needs to prioritize *some* background tasks over others
// -
let mut tasks = FuturesUnordered::new();
let mut seen = HashSet::default();
for entry in cached_status {
seen.insert(entry.repo_path.clone());
let tree_diff_status = cx.read_entity(&branch_diff, |branch_diff, _| {
branch_diff
.tree_diff
.as_ref()
.and_then(|t| t.entries.get(&entry.repo_path))
.cloned()
})?;
let Some(status) = cx.read_entity(&branch_diff, |bd, _| {
bd.merge_statuses(Some(entry.status), tree_diff_status.as_ref())
})?
else {
continue;
};
if !status.has_changes() {
continue;
}
this.multibuffer.update(cx, |multibuffer, cx| {
let Some(project_path) = cx.read_entity(&repo, |repo, cx| {
repo.repo_path_to_project_path(&entry.repo_path, cx)
})?
else {
continue;
};
let sort_prefix = cx.read_entity(&repo, |repo, cx| {
sort_prefix(repo, &entry.repo_path, entry.status, cx)
})?;
let path_key = PathKey::with_sort_prefix(sort_prefix, entry.repo_path.into_arc());
previous_paths.remove(&path_key);
let repo = repo.clone();
let project = project.downgrade();
let task = cx.spawn(async move |cx| {
let res = BranchDiff::load_buffer(
tree_diff_status,
project_path,
repo,
project,
&mut cx.to_app(),
)
.await;
(res, path_key, entry.status)
});
tasks.push(task)
}
// remove anything not part of the diff in the multibuffer
this.update(cx, |this, cx| {
multibuffer.update(cx, |multibuffer, cx| {
for path in previous_paths {
this.buffer_diff_subscriptions.remove(&path.path);
multibuffer.remove_excerpts_for_path(path, cx);
}
});
buffers_to_load
})?;
for (entry, path_key) in buffers_to_load.into_iter().zip(path_keys.into_iter()) {
if let Some((buffer, diff)) = entry.load.await.log_err() {
// add the new buffers as they are parsed
let mut last_notify = Instant::now();
while let Some((res, path_key, file_status)) = tasks.next().await {
if let Some((buffer, diff)) = res.log_err() {
cx.update(|window, cx| {
this.update(cx, |this, cx| {
this.register_buffer(path_key, entry.file_status, buffer, diff, window, cx)
})
.ok();
this.register_buffer(path_key, file_status, buffer, diff, window, cx)
});
})?;
}
if last_notify.elapsed().as_millis() > 100 {
cx.notify();
last_notify = Instant::now();
}
}
this.update(cx, |this, cx| {
this.pending_scroll.take();
cx.notify();
})?;
Ok(())
}

View File

@@ -187,12 +187,13 @@ font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "11052312
"source-fontconfig-dlopen",
], optional = true }
calloop = { version = "0.13.0" }
calloop = { version = "0.14.3" }
filedescriptor = { version = "0.8.2", optional = true }
open = { version = "5.2.0", optional = true }
# Wayland
calloop-wayland-source = { version = "0.3.0", optional = true }
calloop-wayland-source = { version = "0.4.1", optional = true }
wayland-backend = { version = "0.3.3", features = [
"client_system",
"dlopen",
@@ -265,7 +266,6 @@ naga.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.build-dependencies]
naga.workspace = true
[[example]]
name = "hello_world"
path = "examples/hello_world.rs"

View File

@@ -310,6 +310,11 @@ impl AsyncWindowContext {
.update(self, |_, window, cx| read(cx.global(), window, cx))
}
/// Returns an `AsyncApp` by cloning the one used by Self
pub fn to_app(&self) -> AsyncApp {
self.app.clone()
}
/// A convenience method for [`App::update_global`](BorrowAppContext::update_global).
/// for updating the global state of the specified type.
pub fn update_global<G, R>(

View File

@@ -233,6 +233,9 @@ impl<'a, T: 'static> Context<'a, T> {
/// Spawn the future returned by the given function.
/// The function is provided a weak handle to the entity owned by this context and a context that can be held across await points.
/// The returned task must be held or detached.
///
/// # Example
/// `cx.spawn(async move |some_weak_entity, cx| ...)`
#[track_caller]
pub fn spawn<AsyncFn, R>(&self, f: AsyncFn) -> Task<R>
where

View File

@@ -487,12 +487,15 @@ impl WaylandClient {
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
let handle = event_loop.handle();
let handle = event_loop.handle(); // CHECK that wayland sources get higher prio
handle
// these are all tasks spawned on the foreground executor.
// There is no concept of priority, they are all equal.
.insert_source(main_receiver, {
let handle = handle.clone();
move |event, _, _: &mut WaylandClientStatePtr| {
if let calloop::channel::Event::Msg(runnable) = event {
// will only be called when the event loop has finished processing all pending events from the sources
handle.insert_idle(|_| {
let start = Instant::now();
let mut timing = match runnable {
@@ -650,6 +653,7 @@ impl WaylandClient {
event_loop: Some(event_loop),
}));
// MAGIC HERE IT IS
WaylandSource::new(conn, event_queue)
.insert(handle)
.unwrap();
@@ -1574,6 +1578,7 @@ fn linux_button_to_gpui(button: u32) -> Option<MouseButton> {
})
}
// how is this being called inside calloop
impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
fn event(
this: &mut Self,
@@ -1664,7 +1669,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
modifiers: state.modifiers,
});
drop(state);
window.handle_input(input);
window.handle_input(input); // How does this get into the event loop?
}
}
wl_pointer::Event::Button {

View File

@@ -18,6 +18,7 @@ workspace.workspace = true
util.workspace = true
serde_json.workspace = true
smol.workspace = true
log.workspace = true
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }

View File

@@ -5,10 +5,7 @@ use std::{
};
use gpui::{
App, AppContext, Context, Entity, Hsla, InteractiveElement, IntoElement, ParentElement, Render,
ScrollHandle, SerializedTaskTiming, StatefulInteractiveElement, Styled, Task, TaskTiming,
TitlebarOptions, WindowBounds, WindowHandle, WindowOptions, div, prelude::FluentBuilder, px,
relative, size,
App, AppContext, Context, Entity, Hsla, InteractiveElement, IntoElement, ParentElement, Render, ScrollHandle, SerializedThreadTaskTimings, StatefulInteractiveElement, Styled, Task, TaskTiming, ThreadTaskTimings, TitlebarOptions, WindowBounds, WindowHandle, WindowOptions, div, prelude::FluentBuilder, px, relative, size
};
use util::ResultExt;
use workspace::{
@@ -287,8 +284,13 @@ impl Render for ProfilerWindow {
let Some(data) = this.get_timings() else {
return;
};
let timings =
SerializedTaskTiming::convert(this.startup_time, &data);
let timings = ThreadTaskTimings {
thread_name: Some("main".to_string()),
thread_id: std::thread::current().id(),
timings: data.clone()
};
let timings = Vec::from([SerializedThreadTaskTimings::convert(this.startup_time, timings)]);
let active_path = workspace
.read_with(cx, |workspace, cx| {
@@ -305,12 +307,17 @@ impl Render for ProfilerWindow {
);
cx.background_spawn(async move {
let path = path.await;
let path =
path.log_err().and_then(|p| p.log_err()).flatten();
let Some(path) = path else {
return;
let path = match path.await.log_err() {
Some(Ok(Some(path))) => path,
Some(e @ Err(_)) => {
e.log_err();
log::warn!("Saving miniprof in workingdir");
std::path::Path::new(
"performance_profile.miniprof",
)
.to_path_buf()
}
Some(Ok(None)) | None => return,
};
let Some(timings) =

View File

@@ -34,11 +34,11 @@ impl DiffBase {
pub struct BranchDiff {
diff_base: DiffBase,
repo: Option<Entity<Repository>>,
project: Entity<Project>,
pub repo: Option<Entity<Repository>>,
pub project: Entity<Project>,
base_commit: Option<SharedString>,
head_commit: Option<SharedString>,
tree_diff: Option<TreeDiff>,
pub tree_diff: Option<TreeDiff>,
_subscription: Subscription,
update_needed: postage::watch::Sender<()>,
_task: Task<()>,
@@ -283,7 +283,11 @@ impl BranchDiff {
else {
continue;
};
let task = Self::load_buffer(branch_diff, project_path, repo.clone(), cx);
let repo = repo.clone();
let task = cx.spawn(async move |project, cx| {
Self::load_buffer(branch_diff, project_path, repo.clone(), project, cx).await
});
output.push(DiffBuffer {
repo_path: item.repo_path.clone(),
@@ -303,8 +307,11 @@ impl BranchDiff {
let Some(project_path) = repo.read(cx).repo_path_to_project_path(&path, cx) else {
continue;
};
let task =
Self::load_buffer(Some(branch_diff.clone()), project_path, repo.clone(), cx);
let repo = repo.clone();
let branch_diff2 = Some(branch_diff.clone());
let task = cx.spawn(async move |project, cx| {
Self::load_buffer(branch_diff2, project_path, repo, project, cx).await
});
let file_status = diff_status_to_file_status(branch_diff);
@@ -318,42 +325,40 @@ impl BranchDiff {
output
}
fn load_buffer(
pub async fn load_buffer(
branch_diff: Option<git::status::TreeDiffStatus>,
project_path: crate::ProjectPath,
repo: Entity<Repository>,
cx: &Context<'_, Project>,
) -> Task<Result<(Entity<Buffer>, Entity<BufferDiff>)>> {
let task = cx.spawn(async move |project, cx| {
let buffer = project
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
.await?;
project: WeakEntity<Project>,
cx: &mut gpui::AsyncApp, // making this generic over AppContext hangs the compiler
) -> Result<(Entity<Buffer>, Entity<BufferDiff>)> {
let buffer = project
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
.await?;
let languages = project.update(cx, |project, _cx| project.languages().clone())?;
let languages = project.update(cx, |project, _cx| project.languages().clone())?;
let changes = if let Some(entry) = branch_diff {
let oid = match entry {
git::status::TreeDiffStatus::Added { .. } => None,
git::status::TreeDiffStatus::Modified { old, .. }
| git::status::TreeDiffStatus::Deleted { old } => Some(old),
};
project
.update(cx, |project, cx| {
project.git_store().update(cx, |git_store, cx| {
git_store.open_diff_since(oid, buffer.clone(), repo, languages, cx)
})
})?
.await?
} else {
project
.update(cx, |project, cx| {
project.open_uncommitted_diff(buffer.clone(), cx)
})?
.await?
let changes = if let Some(entry) = branch_diff {
let oid = match entry {
git::status::TreeDiffStatus::Added { .. } => None,
git::status::TreeDiffStatus::Modified { old, .. }
| git::status::TreeDiffStatus::Deleted { old } => Some(old),
};
Ok((buffer, changes))
});
task
project
.update(cx, |project, cx| {
project.git_store().update(cx, |git_store, cx| {
git_store.open_diff_since(oid, buffer.clone(), repo, languages, cx)
})
})?
.await?
} else {
project
.update(cx, |project, cx| {
project.open_uncommitted_diff(buffer.clone(), cx)
})?
.await?
};
Ok((buffer, changes))
}
}

View File

@@ -13,6 +13,32 @@ use std::{
time::Duration,
};
// https://docs.rs/tokio/latest/src/tokio/task/yield_now.rs.html#39-64
pub async fn yield_now() {
/// Yield implementation
struct YieldNow {
yielded: bool,
}
impl Future for YieldNow {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
// use core::task::ready;
// ready!(crate::trace::trace_leaf(cx));
if self.yielded {
return Poll::Ready(());
}
self.yielded = true;
// context::defer(cx.waker());
Poll::Pending
}
}
YieldNow { yielded: false }.await;
}
#[derive(Clone)]
pub struct ForegroundExecutor {
session_id: SessionId,