Compare commits
14 Commits
github-tok
...
v0.126.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
150e021fd0 | ||
|
|
2ecc6e1625 | ||
|
|
665e09bb3e | ||
|
|
60fa3bc1c8 | ||
|
|
2423c60a9f | ||
|
|
22a7eb681e | ||
|
|
bf8974c561 | ||
|
|
5e7f554cc3 | ||
|
|
1d3fe837f0 | ||
|
|
6d9e40c395 | ||
|
|
8845e68233 | ||
|
|
e0034ec633 | ||
|
|
5a82dc2233 | ||
|
|
afed6f4980 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ DerivedData/
|
||||
.pytest_cache
|
||||
.venv
|
||||
.blob_store
|
||||
extensions/gleam/grammars
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -12824,7 +12824,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.126.0"
|
||||
version = "0.126.2"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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())?;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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(),
|
||||
))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -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))?
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user