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:
authored and GitHub committed 2025-12-16 23:04:16 -07:00
1 parent 897f193cf2
commit 9da3328ace
2 files changed
+50 -12

No files matched your search

+35 -11
View File
@@ -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
View File
@@ -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();