Fix NewWindow flicker by creating buffer synchronously (#44915)
Closes #20613 Release Notes: - Fixed: New windows no longer flicker between "Open a file or project to get started" and an empty editor. --- When opening a new window (`cmd-shift-n`), the window rendered showing the empty state message before the editor was created, causing a visible flicker. **Changes:** - Modified `Workspace::new_local` to accept an optional `init` callback that executes inside the window build closure - The init callback runs within `cx.new` (the `build_root_view` closure), before `window.draw()` is called for the first render - Changed the NewWindow action handler to use `Project::create_local_buffer()` (synchronous) instead of `Editor::new_file()` (asynchronous) - Updated `open_new` to pass the editor creation callback to `new_local` - All other `new_local` call sites pass `None` to maintain existing behavior **Key Technical Detail:** The window creation sequence in `cx.open_window()` is: 1. `build_root_view` closure is called (creates workspace via `cx.new`) 2. `window.draw(cx)` is called (first render) 3. `open_window` returns The fix uses `Project::create_local_buffer()` which creates a buffer **synchronously** (returns `Entity<Buffer>` directly), rather than `Editor::new_file()` which is asynchronous (calls `project.create_buffer()` which returns a `Task`). The editor is created from this buffer inside the `cx.new` closure (step 1), ensuring it exists before step 2 renders the first frame. **Before:** ```rust let task = Workspace::new_local(Vec::new(), app_state, None, env, cx); cx.spawn(async move |cx| { let (workspace, _) = task.await?; // Window already drawn workspace.update(cx, |workspace, window, cx| { Editor::new_file(workspace, ...) // Async - editor not present for first render })?; }) ``` **After:** ```rust cx.open_window(options, { move |window, cx| { cx.new(|cx| { let mut workspace = Workspace::new(...); // Create buffer synchronously, then create editor if let Some(init) = init { init(&mut workspace, window, cx); // Uses create_local_buffer (sync) } workspace }) } })? ``` The editor is now part of the workspace before the window's first frame is rendered, eliminating the flicker. <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Opening a new window flickers before opening an empty buffer</issue_title> > <issue_description>### Check for existing issues > > - [x] Completed > > ### Describe the bug / provide steps to reproduce it > > Opening a new window, with e.g. `cmd-shift-n`, flickers for a fraction of a second. The new window first shows the startup page, "Open a file or project to get started.". Then, a frame or two later, a new empty buffer opens. > > Not sure if I'm sensitive or something but these kinds of flashes can knock me out of focus/flow pretty easily. > > It'd be great to either have the empty buffer open from the first frame, or to have an option to simply not open that empty buffer when a new window is opened. > > ### Zed Version and System Specs > > Zed: v0.170.4 (Zed) > OS: macOS 14.6.1 > Memory: 36 GiB > Architecture: aarch64 > > ### If applicable, add screenshots or screencasts of the incorrect state / behavior > > https://github.com/user-attachments/assets/6d9ba791-8a02-4e13-857c-66a33eb0905b > > ### If applicable, attach your Zed.log file to this issue. > > N/A</issue_description> > > <agent_instructions>We should make sure that the window is created in the correct state, and not have an intermediate render before the editor opens.</agent_instructions> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > <comment_new><author>@ConradIrwin</author><body> > Ugh, no. I don't believe I never noticed this before, but now I can't unsee it :s > > If you'd like to pair on this: https://cal.com/conradirwin/pairing, otherwise I'll see if I get around to it.</body></comment_new> > <comment_new><author>@ConradIrwin</author><body> > Yeah... I wonder if that can be a preview tab or something. It's nice when you want it, but not so nice when you don't. > > Fixing this will also make zed-industries/zed#33334 feel much smoother.</body></comment_new> > <comment_new><author>@zelenenka</author><body> > @robinplace do you maybe have an opportunity to test it with the latest stable version, 0.213.3?</body></comment_new> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes zed-industries/zed#23742 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ConradIrwin <94272+ConradIrwin@users.noreply.github.com> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
1 parent
897f193cf2
commit
9da3328ace
2 files changed
+50
-12
No files matched your search
@@ -1627,6 +1627,7 @@ impl Workspace {
|
||||
app_state: Arc<AppState>,
|
||||
requesting_window: Option<WindowHandle<Workspace>>,
|
||||
env: Option<HashMap<String, String>>,
|
||||
init: Option<Box<dyn FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + Send>>,
|
||||
cx: &mut App,
|
||||
) -> Task<
|
||||
anyhow::Result<(
|
||||
@@ -1734,6 +1735,12 @@ impl Workspace {
|
||||
);
|
||||
|
||||
workspace.centered_layout = centered_layout;
|
||||
|
||||
// Call init callback to add items before window renders
|
||||
if let Some(init) = init {
|
||||
init(&mut workspace, window, cx);
|
||||
}
|
||||
|
||||
workspace
|
||||
});
|
||||
})?;
|
||||
@@ -1785,6 +1792,12 @@ impl Workspace {
|
||||
cx,
|
||||
);
|
||||
workspace.centered_layout = centered_layout;
|
||||
|
||||
// Call init callback to add items before window renders
|
||||
if let Some(init) = init {
|
||||
init(&mut workspace, window, cx);
|
||||
}
|
||||
|
||||
workspace
|
||||
})
|
||||
}
|
||||
@@ -2350,7 +2363,7 @@ impl Workspace {
|
||||
Task::ready(Ok(callback(self, window, cx)))
|
||||
} else {
|
||||
let env = self.project.read(cx).cli_environment(cx);
|
||||
let task = Self::new_local(Vec::new(), self.app_state.clone(), None, env, cx);
|
||||
let task = Self::new_local(Vec::new(), self.app_state.clone(), None, env, None, cx);
|
||||
cx.spawn_in(window, async move |_vh, cx| {
|
||||
let (workspace, _) = task.await?;
|
||||
workspace.update(cx, callback)
|
||||
@@ -7758,7 +7771,14 @@ pub fn join_channel(
|
||||
// no open workspaces, make one to show the error in (blergh)
|
||||
let (window_handle, _) = cx
|
||||
.update(|cx| {
|
||||
Workspace::new_local(vec![], app_state.clone(), requesting_window, None, cx)
|
||||
Workspace::new_local(
|
||||
vec![],
|
||||
app_state.clone(),
|
||||
requesting_window,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
@@ -7824,7 +7844,7 @@ pub async fn get_any_active_workspace(
|
||||
// find an existing workspace to focus and show call controls
|
||||
let active_window = activate_any_workspace_window(&mut cx);
|
||||
if active_window.is_none() {
|
||||
cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, cx))?
|
||||
cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, None, None, cx))?
|
||||
.await?;
|
||||
}
|
||||
activate_any_workspace_window(&mut cx).context("could not open zed")
|
||||
@@ -7991,6 +8011,7 @@ pub fn open_paths(
|
||||
app_state.clone(),
|
||||
open_options.replace_window,
|
||||
open_options.env,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
@@ -8035,14 +8056,17 @@ pub fn open_new(
|
||||
cx: &mut App,
|
||||
init: impl FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) + 'static + Send,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
let task = Workspace::new_local(Vec::new(), app_state, None, open_options.env, cx);
|
||||
cx.spawn(async move |cx| {
|
||||
let (workspace, opened_paths) = task.await?;
|
||||
workspace.update(cx, |workspace, window, cx| {
|
||||
if opened_paths.is_empty() {
|
||||
init(workspace, window, cx)
|
||||
}
|
||||
})?;
|
||||
let task = Workspace::new_local(
|
||||
Vec::new(),
|
||||
app_state,
|
||||
None,
|
||||
open_options.env,
|
||||
Some(Box::new(init)),
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_cx| {
|
||||
let (_workspace, _opened_paths) = task.await?;
|
||||
// Init callback is called synchronously during workspace creation
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
+15
-1
@@ -1111,7 +1111,21 @@ fn register_actions(
|
||||
cx,
|
||||
|workspace, window, cx| {
|
||||
cx.activate(true);
|
||||
Editor::new_file(workspace, &Default::default(), window, cx)
|
||||
// Create buffer synchronously to avoid flicker
|
||||
let project = workspace.project().clone();
|
||||
let buffer = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer("", None, true, cx)
|
||||
});
|
||||
let editor = cx.new(|cx| {
|
||||
Editor::for_buffer(buffer, Some(project), window, cx)
|
||||
});
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(editor),
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
||||
Reference in new issue
Block a user