Compare commits

...

14 Commits

Author SHA1 Message Date
Joseph T. Lyons
150e021fd0 v0.126.x stable 2024-03-13 11:57:21 -04:00
Kirill Bulatov
2ecc6e1625 Omit .git worktree indexing (#9281)
Closes https://github.com/zed-industries/zed/issues/9174

Release Notes:

- Fixed panics when `.git` was opened as a Zed worktree
([9174](https://github.com/zed-industries/zed/issues/9174))
2024-03-13 16:37:56 +02:00
gcp-cherry-pick-bot[bot]
665e09bb3e Fix segfault when dropping MacWindow (cherry-pick #9267) (#9269)
Cherry-picked Fix segfault when dropping MacWindow (#9267)

This avoids calling `window.setDelegate(nil)` when the window was
already closed.

Release Notes:

- Fixed a segfault that could show up when closing windows.

Co-authored-by: Antonio <antonio@zed.dev>

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
Co-authored-by: Antonio <antonio@zed.dev>
2024-03-13 10:43:20 +01:00
gcp-cherry-pick-bot[bot]
60fa3bc1c8 Show only prefix/suffix if there are more than 12 breadcrumbs (cherry-pick #9220) (#9227)
Cherry-picked Show only prefix/suffix if there are more than 12
breadcrumbs (#9220)

Fixes https://github.com/zed-industries/zed/issues/9079

This should fix the arena panic we were observing. I saw that breadcrumb
rendering was on the stack trace for some of the panics, so my suspicion
is that it's being caused by some people navigating into deeply nested
files.

Release Notes:

- Fixed a panic that could occur when displaying too many breadcrumbs.
([#9079](https://github.com/zed-industries/zed/issues/9079))

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2024-03-12 10:22:55 -06:00
Thorsten Ball
2423c60a9f zed 0.126.2 2024-03-12 11:49:45 +01:00
Thorsten Ball
22a7eb681e Fix broken ESLint by pinning to 2.2.20-Insiders release (#9215)
This fixes #9213 by pinning ESLint to `2.2.20-Insiders` which is the
last known version to work well with Zed.

Once this fix is out, we can take a closer look at upgrading to 2.4.x or
even 3.x once that's out of prerelease.

Release Notes:

- Fixed ESLint integration being broken after Mar 7 2024 due to ESLint
3.0.1 alpha release being pushed.
([#9213](https://github.com/zed-industries/zed/issues/9213)).
2024-03-12 11:48:18 +01:00
Marshall Bowers
bf8974c561 v0.126.x: Reload extensions if manifest parsing fails (#9186)
This PR fixes an issue where a malformed extension manifest could cause
the extensions to not be loaded.

We now default to reloading, and only skip reloading if we know it's
safe to do so.

This is an adaption of the change we made here:
ab8a9f9a6f

Release Notes:

- N/A
2024-03-11 12:42:04 -04:00
gcp-cherry-pick-bot[bot]
5e7f554cc3 Fix panic in layout_line when Y coordinate is too high (cherry-pick #9052) (#9075)
Cherry-picked Fix panic in layout_line when Y coordinate is too high
(#9052)

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-03-08 12:39:56 -07:00
Conrad Irwin
1d3fe837f0 try to fix build 2024-03-08 10:04:27 -07:00
Conrad Irwin
6d9e40c395 zed 0.126.1 2024-03-07 16:14:10 -07:00
gcp-cherry-pick-bot[bot]
8845e68233 Fix panic in open urls (cherry-pick #9032) (#9034)
Cherry-picked Fix panic in open urls (#9032)

Co-Authored-By: Nathan <nathan@zed.dev>

Release Notes:

- N/A

Co-authored-by: Nathan <nathan@zed.dev>

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Nathan <nathan@zed.dev>
2024-03-07 14:29:41 -07:00
gcp-cherry-pick-bot[bot]
e0034ec633 Make anchor_in_excerpt Optional (cherry-pick #8975) (#8979)
Cherry-picked Make anchor_in_excerpt Optional (#8975)

We were seeing panics due to callers assuming they had valid
excerpt_ids, but that cannot easily be guaranteed across await points as
anyone may remove an excerpt.

Release Notes:

- Fixed a panic when hovering in a multibuffer

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-03-06 20:42:38 -07:00
Max Brunsfeld
5a82dc2233 Emit the WorktreeUpdatedEntries event for all projects, not just local (#8963)
Fixes a regression introduced in
https://github.com/zed-industries/zed/pull/8846 (which hasn't yet been
released), in which the project panel didn't update correctly for remote
projects when collaborating.

Release Notes:

- N/A
2024-03-06 15:17:11 -05:00
Joseph T. Lyons
afed6f4980 v0.126.x preview 2024-03-06 12:29:47 -05:00
31 changed files with 469 additions and 342 deletions

1
.gitignore vendored
View File

@@ -23,3 +23,4 @@ DerivedData/
.pytest_cache
.venv
.blob_store
extensions/gleam/grammars

2
Cargo.lock generated
View File

@@ -12824,7 +12824,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.126.0"
version = "0.126.2"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -2413,7 +2413,9 @@ impl ConversationEditor {
.read(cx)
.messages(cx)
.map(|message| BlockProperties {
position: buffer.anchor_in_excerpt(excerpt_id, message.anchor),
position: buffer
.anchor_in_excerpt(excerpt_id, message.anchor)
.unwrap(),
height: 2,
style: BlockStyle::Sticky,
render: Arc::new({

View File

@@ -4,10 +4,11 @@ use gpui::{
ViewContext,
};
use itertools::Itertools;
use std::cmp;
use theme::ActiveTheme;
use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
use workspace::{
item::{ItemEvent, ItemHandle},
item::{BreadcrumbText, ItemEvent, ItemHandle},
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
};
@@ -31,14 +32,30 @@ impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
const MAX_SEGMENTS: usize = 12;
let element = h_flex().text_ui();
let Some(active_item) = self.active_item.as_ref() else {
return element;
};
let Some(segments) = active_item.breadcrumbs(cx.theme(), cx) else {
let Some(mut segments) = active_item.breadcrumbs(cx.theme(), cx) else {
return element;
};
let prefix_end_ix = cmp::min(segments.len(), MAX_SEGMENTS / 2);
let suffix_start_ix = cmp::max(
prefix_end_ix,
segments.len().saturating_sub(MAX_SEGMENTS / 2),
);
if suffix_start_ix > prefix_end_ix {
segments.splice(
prefix_end_ix..suffix_start_ix,
Some(BreadcrumbText {
text: "".into(),
highlights: None,
}),
);
}
let highlighted_segments = segments.into_iter().map(|segment| {
let mut text_style = cx.text_style();
text_style.color = Color::Muted.color(cx);

View File

@@ -517,15 +517,15 @@ impl ProjectDiagnosticsEditor {
self.editor.update(cx, |editor, cx| {
editor.remove_blocks(blocks_to_remove, None, cx);
let block_ids = editor.insert_blocks(
blocks_to_add.into_iter().map(|block| {
blocks_to_add.into_iter().flat_map(|block| {
let (excerpt_id, text_anchor) = block.position;
BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
Some(BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
height: block.height,
style: block.style,
render: block.render,
disposition: block.disposition,
}
})
}),
Some(Autoscroll::fit()),
cx,
@@ -589,14 +589,16 @@ impl ProjectDiagnosticsEditor {
Ok(ix) | Err(ix) => ix,
};
if let Some(group) = groups.get(group_ix) {
let offset = excerpts_snapshot
if let Some(offset) = excerpts_snapshot
.anchor_in_excerpt(
group.excerpts[group.primary_excerpt_ix],
group.primary_diagnostic.range.start,
)
.to_offset(&excerpts_snapshot);
selection.start = offset;
selection.end = offset;
.map(|anchor| anchor.to_offset(&excerpts_snapshot))
{
selection.start = offset;
selection.end = offset;
}
}
}
}

View File

@@ -249,7 +249,7 @@ pub fn init(cx: &mut AppContext) {
cx.on_action(move |_: &workspace::NewFile, cx| {
let app_state = workspace::AppState::global(cx);
if let Some(app_state) = app_state.upgrade() {
workspace::open_new(&app_state, cx, |workspace, cx| {
workspace::open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();
@@ -258,7 +258,7 @@ pub fn init(cx: &mut AppContext) {
cx.on_action(move |_: &workspace::NewWindow, cx| {
let app_state = workspace::AppState::global(cx);
if let Some(app_state) = app_state.upgrade() {
workspace::open_new(&app_state, cx, |workspace, cx| {
workspace::open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();

View File

@@ -13,7 +13,7 @@ use project::{
};
use std::ops::Range;
use theme::ActiveTheme as _;
use util::TryFutureExt;
use util::{maybe, TryFutureExt};
#[derive(Debug)]
pub struct HoveredLinkState {
@@ -424,12 +424,13 @@ pub fn show_link_definition(
TriggerPoint::Text(_) => {
if let Some((url_range, url)) = find_url(&buffer, buffer_position, cx.clone()) {
this.update(&mut cx, |_, _| {
let start = snapshot.anchor_in_excerpt(excerpt_id, url_range.start);
let end = snapshot.anchor_in_excerpt(excerpt_id, url_range.end);
(
Some(RangeInEditor::Text(start..end)),
vec![HoverLink::Url(url)],
)
let range = maybe!({
let start =
snapshot.anchor_in_excerpt(excerpt_id, url_range.start)?;
let end = snapshot.anchor_in_excerpt(excerpt_id, url_range.end)?;
Some(RangeInEditor::Text(start..end))
});
(range, vec![HoverLink::Url(url)])
})
.ok()
} else if let Some(project) = project {
@@ -449,12 +450,14 @@ pub fn show_link_definition(
.map(|definition_result| {
(
definition_result.iter().find_map(|link| {
link.origin.as_ref().map(|origin| {
let start = snapshot
.anchor_in_excerpt(excerpt_id, origin.range.start);
link.origin.as_ref().and_then(|origin| {
let start = snapshot.anchor_in_excerpt(
excerpt_id,
origin.range.start,
)?;
let end = snapshot
.anchor_in_excerpt(excerpt_id, origin.range.end);
RangeInEditor::Text(start..end)
.anchor_in_excerpt(excerpt_id, origin.range.end)?;
Some(RangeInEditor::Text(start..end))
})
}),
definition_result.into_iter().map(HoverLink::Text).collect(),

View File

@@ -294,18 +294,19 @@ fn show_hover(
let hover_popover = match hover_result {
Some(hover_result) if !hover_result.is_empty() => {
// Create symbol range of anchors for highlighting and filtering of future requests.
let range = if let Some(range) = hover_result.range {
let start = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start);
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end);
let range = hover_result
.range
.and_then(|range| {
let start = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start)?;
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end)?;
start..end
} else {
anchor..anchor
};
Some(start..end)
})
.unwrap_or_else(|| anchor..anchor);
let language_registry =
project.update(&mut cx, |p, _| p.languages().clone())?;

View File

@@ -460,14 +460,15 @@ impl InlayHintCache {
if !old_kinds.contains(&cached_hint.kind)
&& new_kinds.contains(&cached_hint.kind)
{
to_insert.push(Inlay::hint(
cached_hint_id.id(),
multi_buffer_snapshot.anchor_in_excerpt(
*excerpt_id,
cached_hint.position,
),
&cached_hint,
));
if let Some(anchor) = multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, cached_hint.position)
{
to_insert.push(Inlay::hint(
cached_hint_id.id(),
anchor,
&cached_hint,
));
}
}
excerpt_cache.next();
}
@@ -483,12 +484,15 @@ impl InlayHintCache {
let maybe_missed_cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id];
let cached_hint_kind = maybe_missed_cached_hint.kind;
if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
to_insert.push(Inlay::hint(
cached_hint_id.id(),
multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
&maybe_missed_cached_hint,
));
if let Some(anchor) = multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position)
{
to_insert.push(Inlay::hint(
cached_hint_id.id(),
anchor,
&maybe_missed_cached_hint,
));
}
}
}
}
@@ -1200,11 +1204,13 @@ fn apply_hint_update(
.allowed_hint_kinds
.contains(&new_hint.kind)
{
let new_hint_position =
multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
splice
.to_insert
.push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
if let Some(new_hint_position) =
multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position)
{
splice
.to_insert
.push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
}
}
let new_id = InlayId::Hint(new_inlay_id);
cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);

View File

@@ -1154,8 +1154,8 @@ impl SearchableItem for Editor {
let end = excerpt
.buffer
.anchor_before(excerpt_range.start + range.end);
buffer.anchor_in_excerpt(excerpt.id, start)
..buffer.anchor_in_excerpt(excerpt.id, end)
buffer.anchor_in_excerpt(excerpt.id, start).unwrap()
..buffer.anchor_in_excerpt(excerpt.id, end).unwrap()
}),
);
}

View File

@@ -261,21 +261,23 @@ impl ExtensionStore {
)
});
let mut should_reload = true;
if let Some(manifest_content) = manifest_content.log_err() {
if let Some(manifest) = serde_json::from_str(&manifest_content).log_err() {
// TODO: don't detach
self.extensions_updated(manifest, cx).detach();
if let (Ok(Some(manifest_metadata)), Ok(Some(extensions_metadata))) =
(manifest_metadata, extensions_metadata)
{
if manifest_metadata.mtime > extensions_metadata.mtime {
should_reload = false;
}
}
}
}
let should_reload = if let (Ok(Some(manifest_metadata)), Ok(Some(extensions_metadata))) =
(manifest_metadata, extensions_metadata)
{
extensions_metadata.mtime > manifest_metadata.mtime
} else {
true
};
if should_reload {
self.reload(cx)
}

View File

@@ -150,14 +150,9 @@ impl App {
/// to open one or more URLs.
pub fn on_open_urls<F>(&self, mut callback: F) -> &Self
where
F: 'static + FnMut(Vec<String>, &mut AppContext),
F: 'static + FnMut(Vec<String>),
{
let this = Rc::downgrade(&self.0);
self.0.borrow().platform.on_open_urls(Box::new(move |urls| {
if let Some(app) = this.upgrade() {
callback(urls, &mut app.borrow_mut());
}
}));
self.0.borrow().platform.on_open_urls(Box::new(callback));
self
}

View File

@@ -738,10 +738,11 @@ impl Drop for MacWindow {
this.renderer.destroy();
let window = this.native_window;
this.display_link.take();
unsafe {
this.native_window.setDelegate_(nil);
}
if !this.native_window_was_closed {
unsafe {
this.native_window.setDelegate_(nil);
}
this.executor
.spawn(async move {
unsafe {

View File

@@ -247,10 +247,11 @@ impl WrappedLineLayout {
let wrapped_line_ix = (position.y / line_height) as usize;
let wrapped_line_start_x = if wrapped_line_ix > 0 {
let wrap_boundary_ix = wrapped_line_ix - 1;
let wrap_boundary = self.wrap_boundaries[wrap_boundary_ix];
let run = &self.unwrapped_layout.runs[wrap_boundary.run_ix];
run.glyphs[wrap_boundary.glyph_ix].position.x
let Some(line_start_boundary) = self.wrap_boundaries.get(wrapped_line_ix - 1) else {
return None;
};
let run = &self.unwrapped_layout.runs[line_start_boundary.run_ix];
run.glyphs[line_start_boundary.glyph_ix].position.x
} else {
Pixels::ZERO
};

View File

@@ -102,7 +102,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut WindowContext) {
cx.spawn(|mut cx| async move {
let (journal_dir, entry_path) = create_entry.await?;
let (workspace, _) = cx
.update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?
.update(|cx| workspace::open_paths(&[journal_dir], app_state, None, cx))?
.await?;
let opened = workspace

View File

@@ -227,8 +227,12 @@ impl SyntaxTreeView {
let multibuffer = editor_state.editor.read(cx).buffer();
let multibuffer = multibuffer.read(cx).snapshot(cx);
let excerpt_id = buffer_state.excerpt_id;
let range = multibuffer.anchor_in_excerpt(excerpt_id, range.start)
..multibuffer.anchor_in_excerpt(excerpt_id, range.end);
let range = multibuffer
.anchor_in_excerpt(excerpt_id, range.start)
.unwrap()
..multibuffer
.anchor_in_excerpt(excerpt_id, range.end)
.unwrap();
// Update the editor with the anchor range.
editor_state.editor.update(cx, |editor, cx| {

View File

@@ -20,7 +20,7 @@ use std::{
use util::{
async_maybe,
fs::remove_matching,
github::{latest_github_release, GitHubLspBinaryVersion},
github::{github_release_with_tag, GitHubLspBinaryVersion},
ResultExt,
};
@@ -283,13 +283,11 @@ impl LspAdapter for EsLintLspAdapter {
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
// At the time of writing the latest vscode-eslint release was released in 2020 and requires
// special custom LSP protocol extensions be handled to fully initialize. Download the latest
// prerelease instead to sidestep this issue
let release = latest_github_release(
// We're using this hardcoded release tag, because ESLint's API changed with
// >= 2.3 and we haven't upgraded yet.
let release = github_release_with_tag(
"microsoft/vscode-eslint",
false,
true,
"release/2.2.20-Insider",
delegate.http_client(),
)
.await?;

View File

@@ -2788,7 +2788,13 @@ impl MultiBufferSnapshot {
}
}
pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor {
/// Returns an anchor for the given excerpt and text anchor,
/// returns None if the excerpt_id is no longer valid.
pub fn anchor_in_excerpt(
&self,
excerpt_id: ExcerptId,
text_anchor: text::Anchor,
) -> Option<Anchor> {
let locator = self.excerpt_locator_for_id(excerpt_id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
cursor.seek(locator, Bias::Left, &());
@@ -2796,14 +2802,14 @@ impl MultiBufferSnapshot {
if excerpt.id == excerpt_id {
let text_anchor = excerpt.clip_anchor(text_anchor);
drop(cursor);
return Anchor {
return Some(Anchor {
buffer_id: Some(excerpt.buffer_id),
excerpt_id,
text_anchor,
};
});
}
}
panic!("excerpt not found");
None
}
pub fn can_resolve(&self, anchor: &Anchor) -> bool {
@@ -3283,13 +3289,15 @@ impl MultiBufferSnapshot {
outline
.items
.into_iter()
.map(|item| OutlineItem {
depth: item.depth,
range: self.anchor_in_excerpt(*excerpt_id, item.range.start)
..self.anchor_in_excerpt(*excerpt_id, item.range.end),
text: item.text,
highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges,
.flat_map(|item| {
Some(OutlineItem {
depth: item.depth,
range: self.anchor_in_excerpt(*excerpt_id, item.range.start)?
..self.anchor_in_excerpt(*excerpt_id, item.range.end)?,
text: item.text,
highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges,
})
})
.collect(),
))
@@ -3310,13 +3318,15 @@ impl MultiBufferSnapshot {
.symbols_containing(anchor.text_anchor, theme)
.into_iter()
.flatten()
.map(|item| OutlineItem {
depth: item.depth,
range: self.anchor_in_excerpt(excerpt_id, item.range.start)
..self.anchor_in_excerpt(excerpt_id, item.range.end),
text: item.text,
highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges,
.flat_map(|item| {
Some(OutlineItem {
depth: item.depth,
range: self.anchor_in_excerpt(excerpt_id, item.range.start)?
..self.anchor_in_excerpt(excerpt_id, item.range.end)?,
text: item.text,
highlight_ranges: item.highlight_ranges,
name_ranges: item.name_ranges,
})
})
.collect(),
))

View File

@@ -6745,24 +6745,30 @@ impl Project {
fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
cx.observe(worktree, |_, _, cx| cx.notify()).detach();
if worktree.read(cx).is_local() {
cx.subscribe(worktree, |this, worktree, event, cx| match event {
cx.subscribe(worktree, |this, worktree, event, cx| {
let is_local = worktree.read(cx).is_local();
match event {
worktree::Event::UpdatedEntries(changes) => {
this.update_local_worktree_buffers(&worktree, changes, cx);
this.update_local_worktree_language_servers(&worktree, changes, cx);
this.update_local_worktree_settings(&worktree, changes, cx);
this.update_prettier_settings(&worktree, changes, cx);
if is_local {
this.update_local_worktree_buffers(&worktree, changes, cx);
this.update_local_worktree_language_servers(&worktree, changes, cx);
this.update_local_worktree_settings(&worktree, changes, cx);
this.update_prettier_settings(&worktree, changes, cx);
}
cx.emit(Event::WorktreeUpdatedEntries(
worktree.read(cx).id(),
changes.clone(),
));
}
worktree::Event::UpdatedGitRepositories(updated_repos) => {
this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx)
if is_local {
this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx)
}
}
})
.detach();
}
}
})
.detach();
let push_strong_handle = {
let worktree = worktree.read(cx);

View File

@@ -2663,13 +2663,28 @@ impl BackgroundScannerState {
Arc<Mutex<dyn GitRepository>>,
TreeMap<RepoPath, GitFileStatus>,
)> {
log::info!("build git repository {:?}", dot_git_path);
let work_dir_path: Arc<Path> = dot_git_path.parent().unwrap().into();
// Guard against repositories inside the repository metadata
if work_dir_path.iter().any(|component| component == *DOT_GIT) {
return None;
let work_dir_path: Arc<Path> = match dot_git_path.parent() {
Some(parent_dir) => {
// Guard against repositories inside the repository metadata
if parent_dir.iter().any(|component| component == *DOT_GIT) {
log::info!(
"not building git repository for nested `.git` directory, `.git` path in the worktree: {dot_git_path:?}"
);
return None;
};
log::info!(
"building git repository, `.git` path in the worktree: {dot_git_path:?}"
);
parent_dir.into()
}
None => {
// `dot_git_path.parent().is_none()` means `.git` directory is the opened worktree itself,
// no files inside that directory are tracked by git, so no need to build the repo around it
log::info!(
"not building git repository for the worktree itself, `.git` path in the worktree: {dot_git_path:?}"
);
return None;
}
};
let work_dir_id = self

View File

@@ -1199,6 +1199,43 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_fs_events_in_dot_git_worktree(cx: &mut TestAppContext) {
init_test(cx);
cx.executor().allow_parking();
let dir = temp_tree(json!({
".git": {
"HEAD": "ref: refs/heads/main\n",
"foo": "foo contents",
},
}));
let dot_git_worktree_dir = dir.path().join(".git");
let tree = Worktree::local(
build_client(cx),
dot_git_worktree_dir.clone(),
true,
Arc::new(RealFs),
Default::default(),
&mut cx.to_async(),
)
.await
.unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _| {
check_worktree_entries(tree, &[], &["HEAD", "foo"], &[])
});
std::fs::write(dot_git_worktree_dir.join("new_file"), "new file contents")
.unwrap_or_else(|e| panic!("Failed to create in {dot_git_worktree_dir:?} a new file: {e}"));
tree.flush_fs_events(cx).await;
tree.read_with(cx, |tree, _| {
check_worktree_entries(tree, &[], &["HEAD", "foo", "new_file"], &[])
});
}
#[gpui::test(iterations = 30)]
async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
init_test(cx);

View File

@@ -486,7 +486,7 @@ mod tests {
}),
)
.await;
cx.update(|cx| open_paths(&[PathBuf::from("/dir/main.ts")], &app_state, None, cx))
cx.update(|cx| open_paths(&[PathBuf::from("/dir/main.ts")], app_state, None, cx))
.await
.unwrap();
assert_eq!(cx.update(|cx| cx.windows().len()), 1);

View File

@@ -3,6 +3,7 @@ use anyhow::{anyhow, bail, Context, Result};
use futures::AsyncReadExt;
use serde::Deserialize;
use std::sync::Arc;
use url::Url;
pub struct GitHubLspBinaryVersion {
pub name: String,
@@ -74,3 +75,70 @@ pub async fn latest_github_release(
.find(|release| release.pre_release == pre_release)
.ok_or(anyhow!("Failed to find a release"))
}
pub async fn github_release_with_tag(
repo_name_with_owner: &str,
tag: &str,
http: Arc<dyn HttpClient>,
) -> Result<GithubRelease, anyhow::Error> {
let url = build_tagged_release_url(repo_name_with_owner, tag)?;
let mut response = http
.get(&url, Default::default(), true)
.await
.context("error fetching latest release")?;
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading latest release")?;
if response.status().is_client_error() {
let text = String::from_utf8_lossy(body.as_slice());
bail!(
"status error {}, response: {text:?}",
response.status().as_u16()
);
}
match serde_json::from_slice::<GithubRelease>(body.as_slice()) {
Ok(release) => Ok(release),
Err(err) => {
log::error!("Error deserializing: {:?}", err);
log::error!(
"GitHub API response text: {:?}",
String::from_utf8_lossy(body.as_slice())
);
return Err(anyhow!("error deserializing latest release"));
}
}
}
fn build_tagged_release_url(repo_name_with_owner: &str, tag: &str) -> Result<String> {
let mut url = Url::parse(&format!(
"https://api.github.com/repos/{repo_name_with_owner}/releases/tags"
))?;
// We're pushing this here, because tags may contain `/` and other characters
// that need to be escaped.
url.path_segments_mut()
.map_err(|_| anyhow!("cannot modify url path segments"))?
.push(tag);
Ok(url.to_string())
}
#[cfg(test)]
mod tests {
use super::build_tagged_release_url;
#[test]
fn test_build_tagged_release_url() {
let tag = "release/2.2.20-Insider";
let repo_name_with_owner = "microsoft/vscode-eslint";
let have = build_tagged_release_url(repo_name_with_owner, tag).unwrap();
assert_eq!(have, "https://api.github.com/repos/microsoft/vscode-eslint/releases/tags/release%2F2.2.20-Insider");
}
}

View File

@@ -37,8 +37,8 @@ pub fn init(cx: &mut AppContext) {
base_keymap_picker::init(cx);
}
pub fn show_welcome_view(app_state: &Arc<AppState>, cx: &mut AppContext) {
open_new(&app_state, cx, |workspace, cx| {
pub fn show_welcome_view(app_state: Arc<AppState>, cx: &mut AppContext) {
open_new(app_state, cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Left, cx);
let welcome_page = WelcomePage::new(workspace, cx);
workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);

View File

@@ -262,7 +262,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
cx.spawn(move |cx| async move {
if let Some(paths) = paths.await.log_err().flatten() {
cx.update(|cx| {
open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
open_paths(&paths, app_state, None, cx).detach_and_log_err(cx)
})
.ok();
}
@@ -1414,7 +1414,7 @@ impl Workspace {
let app_state = self.app_state.clone();
cx.spawn(|_, mut cx| async move {
cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))?
cx.update(|cx| open_paths(&paths, app_state, window_to_replace, cx))?
.await?;
Ok(())
})
@@ -4381,7 +4381,7 @@ fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<WindowHandl
#[allow(clippy::type_complexity)]
pub fn open_paths(
abs_paths: &[PathBuf],
app_state: &Arc<AppState>,
app_state: Arc<AppState>,
requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut AppContext,
) -> Task<
@@ -4390,7 +4390,6 @@ pub fn open_paths(
Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
)>,
> {
let app_state = app_state.clone();
let abs_paths = abs_paths.to_vec();
// Open paths in existing workspace if possible
let existing = activate_workspace_for_project(cx, {
@@ -4417,11 +4416,11 @@ pub fn open_paths(
}
pub fn open_new(
app_state: &Arc<AppState>,
app_state: Arc<AppState>,
cx: &mut AppContext,
init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
) -> Task<()> {
let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
let task = Workspace::new_local(Vec::new(), app_state, None, cx);
cx.spawn(|mut cx| async move {
if let Some((workspace, opened_paths)) = task.await.log_err() {
workspace

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.126.0"
version = "0.126.2"
publish = false
license = "GPL-3.0-or-later"

View File

@@ -1 +1 @@
dev
stable

View File

@@ -106,11 +106,11 @@ fn main() {
let (listener, mut open_rx) = OpenListener::new();
let listener = Arc::new(listener);
let open_listener = listener.clone();
app.on_open_urls(move |urls, cx| open_listener.open_urls(&urls, cx));
app.on_open_urls(move |urls| open_listener.open_urls(urls));
app.on_reopen(move |cx| {
if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
{
workspace::open_new(&app_state, cx, |workspace, cx| {
workspace::open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();
@@ -273,7 +273,7 @@ fn main() {
cx.activate(true);
let urls = collect_url_args(cx);
if !urls.is_empty() {
listener.open_urls(&urls, cx)
listener.open_urls(urls)
}
} else {
upload_panics_and_crashes(http.clone(), cx);
@@ -282,141 +282,38 @@ fn main() {
if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some()
&& !listener.triggered.load(Ordering::Acquire)
{
listener.open_urls(&collect_url_args(cx), cx)
listener.open_urls(collect_url_args(cx))
}
}
let mut triggered_authentication = false;
fn open_paths_and_log_errs(
paths: &[PathBuf],
app_state: &Arc<AppState>,
cx: &mut AppContext,
) {
let task = workspace::open_paths(&paths, &app_state, None, cx);
cx.spawn(|_| async move {
if let Some((_window, results)) = task.await.log_err() {
for result in results.into_iter().flatten() {
if let Err(err) = result {
log::error!("Error opening path: {err}",);
}
}
}
})
.detach();
}
match open_rx.try_next() {
Ok(Some(OpenRequest::Paths { paths })) => {
open_paths_and_log_errs(&paths, &app_state, cx)
match open_rx
.try_next()
.ok()
.flatten()
.and_then(|urls| OpenRequest::parse(urls, cx).log_err())
{
Some(request) => {
triggered_authentication = handle_open_request(request, app_state.clone(), cx)
}
Ok(Some(OpenRequest::CliConnection { connection })) => {
let app_state = app_state.clone();
cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
.detach();
}
Ok(Some(OpenRequest::JoinChannel { channel_id })) => {
triggered_authentication = true;
let app_state = app_state.clone();
let client = client.clone();
cx.spawn(|cx| async move {
// ignore errors here, we'll show a generic "not signed in"
let _ = authenticate(client, &cx).await;
cx.update(|cx| {
workspace::join_channel(client::ChannelId(channel_id), app_state, None, cx)
})?
.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
Ok(Some(OpenRequest::OpenChannelNotes {
channel_id,
heading,
})) => {
triggered_authentication = true;
let app_state = app_state.clone();
let client = client.clone();
cx.spawn(|mut cx| async move {
// ignore errors here, we'll show a generic "not signed in"
let _ = authenticate(client, &cx).await;
let workspace_window =
workspace::get_any_active_workspace(app_state, cx.clone()).await?;
let workspace = workspace_window.root_view(&cx)?;
cx.update_window(workspace_window.into(), |_, cx| {
ChannelView::open(client::ChannelId(channel_id), heading, workspace, cx)
})?
.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
Ok(None) | Err(_) => cx
None => cx
.spawn({
let app_state = app_state.clone();
|cx| async move { restore_or_create_workspace(&app_state, cx).await }
|cx| async move { restore_or_create_workspace(app_state, cx).await }
})
.detach(),
}
let app_state = app_state.clone();
cx.spawn(move |cx| async move {
while let Some(request) = open_rx.next().await {
match request {
OpenRequest::Paths { paths } => {
cx.update(|cx| open_paths_and_log_errs(&paths, &app_state, cx))
.ok();
while let Some(urls) = open_rx.next().await {
cx.update(|cx| {
if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
handle_open_request(request, app_state.clone(), cx);
}
OpenRequest::CliConnection { connection } => {
let app_state = app_state.clone();
cx.spawn(move |cx| {
handle_cli_connection(connection, app_state.clone(), cx)
})
.detach();
}
OpenRequest::JoinChannel { channel_id } => {
let app_state = app_state.clone();
cx.update(|mut cx| {
cx.spawn(|cx| async move {
cx.update(|cx| {
workspace::join_channel(
client::ChannelId(channel_id),
app_state,
None,
cx,
)
})?
.await?;
anyhow::Ok(())
})
.detach_and_log_err(&mut cx);
})
.log_err();
}
OpenRequest::OpenChannelNotes {
channel_id,
heading,
} => {
let app_state = app_state.clone();
let open_notes_task = cx.spawn(|mut cx| async move {
let workspace_window =
workspace::get_any_active_workspace(app_state, cx.clone()).await?;
let workspace = workspace_window.root_view(&cx)?;
cx.update_window(workspace_window.into(), |_, cx| {
ChannelView::open(
client::ChannelId(channel_id),
heading,
workspace,
cx,
)
})?
.await?;
anyhow::Ok(())
});
cx.update(|cx| open_notes_task.detach_and_log_err(cx))
.log_err();
}
}
})
.ok();
}
})
.detach();
@@ -428,6 +325,70 @@ fn main() {
});
}
fn open_paths_and_log_errs(paths: &[PathBuf], app_state: Arc<AppState>, cx: &mut AppContext) {
let task = workspace::open_paths(&paths, app_state, None, cx);
cx.spawn(|_| async move {
if let Some((_window, results)) = task.await.log_err() {
for result in results.into_iter().flatten() {
if let Err(err) = result {
log::error!("Error opening path: {err}",);
}
}
}
})
.detach();
}
fn handle_open_request(
request: OpenRequest,
app_state: Arc<AppState>,
cx: &mut AppContext,
) -> bool {
let mut triggered_authentication = false;
match request {
OpenRequest::Paths { paths } => open_paths_and_log_errs(&paths, app_state, cx),
OpenRequest::CliConnection { connection } => {
let app_state = app_state.clone();
cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
.detach();
}
OpenRequest::JoinChannel { channel_id } => {
triggered_authentication = true;
cx.spawn(|cx| async move {
// ignore errors here, we'll show a generic "not signed in"
let _ = authenticate(app_state.client.clone(), &cx).await;
cx.update(|cx| {
workspace::join_channel(client::ChannelId(channel_id), app_state, None, cx)
})?
.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
OpenRequest::OpenChannelNotes {
channel_id,
heading,
} => {
triggered_authentication = true;
let client = app_state.client.clone();
cx.spawn(|mut cx| async move {
// ignore errors here, we'll show a generic "not signed in"
let _ = authenticate(client, &cx).await;
let workspace_window =
workspace::get_any_active_workspace(app_state, cx.clone()).await?;
let workspace = workspace_window.root_view(&cx)?;
cx.update_window(workspace_window.into(), |_, cx| {
ChannelView::open(client::ChannelId(channel_id), heading, workspace, cx)
})?
.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
}
triggered_authentication
}
async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
if stdout_is_a_pty() {
if client::IMPERSONATE_LOGIN.is_some() {
@@ -465,7 +426,7 @@ async fn installation_id() -> Result<(String, bool)> {
Ok((installation_id, false))
}
async fn restore_or_create_workspace(app_state: &Arc<AppState>, cx: AsyncAppContext) {
async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) {
async_maybe!({
if let Some(location) = workspace::last_opened_workspace_paths().await {
cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))?

View File

@@ -37,8 +37,66 @@ pub enum OpenRequest {
},
}
impl OpenRequest {
pub fn parse(urls: Vec<String>, cx: &AppContext) -> Result<Self> {
if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) {
Self::parse_cli_connection(server_name)
} else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url, cx)) {
Self::parse_zed_url(request_path)
} else {
Ok(Self::parse_file_urls(urls))
}
}
fn parse_cli_connection(server_name: &str) -> Result<OpenRequest> {
let connection = connect_to_cli(server_name)?;
Ok(OpenRequest::CliConnection { connection })
}
fn parse_zed_url(request_path: &str) -> Result<OpenRequest> {
let mut parts = request_path.split('/');
if parts.next() == Some("channel") {
if let Some(slug) = parts.next() {
if let Some(id_str) = slug.split('-').last() {
if let Ok(channel_id) = id_str.parse::<u64>() {
let Some(next) = parts.next() else {
return Ok(OpenRequest::JoinChannel { channel_id });
};
if let Some(heading) = next.strip_prefix("notes#") {
return Ok(OpenRequest::OpenChannelNotes {
channel_id,
heading: Some([heading].into_iter().chain(parts).join("/")),
});
} else if next == "notes" {
return Ok(OpenRequest::OpenChannelNotes {
channel_id,
heading: None,
});
}
}
}
}
}
Err(anyhow!("invalid zed url: {}", request_path))
}
fn parse_file_urls(urls: Vec<String>) -> OpenRequest {
let paths: Vec<_> = urls
.iter()
.flat_map(|url| url.strip_prefix("file://"))
.flat_map(|url| {
let decoded = urlencoding::decode_binary(url.as_bytes());
PathBuf::try_from_bytes(decoded.as_ref()).log_err()
})
.collect();
OpenRequest::Paths { paths }
}
}
pub struct OpenListener {
tx: UnboundedSender<OpenRequest>,
tx: UnboundedSender<Vec<String>>,
pub triggered: AtomicBool,
}
@@ -55,7 +113,7 @@ impl OpenListener {
cx.set_global(GlobalOpenListener(listener))
}
pub fn new() -> (Self, UnboundedReceiver<OpenRequest>) {
pub fn new() -> (Self, UnboundedReceiver<Vec<String>>) {
let (tx, rx) = mpsc::unbounded();
(
OpenListener {
@@ -66,74 +124,12 @@ impl OpenListener {
)
}
pub fn open_urls(&self, urls: &[String], cx: &AppContext) {
pub fn open_urls(&self, urls: Vec<String>) {
self.triggered.store(true, Ordering::Release);
let request = if let Some(server_name) =
urls.first().and_then(|url| url.strip_prefix("zed-cli://"))
{
self.handle_cli_connection(server_name)
} else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url, cx)) {
self.handle_zed_url_scheme(request_path)
} else {
self.handle_file_urls(urls)
};
if let Some(request) = request {
self.tx
.unbounded_send(request)
.map_err(|_| anyhow!("no listener for open requests"))
.log_err();
}
}
fn handle_cli_connection(&self, server_name: &str) -> Option<OpenRequest> {
if let Some(connection) = connect_to_cli(server_name).log_err() {
return Some(OpenRequest::CliConnection { connection });
}
None
}
fn handle_zed_url_scheme(&self, request_path: &str) -> Option<OpenRequest> {
let mut parts = request_path.split('/');
if parts.next() == Some("channel") {
if let Some(slug) = parts.next() {
if let Some(id_str) = slug.split('-').last() {
if let Ok(channel_id) = id_str.parse::<u64>() {
let Some(next) = parts.next() else {
return Some(OpenRequest::JoinChannel { channel_id });
};
if let Some(heading) = next.strip_prefix("notes#") {
return Some(OpenRequest::OpenChannelNotes {
channel_id,
heading: Some([heading].into_iter().chain(parts).join("/")),
});
} else if next == "notes" {
return Some(OpenRequest::OpenChannelNotes {
channel_id,
heading: None,
});
}
}
}
}
}
log::error!("invalid zed url: {}", request_path);
None
}
fn handle_file_urls(&self, urls: &[String]) -> Option<OpenRequest> {
let paths: Vec<_> = urls
.iter()
.flat_map(|url| url.strip_prefix("file://"))
.flat_map(|url| {
let decoded = urlencoding::decode_binary(url.as_bytes());
PathBuf::try_from_bytes(decoded.as_ref()).log_err()
})
.collect();
Some(OpenRequest::Paths { paths })
self.tx
.unbounded_send(urls)
.map_err(|_| anyhow!("no listener for open requests"))
.log_err();
}
}
@@ -210,7 +206,7 @@ pub async fn handle_cli_connection(
let mut errored = false;
match cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx)) {
match cx.update(|cx| workspace::open_paths(&paths, app_state, None, cx)) {
Ok(task) => match task.await {
Ok((workspace, items)) => {
let mut item_release_futures = Vec::new();

View File

@@ -233,7 +233,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
cx.toggle_full_screen();
})
.register_action(|_, action: &OpenZedUrl, cx| {
OpenListener::global(cx).open_urls(&[action.url.clone()], cx)
OpenListener::global(cx).open_urls(vec![action.url.clone()])
})
.register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url))
.register_action(move |_, _: &IncreaseBufferFontSize, cx| {
@@ -399,7 +399,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
let app_state = Arc::downgrade(&app_state);
move |_, _: &NewWindow, cx| {
if let Some(app_state) = app_state.upgrade() {
open_new(&app_state, cx, |workspace, cx| {
open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();
@@ -410,7 +410,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
let app_state = Arc::downgrade(&app_state);
move |_, _: &NewFile, cx| {
if let Some(app_state) = app_state.upgrade() {
open_new(&app_state, cx, |workspace, cx| {
open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();
@@ -911,7 +911,7 @@ mod tests {
cx.update(|cx| {
open_paths(
&[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
&app_state,
app_state.clone(),
None,
cx,
)
@@ -920,7 +920,7 @@ mod tests {
.unwrap();
assert_eq!(cx.read(|cx| cx.windows().len()), 1);
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], app_state.clone(), None, cx))
.await
.unwrap();
assert_eq!(cx.read(|cx| cx.windows().len()), 1);
@@ -942,7 +942,7 @@ mod tests {
cx.update(|cx| {
open_paths(
&[PathBuf::from("/root/b"), PathBuf::from("/root/c")],
&app_state,
app_state.clone(),
None,
cx,
)
@@ -958,7 +958,7 @@ mod tests {
cx.update(|cx| {
open_paths(
&[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
&app_state,
app_state,
Some(window),
cx,
)
@@ -995,7 +995,7 @@ mod tests {
.insert_tree("/root", json!({"a": "hey"}))
.await;
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], app_state.clone(), None, cx))
.await
.unwrap();
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
@@ -1062,7 +1062,7 @@ mod tests {
assert!(!window_is_edited(window, cx));
// Opening the buffer again doesn't impact the window's edited state.
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], app_state, None, cx))
.await
.unwrap();
let editor = window
@@ -1100,7 +1100,7 @@ mod tests {
async fn test_new_empty_workspace(cx: &mut TestAppContext) {
let app_state = init_test(cx);
cx.update(|cx| {
open_new(&app_state, cx, |workspace, cx| {
open_new(app_state.clone(), cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
})
@@ -1291,7 +1291,7 @@ mod tests {
)
.await;
cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], app_state, None, cx))
.await
.unwrap();
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
@@ -1525,7 +1525,7 @@ mod tests {
Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(),
];
let (opened_workspace, new_items) = cx
.update(|cx| workspace::open_paths(&paths_to_open, &app_state, None, cx))
.update(|cx| workspace::open_paths(&paths_to_open, app_state, None, cx))
.await
.unwrap();

View File

@@ -28,5 +28,7 @@ extend-ignore-re = [
# crates/collab/src/api/events.rs
"rename = \"sesssion_id\"",
"doas",
# extensions/gleam/grammars
"_maybe_function_expresssion"
]
check-filename = true