Merge pull request #948 from zed-industries/project-browser-refinements
Add commands for manipulating files in the project panel
This commit is contained in:
Generated
+5
-2
@@ -63,9 +63,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.42"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
|
||||
checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
@@ -3329,7 +3329,10 @@ dependencies = [
|
||||
name = "project_panel"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"editor",
|
||||
"futures",
|
||||
"gpui",
|
||||
"postage",
|
||||
"project",
|
||||
"serde_json",
|
||||
"settings",
|
||||
|
||||
@@ -331,7 +331,9 @@
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"left": "project_panel::CollapseSelectedEntry",
|
||||
"right": "project_panel::ExpandSelectedEntry"
|
||||
"right": "project_panel::ExpandSelectedEntry",
|
||||
"f2": "project_panel::Rename",
|
||||
"backspace": "project_panel::Delete"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -937,6 +937,7 @@
|
||||
"top": 6,
|
||||
"bottom": 6
|
||||
},
|
||||
"indent_width": 20,
|
||||
"entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#8b8792",
|
||||
@@ -946,41 +947,42 @@
|
||||
"family": "Zed Mono",
|
||||
"color": "#8b8792",
|
||||
"size": 14
|
||||
},
|
||||
"hover": {
|
||||
"background": "#5852603d",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#e2dfe7",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active": {
|
||||
"background": "#5852605c",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#e2dfe7",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active_hover": {
|
||||
"background": "#5852603d",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#efecf4",
|
||||
"size": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"hovered_entry": {
|
||||
"height": 24,
|
||||
"background": "#5852603d",
|
||||
"icon_color": "#8b8792",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"filename_editor": {
|
||||
"background": "#26232a5c",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#e2dfe7",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"selected_entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#8b8792",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#e2dfe7",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"hovered_selected_entry": {
|
||||
"height": 24,
|
||||
"background": "#5852603d",
|
||||
"icon_color": "#8b8792",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#efecf4",
|
||||
"size": 14
|
||||
},
|
||||
"selection": {
|
||||
"cursor": "#576ddb",
|
||||
"selection": "#576ddb3d"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -937,6 +937,7 @@
|
||||
"top": 6,
|
||||
"bottom": 6
|
||||
},
|
||||
"indent_width": 20,
|
||||
"entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#585260",
|
||||
@@ -946,41 +947,42 @@
|
||||
"family": "Zed Mono",
|
||||
"color": "#585260",
|
||||
"size": 14
|
||||
},
|
||||
"hover": {
|
||||
"background": "#8b87921f",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#26232a",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active": {
|
||||
"background": "#8b87922e",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#26232a",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active_hover": {
|
||||
"background": "#8b87921f",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#19171c",
|
||||
"size": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"hovered_entry": {
|
||||
"height": 24,
|
||||
"background": "#8b87921f",
|
||||
"icon_color": "#585260",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"filename_editor": {
|
||||
"background": "#e2dfe72e",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#26232a",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"selected_entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#585260",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#26232a",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"hovered_selected_entry": {
|
||||
"height": 24,
|
||||
"background": "#8b87921f",
|
||||
"icon_color": "#585260",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#19171c",
|
||||
"size": 14
|
||||
},
|
||||
"selection": {
|
||||
"cursor": "#576ddb",
|
||||
"selection": "#576ddb3d"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+31
-29
@@ -937,6 +937,7 @@
|
||||
"top": 6,
|
||||
"bottom": 6
|
||||
},
|
||||
"indent_width": 20,
|
||||
"entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#555555",
|
||||
@@ -946,41 +947,42 @@
|
||||
"family": "Zed Mono",
|
||||
"color": "#808080",
|
||||
"size": 14
|
||||
},
|
||||
"hover": {
|
||||
"background": "#232323",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#f1f1f1",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active": {
|
||||
"background": "#2b2b2b",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#f1f1f1",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active_hover": {
|
||||
"background": "#232323",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#ffffff",
|
||||
"size": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"hovered_entry": {
|
||||
"height": 24,
|
||||
"background": "#232323",
|
||||
"icon_color": "#555555",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"filename_editor": {
|
||||
"background": "#ffffff1f",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#f1f1f1",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"selected_entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#555555",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#f1f1f1",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"hovered_selected_entry": {
|
||||
"height": 24,
|
||||
"background": "#232323",
|
||||
"icon_color": "#555555",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#ffffff",
|
||||
"size": 14
|
||||
},
|
||||
"selection": {
|
||||
"cursor": "#2472f2",
|
||||
"selection": "#2472f23d"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+31
-29
@@ -937,6 +937,7 @@
|
||||
"top": 6,
|
||||
"bottom": 6
|
||||
},
|
||||
"indent_width": 20,
|
||||
"entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#9c9c9c",
|
||||
@@ -946,41 +947,42 @@
|
||||
"family": "Zed Mono",
|
||||
"color": "#636363",
|
||||
"size": 14
|
||||
},
|
||||
"hover": {
|
||||
"background": "#eaeaea",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#2b2b2b",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active": {
|
||||
"background": "#e3e3e3",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#2b2b2b",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active_hover": {
|
||||
"background": "#eaeaea",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#000000",
|
||||
"size": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"hovered_entry": {
|
||||
"height": 24,
|
||||
"background": "#eaeaea",
|
||||
"icon_color": "#9c9c9c",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"filename_editor": {
|
||||
"background": "#0000000f",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#2b2b2b",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"selected_entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#9c9c9c",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#2b2b2b",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"hovered_selected_entry": {
|
||||
"height": 24,
|
||||
"background": "#eaeaea",
|
||||
"icon_color": "#9c9c9c",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#000000",
|
||||
"size": 14
|
||||
},
|
||||
"selection": {
|
||||
"cursor": "#2472f2",
|
||||
"selection": "#2472f23d"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -937,6 +937,7 @@
|
||||
"top": 6,
|
||||
"bottom": 6
|
||||
},
|
||||
"indent_width": 20,
|
||||
"entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#93a1a1",
|
||||
@@ -946,41 +947,42 @@
|
||||
"family": "Zed Mono",
|
||||
"color": "#93a1a1",
|
||||
"size": 14
|
||||
},
|
||||
"hover": {
|
||||
"background": "#586e753d",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#eee8d5",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active": {
|
||||
"background": "#586e755c",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#eee8d5",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active_hover": {
|
||||
"background": "#586e753d",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#fdf6e3",
|
||||
"size": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"hovered_entry": {
|
||||
"height": 24,
|
||||
"background": "#586e753d",
|
||||
"icon_color": "#93a1a1",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"filename_editor": {
|
||||
"background": "#0736425c",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#eee8d5",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"selected_entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#93a1a1",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#eee8d5",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"hovered_selected_entry": {
|
||||
"height": 24,
|
||||
"background": "#586e753d",
|
||||
"icon_color": "#93a1a1",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#fdf6e3",
|
||||
"size": 14
|
||||
},
|
||||
"selection": {
|
||||
"cursor": "#268bd2",
|
||||
"selection": "#268bd23d"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -937,6 +937,7 @@
|
||||
"top": 6,
|
||||
"bottom": 6
|
||||
},
|
||||
"indent_width": 20,
|
||||
"entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#586e75",
|
||||
@@ -946,41 +947,42 @@
|
||||
"family": "Zed Mono",
|
||||
"color": "#586e75",
|
||||
"size": 14
|
||||
},
|
||||
"hover": {
|
||||
"background": "#93a1a11f",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#073642",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active": {
|
||||
"background": "#93a1a12e",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#073642",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active_hover": {
|
||||
"background": "#93a1a11f",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#002b36",
|
||||
"size": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"hovered_entry": {
|
||||
"height": 24,
|
||||
"background": "#93a1a11f",
|
||||
"icon_color": "#586e75",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"filename_editor": {
|
||||
"background": "#eee8d52e",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#073642",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"selected_entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#586e75",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#073642",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"hovered_selected_entry": {
|
||||
"height": 24,
|
||||
"background": "#93a1a11f",
|
||||
"icon_color": "#586e75",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#002b36",
|
||||
"size": 14
|
||||
},
|
||||
"selection": {
|
||||
"cursor": "#268bd2",
|
||||
"selection": "#268bd23d"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -937,6 +937,7 @@
|
||||
"top": 6,
|
||||
"bottom": 6
|
||||
},
|
||||
"indent_width": 20,
|
||||
"entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#979db4",
|
||||
@@ -946,41 +947,42 @@
|
||||
"family": "Zed Mono",
|
||||
"color": "#979db4",
|
||||
"size": 14
|
||||
},
|
||||
"hover": {
|
||||
"background": "#5e66873d",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#dfe2f1",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active": {
|
||||
"background": "#5e66875c",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#dfe2f1",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active_hover": {
|
||||
"background": "#5e66873d",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#f5f7ff",
|
||||
"size": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"hovered_entry": {
|
||||
"height": 24,
|
||||
"background": "#5e66873d",
|
||||
"icon_color": "#979db4",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"filename_editor": {
|
||||
"background": "#2932565c",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#dfe2f1",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"selected_entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#979db4",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#dfe2f1",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"hovered_selected_entry": {
|
||||
"height": 24,
|
||||
"background": "#5e66873d",
|
||||
"icon_color": "#979db4",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#f5f7ff",
|
||||
"size": 14
|
||||
},
|
||||
"selection": {
|
||||
"cursor": "#3d8fd1",
|
||||
"selection": "#3d8fd13d"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -937,6 +937,7 @@
|
||||
"top": 6,
|
||||
"bottom": 6
|
||||
},
|
||||
"indent_width": 20,
|
||||
"entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#5e6687",
|
||||
@@ -946,41 +947,42 @@
|
||||
"family": "Zed Mono",
|
||||
"color": "#5e6687",
|
||||
"size": 14
|
||||
},
|
||||
"hover": {
|
||||
"background": "#979db41f",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#293256",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active": {
|
||||
"background": "#979db42e",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#293256",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"active_hover": {
|
||||
"background": "#979db41f",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#202746",
|
||||
"size": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
"hovered_entry": {
|
||||
"height": 24,
|
||||
"background": "#979db41f",
|
||||
"icon_color": "#5e6687",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"filename_editor": {
|
||||
"background": "#dfe2f12e",
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#293256",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"selected_entry": {
|
||||
"height": 24,
|
||||
"icon_color": "#5e6687",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#293256",
|
||||
"size": 14
|
||||
}
|
||||
},
|
||||
"hovered_selected_entry": {
|
||||
"height": 24,
|
||||
"background": "#979db41f",
|
||||
"icon_color": "#5e6687",
|
||||
"icon_size": 8,
|
||||
"icon_spacing": 8,
|
||||
"text": {
|
||||
"family": "Zed Mono",
|
||||
"color": "#202746",
|
||||
"size": 14
|
||||
},
|
||||
"selection": {
|
||||
"cursor": "#3d8fd1",
|
||||
"selection": "#3d8fd13d"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -270,7 +270,7 @@ impl View for AutoUpdateIndicator {
|
||||
)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(|cx| cx.dispatch_action(DismissErrorMessage))
|
||||
.on_click(|_, cx| cx.dispatch_action(DismissErrorMessage))
|
||||
.boxed()
|
||||
}
|
||||
AutoUpdateStatus::Idle => Empty::new().boxed(),
|
||||
|
||||
@@ -320,7 +320,7 @@ impl ChatPanel {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(move |cx| {
|
||||
.on_click(move |_, cx| {
|
||||
let rpc = rpc.clone();
|
||||
let this = this.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
|
||||
+231
-14
@@ -126,6 +126,9 @@ impl Server {
|
||||
.add_request_handler(Server::forward_project_request::<proto::PerformRename>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::ReloadBuffers>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::FormatBuffers>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::CreateProjectEntry>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::RenameProjectEntry>)
|
||||
.add_request_handler(Server::forward_project_request::<proto::DeleteProjectEntry>)
|
||||
.add_request_handler(Server::update_buffer)
|
||||
.add_message_handler(Server::update_buffer_file)
|
||||
.add_message_handler(Server::buffer_reloaded)
|
||||
@@ -157,9 +160,7 @@ impl Server {
|
||||
let span = info_span!(
|
||||
"handle message",
|
||||
payload_type = envelope.payload_type_name(),
|
||||
payload = serde_json::to_string_pretty(&envelope.payload)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
payload = format!("{:?}", envelope.payload).as_str(),
|
||||
);
|
||||
let future = (handler)(server, *envelope);
|
||||
async move {
|
||||
@@ -447,6 +448,7 @@ impl Server {
|
||||
.cloned()
|
||||
.collect(),
|
||||
visible: worktree.visible,
|
||||
scan_id: shared_worktree.scan_id,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
@@ -577,6 +579,7 @@ impl Server {
|
||||
request.payload.worktree_id,
|
||||
&request.payload.removed_entries,
|
||||
&request.payload.updated_entries,
|
||||
request.payload.scan_id,
|
||||
)?;
|
||||
|
||||
broadcast(request.sender_id, connection_ids, |connection_id| {
|
||||
@@ -1808,6 +1811,176 @@ mod tests {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_fs_operations(
|
||||
executor: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
executor.forbid_parking();
|
||||
let fs = FakeFs::new(cx_a.background());
|
||||
|
||||
// Connect to a server as 2 clients.
|
||||
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
|
||||
let mut client_a = server.create_client(cx_a, "user_a").await;
|
||||
let mut client_b = server.create_client(cx_b, "user_b").await;
|
||||
|
||||
// Share a project as client A
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
".zed.toml": r#"collaborators = ["user_b"]"#,
|
||||
"a.txt": "a-contents",
|
||||
"b.txt": "b-contents",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (project_a, worktree_id) = client_a.build_local_project(fs, "/dir", cx_a).await;
|
||||
let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
|
||||
project_a
|
||||
.update(cx_a, |project, cx| project.share(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
let worktree_a =
|
||||
project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
let worktree_b =
|
||||
project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
|
||||
let entry = project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project
|
||||
.create_entry((worktree_id, "c.txt"), false, cx)
|
||||
.unwrap()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "a.txt", "b.txt", "c.txt"]
|
||||
);
|
||||
});
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "a.txt", "b.txt", "c.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.rename_entry(entry.id, Path::new("d.txt"), cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
let dir_entry = project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project
|
||||
.create_entry((worktree_id, "DIR"), true, cx)
|
||||
.unwrap()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "DIR", "a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "DIR", "a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.delete_entry(dir_entry.id, cx).unwrap()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "a.txt", "b.txt", "d.txt"]
|
||||
);
|
||||
});
|
||||
|
||||
project_b
|
||||
.update(cx_b, |project, cx| {
|
||||
project.delete_entry(entry.id, cx).unwrap()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a.read_with(cx_a, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "a.txt", "b.txt"]
|
||||
);
|
||||
});
|
||||
worktree_b.read_with(cx_b, |worktree, _| {
|
||||
assert_eq!(
|
||||
worktree
|
||||
.paths()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.collect::<Vec<_>>(),
|
||||
[".zed.toml", "a.txt", "b.txt"]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
cx_a.foreground().forbid_parking();
|
||||
@@ -3725,7 +3898,7 @@ mod tests {
|
||||
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(¶ms, cx));
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "main.rs"), cx)
|
||||
workspace.open_path((worktree_id, "main.rs"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -3973,7 +4146,7 @@ mod tests {
|
||||
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(¶ms, cx));
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "one.rs"), cx)
|
||||
workspace.open_path((worktree_id, "one.rs"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -4725,7 +4898,7 @@ mod tests {
|
||||
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
|
||||
let editor_a1 = workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), cx)
|
||||
workspace.open_path((worktree_id, "1.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -4733,7 +4906,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let editor_a2 = workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "2.txt"), cx)
|
||||
workspace.open_path((worktree_id, "2.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -4744,7 +4917,7 @@ mod tests {
|
||||
let workspace_b = client_b.build_workspace(&project_b, cx_b);
|
||||
let editor_b1 = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), cx)
|
||||
workspace.open_path((worktree_id, "1.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -4937,7 +5110,7 @@ mod tests {
|
||||
let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
|
||||
let _editor_a1 = workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), cx)
|
||||
workspace.open_path((worktree_id, "1.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -4949,7 +5122,7 @@ mod tests {
|
||||
let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
|
||||
let _editor_b1 = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "2.txt"), cx)
|
||||
workspace.open_path((worktree_id, "2.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -4984,7 +5157,7 @@ mod tests {
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.activate_next_pane(cx);
|
||||
assert_eq!(*workspace.active_pane(), pane_a1);
|
||||
workspace.open_path((worktree_id, "3.txt"), cx)
|
||||
workspace.open_path((worktree_id, "3.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -4992,7 +5165,7 @@ mod tests {
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.activate_next_pane(cx);
|
||||
assert_eq!(*workspace.active_pane(), pane_b1);
|
||||
workspace.open_path((worktree_id, "4.txt"), cx)
|
||||
workspace.open_path((worktree_id, "4.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -5081,7 +5254,7 @@ mod tests {
|
||||
let workspace_a = client_a.build_workspace(&project_a, cx_a);
|
||||
let _editor_a1 = workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "1.txt"), cx)
|
||||
workspace.open_path((worktree_id, "1.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -5194,7 +5367,7 @@ mod tests {
|
||||
// When client B activates a different item in the original pane, it automatically stops following client A.
|
||||
workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id, "2.txt"), cx)
|
||||
workspace.open_path((worktree_id, "2.txt"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -5633,6 +5806,7 @@ mod tests {
|
||||
guest_client.username,
|
||||
id
|
||||
);
|
||||
assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
|
||||
}
|
||||
|
||||
guest_client
|
||||
@@ -6354,6 +6528,49 @@ mod tests {
|
||||
client.buffers.extend(search.await?.into_keys());
|
||||
}
|
||||
}
|
||||
60..=69 => {
|
||||
let worktree = project
|
||||
.read_with(cx, |project, cx| {
|
||||
project
|
||||
.worktrees(&cx)
|
||||
.filter(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
worktree.is_visible()
|
||||
&& worktree.entries(false).any(|e| e.is_file())
|
||||
&& worktree
|
||||
.root_entry()
|
||||
.map_or(false, |e| e.is_dir())
|
||||
})
|
||||
.choose(&mut *rng.lock())
|
||||
})
|
||||
.unwrap();
|
||||
let (worktree_id, worktree_root_name) = worktree
|
||||
.read_with(cx, |worktree, _| {
|
||||
(worktree.id(), worktree.root_name().to_string())
|
||||
});
|
||||
|
||||
let mut new_name = String::new();
|
||||
for _ in 0..10 {
|
||||
let letter = rng.lock().gen_range('a'..='z');
|
||||
new_name.push(letter);
|
||||
}
|
||||
let mut new_path = PathBuf::new();
|
||||
new_path.push(new_name);
|
||||
new_path.set_extension("rs");
|
||||
log::info!(
|
||||
"{}: creating {:?} in worktree {} ({})",
|
||||
guest_username,
|
||||
new_path,
|
||||
worktree_id,
|
||||
worktree_root_name,
|
||||
);
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_entry((worktree_id, new_path), false, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await?;
|
||||
}
|
||||
_ => {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
log::info!(
|
||||
|
||||
@@ -46,6 +46,7 @@ pub struct ProjectShare {
|
||||
pub struct WorktreeShare {
|
||||
pub entries: HashMap<u64, proto::Entry>,
|
||||
pub diagnostic_summaries: BTreeMap<PathBuf, proto::DiagnosticSummary>,
|
||||
pub scan_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -561,6 +562,7 @@ impl Store {
|
||||
worktree_id: u64,
|
||||
removed_entries: &[u64],
|
||||
updated_entries: &[proto::Entry],
|
||||
scan_id: u64,
|
||||
) -> Result<Vec<ConnectionId>> {
|
||||
let project = self.write_project(project_id, connection_id)?;
|
||||
let worktree = project
|
||||
@@ -574,6 +576,7 @@ impl Store {
|
||||
for entry in updated_entries {
|
||||
worktree.entries.insert(entry.id, entry.clone());
|
||||
}
|
||||
worktree.scan_id = scan_id;
|
||||
let connection_ids = project.connection_ids();
|
||||
Ok(connection_ids)
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ impl ContactsPanel {
|
||||
} else {
|
||||
CursorStyle::Arrow
|
||||
})
|
||||
.on_click(move |cx| {
|
||||
.on_click(move |_, cx| {
|
||||
if !is_host && !is_guest {
|
||||
cx.dispatch_global_action(JoinProject {
|
||||
project_id,
|
||||
|
||||
@@ -161,7 +161,7 @@ impl View for DiagnosticIndicator {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(|cx| cx.dispatch_action(crate::Deploy))
|
||||
.on_click(|_, cx| cx.dispatch_action(crate::Deploy))
|
||||
.aligned()
|
||||
.boxed(),
|
||||
);
|
||||
@@ -194,7 +194,7 @@ impl View for DiagnosticIndicator {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(|cx| cx.dispatch_action(GoToNextDiagnostic))
|
||||
.on_click(|_, cx| cx.dispatch_action(GoToNextDiagnostic))
|
||||
.boxed(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9354,19 +9354,10 @@ mod tests {
|
||||
let fs = FakeFs::new(cx.background().clone());
|
||||
fs.insert_file("/file.rs", Default::default()).await;
|
||||
|
||||
let project = Project::test(fs, cx);
|
||||
let project = Project::test(fs, ["/file.rs"], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
|
||||
let worktree_id = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_local_worktree("/file.rs", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
.read_with(cx, |tree, _| tree.id());
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
|
||||
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -9485,19 +9476,10 @@ mod tests {
|
||||
let fs = FakeFs::new(cx.background().clone());
|
||||
fs.insert_file("/file.rs", text).await;
|
||||
|
||||
let project = Project::test(fs, cx);
|
||||
let project = Project::test(fs, ["/file.rs"], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
|
||||
let worktree_id = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_local_worktree("/file.rs", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
.read_with(cx, |tree, _| tree.id());
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
|
||||
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut fake_server = fake_servers.next().await.unwrap();
|
||||
|
||||
@@ -875,6 +875,12 @@ impl Element for EditorElement {
|
||||
.max(constraint.min_along(Axis::Vertical))
|
||||
.min(line_height * max_lines as f32),
|
||||
)
|
||||
} else if let EditorMode::SingleLine = snapshot.mode {
|
||||
size.set_y(
|
||||
line_height
|
||||
.min(constraint.max_along(Axis::Vertical))
|
||||
.max(constraint.min_along(Axis::Vertical)),
|
||||
)
|
||||
} else if size.y().is_infinite() {
|
||||
size.set_y(scroll_height);
|
||||
}
|
||||
@@ -1183,7 +1189,7 @@ impl Element for EditorElement {
|
||||
click_count,
|
||||
..
|
||||
} => self.mouse_down(*position, *alt, *shift, *click_count, layout, paint, cx),
|
||||
Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
|
||||
Event::LeftMouseUp { position, .. } => self.mouse_up(*position, cx),
|
||||
Event::LeftMouseDragged { position } => {
|
||||
self.mouse_dragged(*position, layout, paint, cx)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ impl FileFinder {
|
||||
match event {
|
||||
Event::Selected(project_path) => {
|
||||
workspace
|
||||
.open_path(project_path.clone(), cx)
|
||||
.open_path(project_path.clone(), true, cx)
|
||||
.detach_and_log_err(cx);
|
||||
workspace.dismiss_modal(cx);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct MouseEventHandler {
|
||||
child: ElementBox,
|
||||
cursor_style: Option<CursorStyle>,
|
||||
mouse_down_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
|
||||
click_handler: Option<Box<dyn FnMut(&mut EventContext)>>,
|
||||
click_handler: Option<Box<dyn FnMut(usize, &mut EventContext)>>,
|
||||
drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>,
|
||||
padding: Padding,
|
||||
}
|
||||
@@ -57,7 +57,7 @@ impl MouseEventHandler {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(mut self, handler: impl FnMut(&mut EventContext) + 'static) -> Self {
|
||||
pub fn on_click(mut self, handler: impl FnMut(usize, &mut EventContext) + 'static) -> Self {
|
||||
self.click_handler = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
@@ -151,14 +151,18 @@ impl Element for MouseEventHandler {
|
||||
handled_in_child
|
||||
}
|
||||
}
|
||||
Event::LeftMouseUp { position, .. } => {
|
||||
Event::LeftMouseUp {
|
||||
position,
|
||||
click_count,
|
||||
..
|
||||
} => {
|
||||
state.prev_drag_position = None;
|
||||
if !handled_in_child && state.clicked {
|
||||
state.clicked = false;
|
||||
cx.notify();
|
||||
if let Some(handler) = click_handler {
|
||||
if hit_bounds.contains_point(*position) {
|
||||
handler(cx);
|
||||
handler(*click_count, cx);
|
||||
}
|
||||
}
|
||||
true
|
||||
|
||||
@@ -360,6 +360,14 @@ impl Deterministic {
|
||||
|
||||
self.state.lock().now = new_now;
|
||||
}
|
||||
|
||||
pub fn forbid_parking(&self) {
|
||||
use rand::prelude::*;
|
||||
|
||||
let mut state = self.state.lock();
|
||||
state.forbid_parking = true;
|
||||
state.rng = StdRng::seed_from_u64(state.seed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Timer {
|
||||
@@ -507,14 +515,8 @@ impl Foreground {
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn forbid_parking(&self) {
|
||||
use rand::prelude::*;
|
||||
|
||||
match self {
|
||||
Self::Deterministic { executor, .. } => {
|
||||
let mut state = executor.state.lock();
|
||||
state.forbid_parking = true;
|
||||
state.rng = StdRng::seed_from_u64(state.seed);
|
||||
}
|
||||
Self::Deterministic { executor, .. } => executor.forbid_parking(),
|
||||
_ => panic!("this method can only be called on a deterministic executor"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum Event {
|
||||
},
|
||||
LeftMouseUp {
|
||||
position: Vector2F,
|
||||
click_count: usize,
|
||||
},
|
||||
LeftMouseDragged {
|
||||
position: Vector2F,
|
||||
@@ -68,7 +69,7 @@ impl Event {
|
||||
Event::KeyDown { .. } => None,
|
||||
Event::ScrollWheel { position, .. }
|
||||
| Event::LeftMouseDown { position, .. }
|
||||
| Event::LeftMouseUp { position }
|
||||
| Event::LeftMouseUp { position, .. }
|
||||
| Event::LeftMouseDragged { position }
|
||||
| Event::RightMouseDown { position, .. }
|
||||
| Event::RightMouseUp { position }
|
||||
|
||||
@@ -129,6 +129,7 @@ impl Event {
|
||||
native_event.locationInWindow().x as f32,
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
),
|
||||
click_count: native_event.clickCount() as usize,
|
||||
}),
|
||||
NSEventType::NSRightMouseDown => {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
|
||||
@@ -119,7 +119,7 @@ impl View for Select {
|
||||
.with_style(style.header)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(move |cx| cx.dispatch_action(ToggleSelect))
|
||||
.on_click(move |_, cx| cx.dispatch_action(ToggleSelect))
|
||||
.boxed(),
|
||||
);
|
||||
if self.is_open {
|
||||
@@ -153,7 +153,7 @@ impl View for Select {
|
||||
)
|
||||
},
|
||||
)
|
||||
.on_click(move |cx| cx.dispatch_action(SelectItem(ix)))
|
||||
.on_click(move |_, cx| cx.dispatch_action(SelectItem(ix)))
|
||||
.boxed()
|
||||
}))
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ settings = { path = "../settings" }
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
util = { path = "../util" }
|
||||
aho-corasick = "0.7"
|
||||
anyhow = "1.0.38"
|
||||
anyhow = "1.0.57"
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
ignore = "0.4"
|
||||
|
||||
+21
-12
@@ -379,7 +379,7 @@ impl FakeFs {
|
||||
async fn simulate_random_delay(&self) {
|
||||
self.executor
|
||||
.upgrade()
|
||||
.expect("excecutor has been dropped")
|
||||
.expect("executor has been dropped")
|
||||
.simulate_random_delay()
|
||||
.await;
|
||||
}
|
||||
@@ -493,7 +493,7 @@ impl Fs for FakeFs {
|
||||
});
|
||||
|
||||
for (relative_path, entry) in removed {
|
||||
let new_path = target.join(relative_path);
|
||||
let new_path = normalize_path(&target.join(relative_path));
|
||||
state.entries.insert(new_path, entry);
|
||||
}
|
||||
|
||||
@@ -501,13 +501,15 @@ impl Fs for FakeFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_dir(&self, path: &Path, options: RemoveOptions) -> Result<()> {
|
||||
let path = normalize_path(path);
|
||||
async fn remove_dir(&self, dir_path: &Path, options: RemoveOptions) -> Result<()> {
|
||||
let dir_path = normalize_path(dir_path);
|
||||
let mut state = self.state.lock().await;
|
||||
state.validate_path(&path)?;
|
||||
if let Some(entry) = state.entries.get(&path) {
|
||||
state.validate_path(&dir_path)?;
|
||||
if let Some(entry) = state.entries.get(&dir_path) {
|
||||
if !entry.metadata.is_dir {
|
||||
return Err(anyhow!("cannot remove {path:?} because it is not a dir"));
|
||||
return Err(anyhow!(
|
||||
"cannot remove {dir_path:?} because it is not a dir"
|
||||
));
|
||||
}
|
||||
|
||||
if !options.recursive {
|
||||
@@ -517,14 +519,14 @@ impl Fs for FakeFs {
|
||||
.filter(|path| path.starts_with(path))
|
||||
.count();
|
||||
if descendants > 1 {
|
||||
return Err(anyhow!("{path:?} is not empty"));
|
||||
return Err(anyhow!("{dir_path:?} is not empty"));
|
||||
}
|
||||
}
|
||||
|
||||
state.entries.retain(|path, _| !path.starts_with(path));
|
||||
state.emit_event(&[path]).await;
|
||||
state.entries.retain(|path, _| !path.starts_with(&dir_path));
|
||||
state.emit_event(&[dir_path]).await;
|
||||
} else if !options.ignore_if_not_exists {
|
||||
return Err(anyhow!("{path:?} does not exist"));
|
||||
return Err(anyhow!("{dir_path:?} does not exist"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -647,9 +649,16 @@ impl Fs for FakeFs {
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
state.event_txs.push(tx);
|
||||
let path = path.to_path_buf();
|
||||
let executor = self.executor.clone();
|
||||
Box::pin(futures::StreamExt::filter(rx, move |events| {
|
||||
let result = events.iter().any(|event| event.path.starts_with(&path));
|
||||
async move { result }
|
||||
let executor = executor.clone();
|
||||
async move {
|
||||
if let Some(executor) = executor.clone().upgrade() {
|
||||
executor.simulate_random_delay().await;
|
||||
}
|
||||
result
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
+352
-362
File diff suppressed because it is too large
Load Diff
+419
-154
@@ -1,4 +1,4 @@
|
||||
use crate::ProjectEntryId;
|
||||
use crate::{ProjectEntryId, RemoveOptions};
|
||||
|
||||
use super::{
|
||||
fs::{self, Fs},
|
||||
@@ -42,6 +42,7 @@ use std::{
|
||||
fmt,
|
||||
future::Future,
|
||||
ops::{Deref, DerefMut},
|
||||
os::unix::prelude::{OsStrExt, OsStringExt},
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::{Duration, SystemTime},
|
||||
@@ -78,11 +79,12 @@ pub struct LocalWorktree {
|
||||
}
|
||||
|
||||
pub struct RemoteWorktree {
|
||||
pub(crate) snapshot: Snapshot,
|
||||
pub snapshot: Snapshot,
|
||||
pub(crate) background_snapshot: Arc<Mutex<Snapshot>>,
|
||||
project_id: u64,
|
||||
snapshot_rx: watch::Receiver<Snapshot>,
|
||||
client: Arc<Client>,
|
||||
updates_tx: UnboundedSender<proto::UpdateWorktree>,
|
||||
last_scan_id_rx: watch::Receiver<usize>,
|
||||
replica_id: ReplicaId,
|
||||
diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
|
||||
visible: bool,
|
||||
@@ -95,12 +97,12 @@ pub struct Snapshot {
|
||||
root_char_bag: CharBag,
|
||||
entries_by_path: SumTree<Entry>,
|
||||
entries_by_id: SumTree<PathEntry>,
|
||||
scan_id: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocalSnapshot {
|
||||
abs_path: Arc<Path>,
|
||||
scan_id: usize,
|
||||
ignores: HashMap<Arc<Path>, (Arc<Gitignore>, usize)>,
|
||||
removed_entry_ids: HashMap<u64, ProjectEntryId>,
|
||||
next_entry_id: Arc<AtomicUsize>,
|
||||
@@ -214,17 +216,21 @@ impl Worktree {
|
||||
root_char_bag,
|
||||
entries_by_path: Default::default(),
|
||||
entries_by_id: Default::default(),
|
||||
scan_id: worktree.scan_id as usize,
|
||||
};
|
||||
|
||||
let (updates_tx, mut updates_rx) = mpsc::unbounded();
|
||||
let (mut snapshot_tx, snapshot_rx) = watch::channel_with(snapshot.clone());
|
||||
let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
|
||||
let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel();
|
||||
let (mut last_scan_id_tx, last_scan_id_rx) = watch::channel_with(worktree.scan_id as usize);
|
||||
let worktree_handle = cx.add_model(|_: &mut ModelContext<Worktree>| {
|
||||
Worktree::Remote(RemoteWorktree {
|
||||
project_id: project_remote_id,
|
||||
replica_id,
|
||||
snapshot: snapshot.clone(),
|
||||
snapshot_rx: snapshot_rx.clone(),
|
||||
background_snapshot: background_snapshot.clone(),
|
||||
updates_tx,
|
||||
last_scan_id_rx,
|
||||
client: client.clone(),
|
||||
diagnostic_summaries: TreeMap::from_ordered_entries(
|
||||
worktree.diagnostic_summaries.into_iter().map(|summary| {
|
||||
@@ -274,37 +280,42 @@ impl Worktree {
|
||||
.await;
|
||||
|
||||
{
|
||||
let mut snapshot = snapshot_tx.borrow_mut();
|
||||
let mut snapshot = background_snapshot.lock();
|
||||
snapshot.entries_by_path = entries_by_path;
|
||||
snapshot.entries_by_id = entries_by_id;
|
||||
snapshot_updated_tx.send(()).await.ok();
|
||||
}
|
||||
|
||||
cx.background()
|
||||
.spawn(async move {
|
||||
while let Some(update) = updates_rx.next().await {
|
||||
let mut snapshot = snapshot_tx.borrow().clone();
|
||||
if let Err(error) = snapshot.apply_remote_update(update) {
|
||||
if let Err(error) =
|
||||
background_snapshot.lock().apply_remote_update(update)
|
||||
{
|
||||
log::error!("error applying worktree update: {}", error);
|
||||
}
|
||||
*snapshot_tx.borrow_mut() = snapshot;
|
||||
snapshot_updated_tx.send(()).await.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
{
|
||||
let mut snapshot_rx = snapshot_rx.clone();
|
||||
cx.spawn(|mut cx| {
|
||||
let this = worktree_handle.downgrade();
|
||||
cx.spawn(|mut cx| async move {
|
||||
while let Some(_) = snapshot_rx.recv().await {
|
||||
async move {
|
||||
while let Some(_) = snapshot_updated_rx.recv().await {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| this.poll_snapshot(cx));
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.poll_snapshot(cx);
|
||||
let this = this.as_remote_mut().unwrap();
|
||||
*last_scan_id_tx.borrow_mut() = this.snapshot.scan_id;
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
(worktree_handle, deserialize_task)
|
||||
@@ -357,6 +368,13 @@ impl Worktree {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scan_id(&self) -> usize {
|
||||
match self {
|
||||
Worktree::Local(worktree) => worktree.snapshot.scan_id,
|
||||
Worktree::Remote(worktree) => worktree.snapshot.scan_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_visible(&self) -> bool {
|
||||
match self {
|
||||
Worktree::Local(worktree) => worktree.visible,
|
||||
@@ -410,7 +428,7 @@ impl Worktree {
|
||||
}
|
||||
}
|
||||
Self::Remote(worktree) => {
|
||||
worktree.snapshot = worktree.snapshot_rx.borrow().clone();
|
||||
worktree.snapshot = worktree.background_snapshot.lock().clone();
|
||||
cx.emit(Event::UpdatedEntries);
|
||||
}
|
||||
};
|
||||
@@ -454,7 +472,6 @@ impl LocalWorktree {
|
||||
let tree = cx.add_model(move |cx: &mut ModelContext<Worktree>| {
|
||||
let mut snapshot = LocalSnapshot {
|
||||
abs_path,
|
||||
scan_id: 0,
|
||||
ignores: Default::default(),
|
||||
removed_entry_ids: Default::default(),
|
||||
next_entry_id,
|
||||
@@ -464,6 +481,7 @@ impl LocalWorktree {
|
||||
root_char_bag,
|
||||
entries_by_path: Default::default(),
|
||||
entries_by_id: Default::default(),
|
||||
scan_id: 0,
|
||||
},
|
||||
};
|
||||
if let Some(metadata) = metadata {
|
||||
@@ -494,24 +512,13 @@ impl LocalWorktree {
|
||||
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
while let Some(scan_state) = scan_states_rx.next().await {
|
||||
if let Some(handle) = this.upgrade(&cx) {
|
||||
let to_send = handle.update(&mut cx, |this, cx| {
|
||||
last_scan_state_tx.blocking_send(scan_state).ok();
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
last_scan_state_tx.blocking_send(scan_state).ok();
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.poll_snapshot(cx);
|
||||
let tree = this.as_local_mut().unwrap();
|
||||
if !tree.is_scanning() {
|
||||
if let Some(share) = tree.share.as_ref() {
|
||||
return Some((tree.snapshot(), share.snapshots_tx.clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
if let Some((snapshot, snapshots_to_send_tx)) = to_send {
|
||||
if let Err(err) = snapshots_to_send_tx.send(snapshot).await {
|
||||
log::error!("error submitting snapshot to send {}", err);
|
||||
}
|
||||
}
|
||||
this.as_local().unwrap().broadcast_snapshot()
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -623,12 +630,15 @@ impl LocalWorktree {
|
||||
let handle = cx.handle();
|
||||
let path = Arc::from(path);
|
||||
let abs_path = self.absolutize(&path);
|
||||
let background_snapshot = self.background_snapshot.clone();
|
||||
let fs = self.fs.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let text = fs.load(&abs_path).await?;
|
||||
// Eagerly populate the snapshot with an updated entry for the loaded file
|
||||
let entry = refresh_entry(fs.as_ref(), &background_snapshot, path, &abs_path).await?;
|
||||
let entry = this
|
||||
.update(&mut cx, |this, _| {
|
||||
this.as_local().unwrap().refresh_entry(path, abs_path, None)
|
||||
})
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.poll_snapshot(cx));
|
||||
Ok((
|
||||
File {
|
||||
@@ -652,7 +662,7 @@ impl LocalWorktree {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let text = buffer.as_rope().clone();
|
||||
let version = buffer.version();
|
||||
let save = self.save(path, text, cx);
|
||||
let save = self.write_file(path, text, cx);
|
||||
let handle = cx.handle();
|
||||
cx.as_mut().spawn(|mut cx| async move {
|
||||
let entry = save.await?;
|
||||
@@ -672,28 +682,188 @@ impl LocalWorktree {
|
||||
})
|
||||
}
|
||||
|
||||
fn save(
|
||||
pub fn create_entry(
|
||||
&self,
|
||||
path: impl Into<Arc<Path>>,
|
||||
is_dir: bool,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Entry>> {
|
||||
self.write_entry_internal(
|
||||
path,
|
||||
if is_dir {
|
||||
None
|
||||
} else {
|
||||
Some(Default::default())
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_file(
|
||||
&self,
|
||||
path: impl Into<Arc<Path>>,
|
||||
text: Rope,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Entry>> {
|
||||
self.write_entry_internal(path, Some(text), cx)
|
||||
}
|
||||
|
||||
pub fn delete_entry(
|
||||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let entry = self.entry_for_id(entry_id)?.clone();
|
||||
let abs_path = self.absolutize(&entry.path);
|
||||
let delete = cx.background().spawn({
|
||||
let fs = self.fs.clone();
|
||||
let abs_path = abs_path.clone();
|
||||
async move {
|
||||
if entry.is_file() {
|
||||
fs.remove_file(&abs_path, Default::default()).await
|
||||
} else {
|
||||
fs.remove_dir(
|
||||
&abs_path,
|
||||
RemoveOptions {
|
||||
recursive: true,
|
||||
ignore_if_not_exists: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
delete.await?;
|
||||
this.update(&mut cx, |this, _| {
|
||||
let this = this.as_local_mut().unwrap();
|
||||
let mut snapshot = this.background_snapshot.lock();
|
||||
snapshot.delete_entry(entry_id);
|
||||
});
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.poll_snapshot(cx);
|
||||
this.as_local().unwrap().broadcast_snapshot()
|
||||
})
|
||||
.await;
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn rename_entry(
|
||||
&self,
|
||||
entry_id: ProjectEntryId,
|
||||
new_path: impl Into<Arc<Path>>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Option<Task<Result<Entry>>> {
|
||||
let old_path = self.entry_for_id(entry_id)?.path.clone();
|
||||
let new_path = new_path.into();
|
||||
let abs_old_path = self.absolutize(&old_path);
|
||||
let abs_new_path = self.absolutize(&new_path);
|
||||
let rename = cx.background().spawn({
|
||||
let fs = self.fs.clone();
|
||||
let abs_new_path = abs_new_path.clone();
|
||||
async move {
|
||||
fs.rename(&abs_old_path, &abs_new_path, Default::default())
|
||||
.await
|
||||
}
|
||||
});
|
||||
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
rename.await?;
|
||||
let entry = this
|
||||
.update(&mut cx, |this, _| {
|
||||
this.as_local_mut().unwrap().refresh_entry(
|
||||
new_path.clone(),
|
||||
abs_new_path,
|
||||
Some(old_path),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.poll_snapshot(cx);
|
||||
this.as_local().unwrap().broadcast_snapshot()
|
||||
})
|
||||
.await;
|
||||
Ok(entry)
|
||||
}))
|
||||
}
|
||||
|
||||
fn write_entry_internal(
|
||||
&self,
|
||||
path: impl Into<Arc<Path>>,
|
||||
text_if_file: Option<Rope>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Entry>> {
|
||||
let path = path.into();
|
||||
let abs_path = self.absolutize(&path);
|
||||
let background_snapshot = self.background_snapshot.clone();
|
||||
let fs = self.fs.clone();
|
||||
let save = cx.background().spawn(async move {
|
||||
fs.save(&abs_path, &text).await?;
|
||||
refresh_entry(fs.as_ref(), &background_snapshot, path.clone(), &abs_path).await
|
||||
let write = cx.background().spawn({
|
||||
let fs = self.fs.clone();
|
||||
let abs_path = abs_path.clone();
|
||||
async move {
|
||||
if let Some(text) = text_if_file {
|
||||
fs.save(&abs_path, &text).await
|
||||
} else {
|
||||
fs.create_dir(&abs_path).await
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let entry = save.await?;
|
||||
this.update(&mut cx, |this, cx| this.poll_snapshot(cx));
|
||||
write.await?;
|
||||
let entry = this
|
||||
.update(&mut cx, |this, _| {
|
||||
this.as_local_mut()
|
||||
.unwrap()
|
||||
.refresh_entry(path, abs_path, None)
|
||||
})
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.poll_snapshot(cx);
|
||||
this.as_local().unwrap().broadcast_snapshot()
|
||||
})
|
||||
.await;
|
||||
Ok(entry)
|
||||
})
|
||||
}
|
||||
|
||||
fn refresh_entry(
|
||||
&self,
|
||||
path: Arc<Path>,
|
||||
abs_path: PathBuf,
|
||||
old_path: Option<Arc<Path>>,
|
||||
) -> impl Future<Output = Result<Entry>> {
|
||||
let root_char_bag;
|
||||
let next_entry_id;
|
||||
let fs = self.fs.clone();
|
||||
let shared_snapshots_tx = self.share.as_ref().map(|share| share.snapshots_tx.clone());
|
||||
let snapshot = self.background_snapshot.clone();
|
||||
{
|
||||
let snapshot = snapshot.lock();
|
||||
root_char_bag = snapshot.root_char_bag;
|
||||
next_entry_id = snapshot.next_entry_id.clone();
|
||||
}
|
||||
async move {
|
||||
let entry = Entry::new(
|
||||
path,
|
||||
&fs.metadata(&abs_path)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("could not read saved file metadata"))?,
|
||||
&next_entry_id,
|
||||
root_char_bag,
|
||||
);
|
||||
let mut snapshot = snapshot.lock();
|
||||
if let Some(old_path) = old_path {
|
||||
snapshot.remove_path(&old_path);
|
||||
}
|
||||
let entry = snapshot.insert_entry(entry, fs.as_ref());
|
||||
if let Some(tx) = shared_snapshots_tx {
|
||||
tx.send(snapshot.clone()).await.ok();
|
||||
}
|
||||
Ok(entry)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&mut self,
|
||||
project_id: u64,
|
||||
@@ -761,6 +931,7 @@ impl LocalWorktree {
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
removed_entries: Default::default(),
|
||||
scan_id: snapshot.scan_id as u64,
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -829,6 +1000,23 @@ impl LocalWorktree {
|
||||
pub fn is_shared(&self) -> bool {
|
||||
self.share.is_some()
|
||||
}
|
||||
|
||||
fn broadcast_snapshot(&self) -> impl Future<Output = ()> {
|
||||
let mut to_send = None;
|
||||
if !self.is_scanning() {
|
||||
if let Some(share) = self.share.as_ref() {
|
||||
to_send = Some((self.snapshot(), share.snapshots_tx.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
async move {
|
||||
if let Some((snapshot, snapshots_to_send_tx)) = to_send {
|
||||
if let Err(err) = snapshots_to_send_tx.send(snapshot).await {
|
||||
log::error!("error submitting snapshot to send {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteWorktree {
|
||||
@@ -843,10 +1031,20 @@ impl RemoteWorktree {
|
||||
self.updates_tx
|
||||
.unbounded_send(envelope.payload)
|
||||
.expect("consumer runs to completion");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_for_snapshot(&self, scan_id: usize) -> impl Future<Output = ()> {
|
||||
let mut rx = self.last_scan_id_rx.clone();
|
||||
async move {
|
||||
while let Some(applied_scan_id) = rx.next().await {
|
||||
if applied_scan_id >= scan_id {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_diagnostic_summary(
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
@@ -863,6 +1061,44 @@ impl RemoteWorktree {
|
||||
.insert(PathKey(path.clone()), summary);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_entry(
|
||||
&self,
|
||||
entry: proto::Entry,
|
||||
scan_id: usize,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Entry>> {
|
||||
let wait_for_snapshot = self.wait_for_snapshot(scan_id);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
wait_for_snapshot.await;
|
||||
this.update(&mut cx, |worktree, _| {
|
||||
let worktree = worktree.as_remote_mut().unwrap();
|
||||
let mut snapshot = worktree.background_snapshot.lock();
|
||||
let entry = snapshot.insert_entry(entry);
|
||||
worktree.snapshot = snapshot.clone();
|
||||
entry
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn delete_entry(
|
||||
&self,
|
||||
id: ProjectEntryId,
|
||||
scan_id: usize,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<()>> {
|
||||
let wait_for_snapshot = self.wait_for_snapshot(scan_id);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
wait_for_snapshot.await;
|
||||
this.update(&mut cx, |worktree, _| {
|
||||
let worktree = worktree.as_remote_mut().unwrap();
|
||||
let mut snapshot = worktree.background_snapshot.lock();
|
||||
snapshot.delete_entry(id);
|
||||
worktree.snapshot = snapshot.clone();
|
||||
});
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Snapshot {
|
||||
@@ -874,6 +1110,33 @@ impl Snapshot {
|
||||
self.entries_by_id.get(&entry_id, &()).is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn insert_entry(&mut self, entry: proto::Entry) -> Result<Entry> {
|
||||
let entry = Entry::try_from((&self.root_char_bag, entry))?;
|
||||
let old_entry = self.entries_by_id.insert_or_replace(
|
||||
PathEntry {
|
||||
id: entry.id,
|
||||
path: entry.path.clone(),
|
||||
is_ignored: entry.is_ignored,
|
||||
scan_id: 0,
|
||||
},
|
||||
&(),
|
||||
);
|
||||
if let Some(old_entry) = old_entry {
|
||||
self.entries_by_path.remove(&PathKey(old_entry.path), &());
|
||||
}
|
||||
self.entries_by_path.insert_or_replace(entry.clone(), &());
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
fn delete_entry(&mut self, entry_id: ProjectEntryId) -> bool {
|
||||
if let Some(entry) = self.entries_by_id.remove(&entry_id, &()) {
|
||||
self.entries_by_path.remove(&PathKey(entry.path), &());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_remote_update(&mut self, update: proto::UpdateWorktree) -> Result<()> {
|
||||
let mut entries_by_path_edits = Vec::new();
|
||||
let mut entries_by_id_edits = Vec::new();
|
||||
@@ -901,6 +1164,7 @@ impl Snapshot {
|
||||
|
||||
self.entries_by_path.edit(entries_by_path_edits, &());
|
||||
self.entries_by_id.edit(entries_by_id_edits, &());
|
||||
self.scan_id = update.scan_id as usize;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -989,6 +1253,10 @@ impl Snapshot {
|
||||
&self.root_name
|
||||
}
|
||||
|
||||
pub fn scan_id(&self) -> usize {
|
||||
self.scan_id
|
||||
}
|
||||
|
||||
pub fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
|
||||
let path = path.as_ref();
|
||||
self.traverse_from_path(true, true, path)
|
||||
@@ -1038,6 +1306,7 @@ impl LocalSnapshot {
|
||||
.map(|(path, summary)| summary.to_proto(&path.0))
|
||||
.collect(),
|
||||
visible,
|
||||
scan_id: self.scan_id as u64,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1103,6 +1372,7 @@ impl LocalSnapshot {
|
||||
root_name: self.root_name().to_string(),
|
||||
updated_entries,
|
||||
removed_entries,
|
||||
scan_id: self.scan_id as u64,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1146,11 +1416,18 @@ impl LocalSnapshot {
|
||||
entries: impl IntoIterator<Item = Entry>,
|
||||
ignore: Option<Arc<Gitignore>>,
|
||||
) {
|
||||
let mut parent_entry = self
|
||||
.entries_by_path
|
||||
.get(&PathKey(parent_path.clone()), &())
|
||||
.unwrap()
|
||||
.clone();
|
||||
let mut parent_entry = if let Some(parent_entry) =
|
||||
self.entries_by_path.get(&PathKey(parent_path.clone()), &())
|
||||
{
|
||||
parent_entry.clone()
|
||||
} else {
|
||||
log::warn!(
|
||||
"populating a directory {:?} that has been removed",
|
||||
parent_path
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(ignore) = ignore {
|
||||
self.ignores.insert(parent_path, (ignore, self.scan_id));
|
||||
}
|
||||
@@ -1210,7 +1487,7 @@ impl LocalSnapshot {
|
||||
|
||||
if path.file_name() == Some(&GITIGNORE) {
|
||||
if let Some((_, scan_id)) = self.ignores.get_mut(path.parent().unwrap()) {
|
||||
*scan_id = self.scan_id;
|
||||
*scan_id = self.snapshot.scan_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1397,7 +1674,7 @@ impl language::File for File {
|
||||
Worktree::Local(worktree) => {
|
||||
let rpc = worktree.client.clone();
|
||||
let project_id = worktree.share.as_ref().map(|share| share.project_id);
|
||||
let save = worktree.save(self.path.clone(), text, cx);
|
||||
let save = worktree.write_file(self.path.clone(), text, cx);
|
||||
cx.background().spawn(async move {
|
||||
let entry = save.await?;
|
||||
if let Some(project_id) = project_id {
|
||||
@@ -1536,7 +1813,7 @@ pub struct Entry {
|
||||
pub is_ignored: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum EntryKind {
|
||||
PendingDir,
|
||||
Dir,
|
||||
@@ -1777,14 +2054,14 @@ impl BackgroundScanner {
|
||||
let path: Arc<Path> = Arc::from(Path::new(""));
|
||||
let abs_path = self.abs_path();
|
||||
let (tx, rx) = channel::unbounded();
|
||||
tx.send(ScanJob {
|
||||
abs_path: abs_path.to_path_buf(),
|
||||
path,
|
||||
ignore_stack: IgnoreStack::none(),
|
||||
scan_queue: tx.clone(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
self.executor
|
||||
.block(tx.send(ScanJob {
|
||||
abs_path: abs_path.to_path_buf(),
|
||||
path,
|
||||
ignore_stack: IgnoreStack::none(),
|
||||
scan_queue: tx.clone(),
|
||||
}))
|
||||
.unwrap();
|
||||
drop(tx);
|
||||
|
||||
self.executor
|
||||
@@ -1907,83 +2184,91 @@ impl BackgroundScanner {
|
||||
}
|
||||
|
||||
async fn process_events(&mut self, mut events: Vec<fsevent::Event>) -> bool {
|
||||
let mut snapshot = self.snapshot();
|
||||
snapshot.scan_id += 1;
|
||||
events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
|
||||
events.dedup_by(|a, b| a.path.starts_with(&b.path));
|
||||
|
||||
let root_abs_path = if let Ok(abs_path) = self.fs.canonicalize(&snapshot.abs_path).await {
|
||||
let root_char_bag;
|
||||
let root_abs_path;
|
||||
let next_entry_id;
|
||||
{
|
||||
let mut snapshot = self.snapshot.lock();
|
||||
snapshot.scan_id += 1;
|
||||
root_char_bag = snapshot.root_char_bag;
|
||||
root_abs_path = snapshot.abs_path.clone();
|
||||
next_entry_id = snapshot.next_entry_id.clone();
|
||||
}
|
||||
|
||||
let root_abs_path = if let Ok(abs_path) = self.fs.canonicalize(&root_abs_path).await {
|
||||
abs_path
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let root_char_bag = snapshot.root_char_bag;
|
||||
let next_entry_id = snapshot.next_entry_id.clone();
|
||||
let metadata = futures::future::join_all(
|
||||
events
|
||||
.iter()
|
||||
.map(|event| self.fs.metadata(&event.path))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await;
|
||||
|
||||
events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
|
||||
events.dedup_by(|a, b| a.path.starts_with(&b.path));
|
||||
|
||||
for event in &events {
|
||||
match event.path.strip_prefix(&root_abs_path) {
|
||||
Ok(path) => snapshot.remove_path(&path),
|
||||
Err(_) => {
|
||||
log::error!(
|
||||
"unexpected event {:?} for root path {:?}",
|
||||
event.path,
|
||||
root_abs_path
|
||||
);
|
||||
continue;
|
||||
// Hold the snapshot lock while clearing and re-inserting the root entries
|
||||
// for each event. This way, the snapshot is not observable to the foreground
|
||||
// thread while this operation is in-progress.
|
||||
let (scan_queue_tx, scan_queue_rx) = channel::unbounded();
|
||||
{
|
||||
let mut snapshot = self.snapshot.lock();
|
||||
for event in &events {
|
||||
if let Ok(path) = event.path.strip_prefix(&root_abs_path) {
|
||||
snapshot.remove_path(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (scan_queue_tx, scan_queue_rx) = channel::unbounded();
|
||||
for event in events {
|
||||
let path: Arc<Path> = match event.path.strip_prefix(&root_abs_path) {
|
||||
Ok(path) => Arc::from(path.to_path_buf()),
|
||||
Err(_) => {
|
||||
log::error!(
|
||||
"unexpected event {:?} for root path {:?}",
|
||||
event.path,
|
||||
root_abs_path
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
for (event, metadata) in events.into_iter().zip(metadata.into_iter()) {
|
||||
let path: Arc<Path> = match event.path.strip_prefix(&root_abs_path) {
|
||||
Ok(path) => Arc::from(path.to_path_buf()),
|
||||
Err(_) => {
|
||||
log::error!(
|
||||
"unexpected event {:?} for root path {:?}",
|
||||
event.path,
|
||||
root_abs_path
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match self.fs.metadata(&event.path).await {
|
||||
Ok(Some(metadata)) => {
|
||||
let ignore_stack = snapshot.ignore_stack_for_path(&path, metadata.is_dir);
|
||||
let mut fs_entry = Entry::new(
|
||||
path.clone(),
|
||||
&metadata,
|
||||
snapshot.next_entry_id.as_ref(),
|
||||
snapshot.root_char_bag,
|
||||
);
|
||||
fs_entry.is_ignored = ignore_stack.is_all();
|
||||
snapshot.insert_entry(fs_entry, self.fs.as_ref());
|
||||
if metadata.is_dir {
|
||||
scan_queue_tx
|
||||
.send(ScanJob {
|
||||
abs_path: event.path,
|
||||
path,
|
||||
ignore_stack,
|
||||
scan_queue: scan_queue_tx.clone(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
match metadata {
|
||||
Ok(Some(metadata)) => {
|
||||
let ignore_stack = snapshot.ignore_stack_for_path(&path, metadata.is_dir);
|
||||
let mut fs_entry = Entry::new(
|
||||
path.clone(),
|
||||
&metadata,
|
||||
snapshot.next_entry_id.as_ref(),
|
||||
snapshot.root_char_bag,
|
||||
);
|
||||
fs_entry.is_ignored = ignore_stack.is_all();
|
||||
snapshot.insert_entry(fs_entry, self.fs.as_ref());
|
||||
if metadata.is_dir {
|
||||
self.executor
|
||||
.block(scan_queue_tx.send(ScanJob {
|
||||
abs_path: event.path,
|
||||
path,
|
||||
ignore_stack,
|
||||
scan_queue: scan_queue_tx.clone(),
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
// TODO - create a special 'error' entry in the entries tree to mark this
|
||||
log::error!("error reading file on event {:?}", err);
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(err) => {
|
||||
// TODO - create a special 'error' entry in the entries tree to mark this
|
||||
log::error!("error reading file on event {:?}", err);
|
||||
}
|
||||
}
|
||||
drop(scan_queue_tx);
|
||||
}
|
||||
|
||||
*self.snapshot.lock() = snapshot;
|
||||
|
||||
// Scan any directories that were created as part of this event batch.
|
||||
drop(scan_queue_tx);
|
||||
self.executor
|
||||
.scoped(|scope| {
|
||||
for _ in 0..self.executor.num_cpus() {
|
||||
@@ -2107,30 +2392,6 @@ impl BackgroundScanner {
|
||||
}
|
||||
}
|
||||
|
||||
async fn refresh_entry(
|
||||
fs: &dyn Fs,
|
||||
snapshot: &Mutex<LocalSnapshot>,
|
||||
path: Arc<Path>,
|
||||
abs_path: &Path,
|
||||
) -> Result<Entry> {
|
||||
let root_char_bag;
|
||||
let next_entry_id;
|
||||
{
|
||||
let snapshot = snapshot.lock();
|
||||
root_char_bag = snapshot.root_char_bag;
|
||||
next_entry_id = snapshot.next_entry_id.clone();
|
||||
}
|
||||
let entry = Entry::new(
|
||||
path,
|
||||
&fs.metadata(abs_path)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("could not read saved file metadata"))?,
|
||||
&next_entry_id,
|
||||
root_char_bag,
|
||||
);
|
||||
Ok(snapshot.lock().insert_entry(entry, fs))
|
||||
}
|
||||
|
||||
fn char_bag_for_path(root_char_bag: CharBag, path: &Path) -> CharBag {
|
||||
let mut result = root_char_bag;
|
||||
result.extend(
|
||||
@@ -2368,7 +2629,7 @@ impl<'a> From<&'a Entry> for proto::Entry {
|
||||
Self {
|
||||
id: entry.id.to_proto(),
|
||||
is_dir: entry.is_dir(),
|
||||
path: entry.path.to_string_lossy().to_string(),
|
||||
path: entry.path.as_os_str().as_bytes().to_vec(),
|
||||
inode: entry.inode,
|
||||
mtime: Some(entry.mtime.into()),
|
||||
is_symlink: entry.is_symlink,
|
||||
@@ -2386,10 +2647,14 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry {
|
||||
EntryKind::Dir
|
||||
} else {
|
||||
let mut char_bag = root_char_bag.clone();
|
||||
char_bag.extend(entry.path.chars().map(|c| c.to_ascii_lowercase()));
|
||||
char_bag.extend(
|
||||
String::from_utf8_lossy(&entry.path)
|
||||
.chars()
|
||||
.map(|c| c.to_ascii_lowercase()),
|
||||
);
|
||||
EntryKind::File(char_bag)
|
||||
};
|
||||
let path: Arc<Path> = Arc::from(Path::new(&entry.path));
|
||||
let path: Arc<Path> = PathBuf::from(OsString::from_vec(entry.path)).into();
|
||||
Ok(Entry {
|
||||
id: ProjectEntryId::from_proto(entry.id),
|
||||
kind,
|
||||
@@ -2541,7 +2806,6 @@ mod tests {
|
||||
let next_entry_id = Arc::new(AtomicUsize::new(0));
|
||||
let mut initial_snapshot = LocalSnapshot {
|
||||
abs_path: root_dir.path().into(),
|
||||
scan_id: 0,
|
||||
removed_entry_ids: Default::default(),
|
||||
ignores: Default::default(),
|
||||
next_entry_id: next_entry_id.clone(),
|
||||
@@ -2551,6 +2815,7 @@ mod tests {
|
||||
entries_by_id: Default::default(),
|
||||
root_name: Default::default(),
|
||||
root_char_bag: Default::default(),
|
||||
scan_id: 0,
|
||||
},
|
||||
};
|
||||
initial_snapshot.insert_entry(
|
||||
|
||||
@@ -8,15 +8,19 @@ path = "src/project_panel.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
editor = { path = "../editor" }
|
||||
gpui = { path = "../gpui" }
|
||||
project = { path = "../project" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
futures = "0.3"
|
||||
unicase = "2.6"
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { path = "../editor", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
workspace = { path = "../workspace", features = ["test-support"] }
|
||||
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -297,23 +297,12 @@ mod tests {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree("/dir", json!({ "test.rs": "" })).await;
|
||||
|
||||
let project = Project::test(fs.clone(), cx);
|
||||
project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::new(language));
|
||||
});
|
||||
|
||||
let worktree_id = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_local_worktree("/dir", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
.read_with(cx, |tree, _| tree.id());
|
||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
|
||||
let _buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "test.rs"), cx)
|
||||
project.open_local_buffer("/dir/test.rs", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
+78
-47
@@ -36,57 +36,63 @@ message Envelope {
|
||||
RegisterWorktree register_worktree = 28;
|
||||
UnregisterWorktree unregister_worktree = 29;
|
||||
UpdateWorktree update_worktree = 31;
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 32;
|
||||
StartLanguageServer start_language_server = 33;
|
||||
UpdateLanguageServer update_language_server = 34;
|
||||
|
||||
OpenBufferById open_buffer_by_id = 35;
|
||||
OpenBufferByPath open_buffer_by_path = 36;
|
||||
OpenBufferResponse open_buffer_response = 37;
|
||||
UpdateBuffer update_buffer = 38;
|
||||
UpdateBufferFile update_buffer_file = 39;
|
||||
SaveBuffer save_buffer = 40;
|
||||
BufferSaved buffer_saved = 41;
|
||||
BufferReloaded buffer_reloaded = 42;
|
||||
ReloadBuffers reload_buffers = 43;
|
||||
ReloadBuffersResponse reload_buffers_response = 44;
|
||||
FormatBuffers format_buffers = 45;
|
||||
FormatBuffersResponse format_buffers_response = 46;
|
||||
GetCompletions get_completions = 47;
|
||||
GetCompletionsResponse get_completions_response = 48;
|
||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 49;
|
||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 50;
|
||||
GetCodeActions get_code_actions = 51;
|
||||
GetCodeActionsResponse get_code_actions_response = 52;
|
||||
ApplyCodeAction apply_code_action = 53;
|
||||
ApplyCodeActionResponse apply_code_action_response = 54;
|
||||
PrepareRename prepare_rename = 55;
|
||||
PrepareRenameResponse prepare_rename_response = 56;
|
||||
PerformRename perform_rename = 57;
|
||||
PerformRenameResponse perform_rename_response = 58;
|
||||
SearchProject search_project = 59;
|
||||
SearchProjectResponse search_project_response = 60;
|
||||
CreateProjectEntry create_project_entry = 32;
|
||||
RenameProjectEntry rename_project_entry = 33;
|
||||
DeleteProjectEntry delete_project_entry = 34;
|
||||
ProjectEntryResponse project_entry_response = 35;
|
||||
|
||||
GetChannels get_channels = 61;
|
||||
GetChannelsResponse get_channels_response = 62;
|
||||
JoinChannel join_channel = 63;
|
||||
JoinChannelResponse join_channel_response = 64;
|
||||
LeaveChannel leave_channel = 65;
|
||||
SendChannelMessage send_channel_message = 66;
|
||||
SendChannelMessageResponse send_channel_message_response = 67;
|
||||
ChannelMessageSent channel_message_sent = 68;
|
||||
GetChannelMessages get_channel_messages = 69;
|
||||
GetChannelMessagesResponse get_channel_messages_response = 70;
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 36;
|
||||
StartLanguageServer start_language_server = 37;
|
||||
UpdateLanguageServer update_language_server = 38;
|
||||
|
||||
UpdateContacts update_contacts = 71;
|
||||
OpenBufferById open_buffer_by_id = 39;
|
||||
OpenBufferByPath open_buffer_by_path = 40;
|
||||
OpenBufferResponse open_buffer_response = 41;
|
||||
UpdateBuffer update_buffer = 42;
|
||||
UpdateBufferFile update_buffer_file = 43;
|
||||
SaveBuffer save_buffer = 44;
|
||||
BufferSaved buffer_saved = 45;
|
||||
BufferReloaded buffer_reloaded = 46;
|
||||
ReloadBuffers reload_buffers = 47;
|
||||
ReloadBuffersResponse reload_buffers_response = 48;
|
||||
FormatBuffers format_buffers = 49;
|
||||
FormatBuffersResponse format_buffers_response = 50;
|
||||
GetCompletions get_completions = 51;
|
||||
GetCompletionsResponse get_completions_response = 52;
|
||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 53;
|
||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 54;
|
||||
GetCodeActions get_code_actions = 55;
|
||||
GetCodeActionsResponse get_code_actions_response = 56;
|
||||
ApplyCodeAction apply_code_action = 57;
|
||||
ApplyCodeActionResponse apply_code_action_response = 58;
|
||||
PrepareRename prepare_rename = 59;
|
||||
PrepareRenameResponse prepare_rename_response = 60;
|
||||
PerformRename perform_rename = 61;
|
||||
PerformRenameResponse perform_rename_response = 62;
|
||||
SearchProject search_project = 63;
|
||||
SearchProjectResponse search_project_response = 64;
|
||||
|
||||
GetUsers get_users = 72;
|
||||
GetUsersResponse get_users_response = 73;
|
||||
GetChannels get_channels = 65;
|
||||
GetChannelsResponse get_channels_response = 66;
|
||||
JoinChannel join_channel = 67;
|
||||
JoinChannelResponse join_channel_response = 68;
|
||||
LeaveChannel leave_channel = 69;
|
||||
SendChannelMessage send_channel_message = 70;
|
||||
SendChannelMessageResponse send_channel_message_response = 71;
|
||||
ChannelMessageSent channel_message_sent = 72;
|
||||
GetChannelMessages get_channel_messages = 73;
|
||||
GetChannelMessagesResponse get_channel_messages_response = 74;
|
||||
|
||||
Follow follow = 74;
|
||||
FollowResponse follow_response = 75;
|
||||
UpdateFollowers update_followers = 76;
|
||||
Unfollow unfollow = 77;
|
||||
UpdateContacts update_contacts = 75;
|
||||
|
||||
GetUsers get_users = 76;
|
||||
GetUsersResponse get_users_response = 77;
|
||||
|
||||
Follow follow = 78;
|
||||
FollowResponse follow_response = 79;
|
||||
UpdateFollowers update_followers = 80;
|
||||
Unfollow unfollow = 81;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +162,30 @@ message UpdateWorktree {
|
||||
string root_name = 3;
|
||||
repeated Entry updated_entries = 4;
|
||||
repeated uint64 removed_entries = 5;
|
||||
uint64 scan_id = 6;
|
||||
}
|
||||
|
||||
message CreateProjectEntry {
|
||||
uint64 project_id = 1;
|
||||
uint64 worktree_id = 2;
|
||||
bytes path = 3;
|
||||
bool is_directory = 4;
|
||||
}
|
||||
|
||||
message RenameProjectEntry {
|
||||
uint64 project_id = 1;
|
||||
uint64 entry_id = 2;
|
||||
bytes new_path = 3;
|
||||
}
|
||||
|
||||
message DeleteProjectEntry {
|
||||
uint64 project_id = 1;
|
||||
uint64 entry_id = 2;
|
||||
}
|
||||
|
||||
message ProjectEntryResponse {
|
||||
Entry entry = 1;
|
||||
uint64 worktree_scan_id = 2;
|
||||
}
|
||||
|
||||
message AddProjectCollaborator {
|
||||
@@ -630,6 +660,7 @@ message Worktree {
|
||||
repeated Entry entries = 3;
|
||||
repeated DiagnosticSummary diagnostic_summaries = 4;
|
||||
bool visible = 5;
|
||||
uint64 scan_id = 6;
|
||||
}
|
||||
|
||||
message File {
|
||||
@@ -642,7 +673,7 @@ message File {
|
||||
message Entry {
|
||||
uint64 id = 1;
|
||||
bool is_dir = 2;
|
||||
string path = 3;
|
||||
bytes path = 3;
|
||||
uint64 inode = 4;
|
||||
Timestamp mtime = 5;
|
||||
bool is_symlink = 6;
|
||||
|
||||
+14
-3
@@ -6,13 +6,14 @@ use prost::Message as _;
|
||||
use serde::Serialize;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
io,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/zed.messages.rs"));
|
||||
|
||||
pub trait EnvelopedMessage: Clone + Serialize + Sized + Send + Sync + 'static {
|
||||
pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 'static {
|
||||
const NAME: &'static str;
|
||||
const PRIORITY: MessagePriority;
|
||||
fn into_envelope(
|
||||
@@ -147,6 +148,8 @@ messages!(
|
||||
(BufferReloaded, Foreground),
|
||||
(BufferSaved, Foreground),
|
||||
(ChannelMessageSent, Foreground),
|
||||
(CreateProjectEntry, Foreground),
|
||||
(DeleteProjectEntry, Foreground),
|
||||
(Error, Foreground),
|
||||
(Follow, Foreground),
|
||||
(FollowResponse, Foreground),
|
||||
@@ -174,8 +177,6 @@ messages!(
|
||||
(JoinChannelResponse, Foreground),
|
||||
(JoinProject, Foreground),
|
||||
(JoinProjectResponse, Foreground),
|
||||
(StartLanguageServer, Foreground),
|
||||
(UpdateLanguageServer, Foreground),
|
||||
(LeaveChannel, Foreground),
|
||||
(LeaveProject, Foreground),
|
||||
(OpenBufferById, Background),
|
||||
@@ -187,6 +188,7 @@ messages!(
|
||||
(PerformRenameResponse, Background),
|
||||
(PrepareRename, Background),
|
||||
(PrepareRenameResponse, Background),
|
||||
(ProjectEntryResponse, Foreground),
|
||||
(RegisterProjectResponse, Foreground),
|
||||
(Ping, Foreground),
|
||||
(RegisterProject, Foreground),
|
||||
@@ -194,12 +196,14 @@ messages!(
|
||||
(ReloadBuffers, Foreground),
|
||||
(ReloadBuffersResponse, Foreground),
|
||||
(RemoveProjectCollaborator, Foreground),
|
||||
(RenameProjectEntry, Foreground),
|
||||
(SaveBuffer, Foreground),
|
||||
(SearchProject, Background),
|
||||
(SearchProjectResponse, Background),
|
||||
(SendChannelMessage, Foreground),
|
||||
(SendChannelMessageResponse, Foreground),
|
||||
(ShareProject, Foreground),
|
||||
(StartLanguageServer, Foreground),
|
||||
(Test, Foreground),
|
||||
(Unfollow, Foreground),
|
||||
(UnregisterProject, Foreground),
|
||||
@@ -210,6 +214,7 @@ messages!(
|
||||
(UpdateContacts, Foreground),
|
||||
(UpdateDiagnosticSummary, Foreground),
|
||||
(UpdateFollowers, Foreground),
|
||||
(UpdateLanguageServer, Foreground),
|
||||
(UpdateWorktree, Foreground),
|
||||
);
|
||||
|
||||
@@ -219,6 +224,8 @@ request_messages!(
|
||||
ApplyCompletionAdditionalEdits,
|
||||
ApplyCompletionAdditionalEditsResponse
|
||||
),
|
||||
(CreateProjectEntry, ProjectEntryResponse),
|
||||
(DeleteProjectEntry, ProjectEntryResponse),
|
||||
(Follow, FollowResponse),
|
||||
(FormatBuffers, FormatBuffersResponse),
|
||||
(GetChannelMessages, GetChannelMessagesResponse),
|
||||
@@ -241,6 +248,7 @@ request_messages!(
|
||||
(RegisterProject, RegisterProjectResponse),
|
||||
(RegisterWorktree, Ack),
|
||||
(ReloadBuffers, ReloadBuffersResponse),
|
||||
(RenameProjectEntry, ProjectEntryResponse),
|
||||
(SaveBuffer, BufferSaved),
|
||||
(SearchProject, SearchProjectResponse),
|
||||
(SendChannelMessage, SendChannelMessageResponse),
|
||||
@@ -257,6 +265,9 @@ entity_messages!(
|
||||
ApplyCompletionAdditionalEdits,
|
||||
BufferReloaded,
|
||||
BufferSaved,
|
||||
CreateProjectEntry,
|
||||
RenameProjectEntry,
|
||||
DeleteProjectEntry,
|
||||
Follow,
|
||||
FormatBuffers,
|
||||
GetCodeActions,
|
||||
|
||||
@@ -5,4 +5,4 @@ pub mod proto;
|
||||
pub use conn::Connection;
|
||||
pub use peer::*;
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 15;
|
||||
pub const PROTOCOL_VERSION: u32 = 16;
|
||||
|
||||
@@ -292,7 +292,7 @@ impl BufferSearchBar {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(move |cx| cx.dispatch_action(ToggleSearchOption(search_option)))
|
||||
.on_click(move |_, cx| cx.dispatch_action(ToggleSearchOption(search_option)))
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.boxed()
|
||||
}
|
||||
@@ -316,7 +316,7 @@ impl BufferSearchBar {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(move |cx| match direction {
|
||||
.on_click(move |_, cx| match direction {
|
||||
Direction::Prev => cx.dispatch_action(SelectPrevMatch),
|
||||
Direction::Next => cx.dispatch_action(SelectNextMatch),
|
||||
})
|
||||
|
||||
@@ -666,7 +666,7 @@ impl ProjectSearchBar {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(move |cx| match direction {
|
||||
.on_click(move |_, cx| match direction {
|
||||
Direction::Prev => cx.dispatch_action(SelectPrevMatch),
|
||||
Direction::Next => cx.dispatch_action(SelectNextMatch),
|
||||
})
|
||||
@@ -693,7 +693,7 @@ impl ProjectSearchBar {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(move |cx| cx.dispatch_action(ToggleSearchOption(option)))
|
||||
.on_click(move |_, cx| cx.dispatch_action(ToggleSearchOption(option)))
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.boxed()
|
||||
}
|
||||
@@ -844,16 +844,7 @@ mod tests {
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), cx);
|
||||
let (tree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_local_worktree("/dir", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
||||
let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
|
||||
let search_view = cx.add_view(Default::default(), |cx| {
|
||||
ProjectSearchView::new(search.clone(), cx)
|
||||
|
||||
@@ -483,17 +483,20 @@ impl<T: Item + PartialEq> PartialEq for SumTree<T> {
|
||||
impl<T: Item + Eq> Eq for SumTree<T> {}
|
||||
|
||||
impl<T: KeyedItem> SumTree<T> {
|
||||
pub fn insert_or_replace(&mut self, item: T, cx: &<T::Summary as Summary>::Context) -> bool {
|
||||
let mut replaced = false;
|
||||
pub fn insert_or_replace(
|
||||
&mut self,
|
||||
item: T,
|
||||
cx: &<T::Summary as Summary>::Context,
|
||||
) -> Option<T> {
|
||||
let mut replaced = None;
|
||||
*self = {
|
||||
let mut cursor = self.cursor::<T::Key>();
|
||||
let mut new_tree = cursor.slice(&item.key(), Bias::Left, cx);
|
||||
if cursor
|
||||
.item()
|
||||
.map_or(false, |cursor_item| cursor_item.key() == item.key())
|
||||
{
|
||||
cursor.next(cx);
|
||||
replaced = true;
|
||||
if let Some(cursor_item) = cursor.item() {
|
||||
if cursor_item.key() == item.key() {
|
||||
replaced = Some(cursor_item.clone());
|
||||
cursor.next(cx);
|
||||
}
|
||||
}
|
||||
new_tree.push(item, cx);
|
||||
new_tree.push_tree(cursor.suffix(cx), cx);
|
||||
@@ -502,6 +505,23 @@ impl<T: KeyedItem> SumTree<T> {
|
||||
replaced
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &T::Key, cx: &<T::Summary as Summary>::Context) -> Option<T> {
|
||||
let mut removed = None;
|
||||
*self = {
|
||||
let mut cursor = self.cursor::<T::Key>();
|
||||
let mut new_tree = cursor.slice(key, Bias::Left, cx);
|
||||
if let Some(item) = cursor.item() {
|
||||
if item.key() == *key {
|
||||
removed = Some(item.clone());
|
||||
cursor.next(cx);
|
||||
}
|
||||
}
|
||||
new_tree.push_tree(cursor.suffix(cx), cx);
|
||||
new_tree
|
||||
};
|
||||
removed
|
||||
}
|
||||
|
||||
pub fn edit(
|
||||
&mut self,
|
||||
mut edits: Vec<Edit<T>>,
|
||||
|
||||
@@ -204,14 +204,13 @@ pub struct ChatPanel {
|
||||
pub hovered_sign_in_prompt: TextStyle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct ProjectPanel {
|
||||
#[serde(flatten)]
|
||||
pub container: ContainerStyle,
|
||||
pub entry: ProjectPanelEntry,
|
||||
pub hovered_entry: ProjectPanelEntry,
|
||||
pub selected_entry: ProjectPanelEntry,
|
||||
pub hovered_selected_entry: ProjectPanelEntry,
|
||||
pub entry: Interactive<ProjectPanelEntry>,
|
||||
pub filename_editor: FieldEditor,
|
||||
pub indent_width: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
|
||||
@@ -50,7 +50,7 @@ impl<'a> VimTestContext<'a> {
|
||||
|
||||
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||
let item = workspace
|
||||
.update(cx, |workspace, cx| workspace.open_path(file, cx))
|
||||
.update(cx, |workspace, cx| workspace.open_path(file, true, cx))
|
||||
.await
|
||||
.expect("Could not open test file");
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ impl View for LspStatus {
|
||||
self.failed.join(", "),
|
||||
if self.failed.len() > 1 { "s" } else { "" }
|
||||
);
|
||||
handler = Some(|cx: &mut EventContext| cx.dispatch_action(DismissErrorMessage));
|
||||
handler = Some(|_, cx: &mut EventContext| cx.dispatch_action(DismissErrorMessage));
|
||||
} else {
|
||||
return Empty::new().boxed();
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
|
||||
pane.activate_item(action.0, true, cx);
|
||||
pane.activate_item(action.0, true, true, cx);
|
||||
});
|
||||
cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
|
||||
pane.activate_prev_item(cx);
|
||||
@@ -213,7 +213,7 @@ impl Pane {
|
||||
{
|
||||
let prev_active_item_index = pane.active_item_index;
|
||||
pane.nav_history.borrow_mut().set_mode(mode);
|
||||
pane.activate_item(index, true, cx);
|
||||
pane.activate_item(index, true, true, cx);
|
||||
pane.nav_history
|
||||
.borrow_mut()
|
||||
.set_mode(NavigationMode::Normal);
|
||||
@@ -257,6 +257,7 @@ impl Pane {
|
||||
workspace,
|
||||
pane.clone(),
|
||||
project_entry_id,
|
||||
true,
|
||||
cx,
|
||||
build_item,
|
||||
)
|
||||
@@ -287,6 +288,7 @@ impl Pane {
|
||||
workspace: &mut Workspace,
|
||||
pane: ViewHandle<Pane>,
|
||||
project_entry_id: ProjectEntryId,
|
||||
focus_item: bool,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
build_item: impl FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
|
||||
) -> Box<dyn ItemHandle> {
|
||||
@@ -294,7 +296,7 @@ impl Pane {
|
||||
for (ix, item) in pane.items.iter().enumerate() {
|
||||
if item.project_entry_id(cx) == Some(project_entry_id) {
|
||||
let item = item.boxed_clone();
|
||||
pane.activate_item(ix, true, cx);
|
||||
pane.activate_item(ix, true, focus_item, cx);
|
||||
return Some(item);
|
||||
}
|
||||
}
|
||||
@@ -304,7 +306,7 @@ impl Pane {
|
||||
existing_item
|
||||
} else {
|
||||
let item = build_item(cx);
|
||||
Self::add_item(workspace, pane, item.boxed_clone(), true, cx);
|
||||
Self::add_item(workspace, pane, item.boxed_clone(), true, focus_item, cx);
|
||||
item
|
||||
}
|
||||
}
|
||||
@@ -313,12 +315,15 @@ impl Pane {
|
||||
workspace: &mut Workspace,
|
||||
pane: ViewHandle<Pane>,
|
||||
item: Box<dyn ItemHandle>,
|
||||
local: bool,
|
||||
activate_pane: bool,
|
||||
focus_item: bool,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
// Prevent adding the same item to the pane more than once.
|
||||
if let Some(item_ix) = pane.read(cx).items.iter().position(|i| i.id() == item.id()) {
|
||||
pane.update(cx, |pane, cx| pane.activate_item(item_ix, local, cx));
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(item_ix, activate_pane, focus_item, cx)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,7 +332,7 @@ impl Pane {
|
||||
pane.update(cx, |pane, cx| {
|
||||
let item_idx = cmp::min(pane.active_item_index + 1, pane.items.len());
|
||||
pane.items.insert(item_idx, item);
|
||||
pane.activate_item(item_idx, local, cx);
|
||||
pane.activate_item(item_idx, activate_pane, focus_item, cx);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
@@ -378,7 +383,13 @@ impl Pane {
|
||||
self.items.iter().position(|i| i.id() == item.id())
|
||||
}
|
||||
|
||||
pub fn activate_item(&mut self, index: usize, local: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn activate_item(
|
||||
&mut self,
|
||||
index: usize,
|
||||
activate_pane: bool,
|
||||
focus_item: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
use NavigationMode::{GoingBack, GoingForward};
|
||||
if index < self.items.len() {
|
||||
let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
|
||||
@@ -387,11 +398,15 @@ impl Pane {
|
||||
&& prev_active_item_ix < self.items.len())
|
||||
{
|
||||
self.items[prev_active_item_ix].deactivated(cx);
|
||||
cx.emit(Event::ActivateItem { local });
|
||||
cx.emit(Event::ActivateItem {
|
||||
local: activate_pane,
|
||||
});
|
||||
}
|
||||
self.update_toolbar(cx);
|
||||
if local {
|
||||
if focus_item {
|
||||
self.focus_active_item(cx);
|
||||
}
|
||||
if activate_pane {
|
||||
self.activate(cx);
|
||||
}
|
||||
self.autoscroll = true;
|
||||
@@ -406,7 +421,7 @@ impl Pane {
|
||||
} else if self.items.len() > 0 {
|
||||
index = self.items.len() - 1;
|
||||
}
|
||||
self.activate_item(index, true, cx);
|
||||
self.activate_item(index, true, true, cx);
|
||||
}
|
||||
|
||||
pub fn activate_next_item(&mut self, cx: &mut ViewContext<Self>) {
|
||||
@@ -416,7 +431,7 @@ impl Pane {
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
self.activate_item(index, true, cx);
|
||||
self.activate_item(index, true, true, cx);
|
||||
}
|
||||
|
||||
fn close_active_item(
|
||||
@@ -498,7 +513,7 @@ impl Pane {
|
||||
if is_last_item_for_entry {
|
||||
if cx.read(|cx| item.has_conflict(cx) && item.can_save(cx)) {
|
||||
let mut answer = pane.update(&mut cx, |pane, cx| {
|
||||
pane.activate_item(item_to_close_ix, true, cx);
|
||||
pane.activate_item(item_to_close_ix, true, true, cx);
|
||||
cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
CONFLICT_MESSAGE,
|
||||
@@ -518,7 +533,7 @@ impl Pane {
|
||||
} else if cx.read(|cx| item.is_dirty(cx)) {
|
||||
if cx.read(|cx| item.can_save(cx)) {
|
||||
let mut answer = pane.update(&mut cx, |pane, cx| {
|
||||
pane.activate_item(item_to_close_ix, true, cx);
|
||||
pane.activate_item(item_to_close_ix, true, true, cx);
|
||||
cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
DIRTY_MESSAGE,
|
||||
@@ -535,7 +550,7 @@ impl Pane {
|
||||
}
|
||||
} else if cx.read(|cx| item.can_save_as(cx)) {
|
||||
let mut answer = pane.update(&mut cx, |pane, cx| {
|
||||
pane.activate_item(item_to_close_ix, true, cx);
|
||||
pane.activate_item(item_to_close_ix, true, true, cx);
|
||||
cx.prompt(
|
||||
PromptLevel::Warning,
|
||||
DIRTY_MESSAGE,
|
||||
@@ -737,7 +752,7 @@ impl Pane {
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click({
|
||||
let pane = pane.clone();
|
||||
move |cx| {
|
||||
move |_, cx| {
|
||||
cx.dispatch_action(CloseItem {
|
||||
item_id,
|
||||
pane: pane.clone(),
|
||||
@@ -949,7 +964,7 @@ mod tests {
|
||||
|
||||
let close_items = workspace.update(cx, |workspace, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(1, true, cx);
|
||||
pane.activate_item(1, true, true, cx);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item2.id());
|
||||
});
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ impl View for SidebarButtons {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(move |cx| {
|
||||
.on_click(move |_, cx| {
|
||||
cx.dispatch_action(ToggleSidebarItem {
|
||||
side,
|
||||
item_index: ix,
|
||||
|
||||
@@ -493,7 +493,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||
if T::should_activate_item_on_event(event) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
if let Some(ix) = pane.index_for_item(&item) {
|
||||
pane.activate_item(ix, true, cx);
|
||||
pane.activate_item(ix, true, true, cx);
|
||||
pane.activate(cx);
|
||||
}
|
||||
});
|
||||
@@ -898,7 +898,7 @@ impl Workspace {
|
||||
if fs.is_file(&abs_path).await {
|
||||
Some(
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.open_path(project_path, cx)
|
||||
this.open_path(project_path, true, cx)
|
||||
})
|
||||
.await,
|
||||
)
|
||||
@@ -1065,7 +1065,7 @@ impl Workspace {
|
||||
Side::Right => &mut self.right_sidebar,
|
||||
};
|
||||
let active_item = sidebar.update(cx, |sidebar, cx| {
|
||||
sidebar.toggle_item(action.item_index, cx);
|
||||
sidebar.activate_item(action.item_index, cx);
|
||||
sidebar.active_item().cloned()
|
||||
});
|
||||
if let Some(active_item) = active_item {
|
||||
@@ -1099,12 +1099,13 @@ impl Workspace {
|
||||
|
||||
pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
|
||||
let pane = self.active_pane().clone();
|
||||
Pane::add_item(self, pane, item, true, cx);
|
||||
Pane::add_item(self, pane, item, true, true, cx);
|
||||
}
|
||||
|
||||
pub fn open_path(
|
||||
&mut self,
|
||||
path: impl Into<ProjectPath>,
|
||||
focus_item: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>> {
|
||||
let pane = self.active_pane().downgrade();
|
||||
@@ -1119,6 +1120,7 @@ impl Workspace {
|
||||
this,
|
||||
pane,
|
||||
project_entry_id,
|
||||
focus_item,
|
||||
cx,
|
||||
build_item,
|
||||
))
|
||||
@@ -1187,7 +1189,7 @@ impl Workspace {
|
||||
});
|
||||
if let Some((pane, ix)) = result {
|
||||
self.activate_pane(pane.clone(), cx);
|
||||
pane.update(cx, |pane, cx| pane.activate_item(ix, true, cx));
|
||||
pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -1277,7 +1279,7 @@ impl Workspace {
|
||||
self.activate_pane(new_pane.clone(), cx);
|
||||
if let Some(item) = pane.read(cx).active_item() {
|
||||
if let Some(clone) = item.clone_on_split(cx.as_mut()) {
|
||||
Pane::add_item(self, new_pane.clone(), clone, true, cx);
|
||||
Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
|
||||
}
|
||||
}
|
||||
self.center.split(&pane, &new_pane, direction).unwrap();
|
||||
@@ -1584,7 +1586,7 @@ impl Workspace {
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(|cx| cx.dispatch_action(Authenticate))
|
||||
.on_click(|_, cx| cx.dispatch_action(Authenticate))
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
@@ -1635,7 +1637,7 @@ impl Workspace {
|
||||
if let Some(peer_id) = peer_id {
|
||||
MouseEventHandler::new::<ToggleFollow, _, _>(replica_id.into(), cx, move |_, _| content)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(move |cx| cx.dispatch_action(ToggleFollow(peer_id)))
|
||||
.on_click(move |_, cx| cx.dispatch_action(ToggleFollow(peer_id)))
|
||||
.boxed()
|
||||
} else {
|
||||
content
|
||||
@@ -1667,7 +1669,7 @@ impl Workspace {
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(|cx| cx.dispatch_action(ToggleShare))
|
||||
.on_click(|_, cx| cx.dispatch_action(ToggleShare))
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
@@ -1961,7 +1963,7 @@ impl Workspace {
|
||||
}
|
||||
|
||||
for (pane, item) in items_to_add {
|
||||
Pane::add_item(self, pane.clone(), item.boxed_clone(), false, cx);
|
||||
Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx);
|
||||
if pane == self.active_pane {
|
||||
pane.update(cx, |pane, cx| pane.focus_active_item(cx));
|
||||
}
|
||||
|
||||
+11
-10
@@ -446,7 +446,7 @@ mod tests {
|
||||
|
||||
// Open the first entry
|
||||
let entry_1 = workspace
|
||||
.update(cx, |w, cx| w.open_path(file1.clone(), cx))
|
||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| {
|
||||
@@ -460,7 +460,7 @@ mod tests {
|
||||
|
||||
// Open the second entry
|
||||
workspace
|
||||
.update(cx, |w, cx| w.open_path(file2.clone(), cx))
|
||||
.update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| {
|
||||
@@ -474,7 +474,7 @@ mod tests {
|
||||
|
||||
// Open the first entry again. The existing pane item is activated.
|
||||
let entry_1b = workspace
|
||||
.update(cx, |w, cx| w.open_path(file1.clone(), cx))
|
||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(entry_1.id(), entry_1b.id());
|
||||
@@ -492,7 +492,7 @@ mod tests {
|
||||
workspace
|
||||
.update(cx, |w, cx| {
|
||||
w.split_pane(w.active_pane().clone(), SplitDirection::Right, cx);
|
||||
w.open_path(file2.clone(), cx)
|
||||
w.open_path(file2.clone(), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -511,8 +511,8 @@ mod tests {
|
||||
// Open the third entry twice concurrently. Only one pane item is added.
|
||||
let (t1, t2) = workspace.update(cx, |w, cx| {
|
||||
(
|
||||
w.open_path(file3.clone(), cx),
|
||||
w.open_path(file3.clone(), cx),
|
||||
w.open_path(file3.clone(), true, cx),
|
||||
w.open_path(file3.clone(), true, cx),
|
||||
)
|
||||
});
|
||||
t1.await.unwrap();
|
||||
@@ -780,6 +780,7 @@ mod tests {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: Path::new("the-new-name.rs").into(),
|
||||
},
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
@@ -875,7 +876,7 @@ mod tests {
|
||||
let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| w.open_path(file1.clone(), cx))
|
||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -955,7 +956,7 @@ mod tests {
|
||||
let file3 = entries[2].clone();
|
||||
|
||||
let editor1 = workspace
|
||||
.update(cx, |w, cx| w.open_path(file1.clone(), cx))
|
||||
.update(cx, |w, cx| w.open_path(file1.clone(), true, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
@@ -964,13 +965,13 @@ mod tests {
|
||||
editor.select_display_ranges(&[DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)], cx);
|
||||
});
|
||||
let editor2 = workspace
|
||||
.update(cx, |w, cx| w.open_path(file2.clone(), cx))
|
||||
.update(cx, |w, cx| w.open_path(file2.clone(), true, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
let editor3 = workspace
|
||||
.update(cx, |w, cx| w.open_path(file3.clone(), cx))
|
||||
.update(cx, |w, cx| w.open_path(file3.clone(), true, cx))
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
import Theme from "../themes/theme";
|
||||
import { Color } from "../utils/color";
|
||||
import { panel } from "./app";
|
||||
import { backgroundColor, iconColor, text, TextColor } from "./components";
|
||||
import { backgroundColor, iconColor, player, text } from "./components";
|
||||
|
||||
export default function projectPanel(theme: Theme) {
|
||||
function entry(theme: Theme, textColor: TextColor, background?: Color) {
|
||||
return {
|
||||
return {
|
||||
...panel,
|
||||
padding: { left: 12, right: 12, top: 6, bottom: 6 },
|
||||
indentWidth: 20,
|
||||
entry: {
|
||||
height: 24,
|
||||
background,
|
||||
iconColor: iconColor(theme, "muted"),
|
||||
iconSize: 8,
|
||||
iconSpacing: 8,
|
||||
text: text(theme, "mono", textColor, { size: "sm" }),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...panel,
|
||||
entry: entry(theme, "muted"),
|
||||
hoveredEntry: entry(
|
||||
theme,
|
||||
"primary",
|
||||
backgroundColor(theme, 300, "hovered")
|
||||
),
|
||||
selectedEntry: entry(theme, "primary"),
|
||||
hoveredSelectedEntry: entry(
|
||||
theme,
|
||||
"active",
|
||||
backgroundColor(theme, 300, "hovered")
|
||||
),
|
||||
padding: { left: 12, right: 12, top: 6, bottom: 6 },
|
||||
text: text(theme, "mono", "muted", { size: "sm" }),
|
||||
hover: {
|
||||
background: backgroundColor(theme, 300, "hovered"),
|
||||
text: text(theme, "mono", "primary", { size: "sm" }),
|
||||
},
|
||||
active: {
|
||||
background: backgroundColor(theme, 300, "active"),
|
||||
text: text(theme, "mono", "primary", { size: "sm" }),
|
||||
},
|
||||
activeHover: {
|
||||
background: backgroundColor(theme, 300, "hovered"),
|
||||
text: text(theme, "mono", "active", { size: "sm" }),
|
||||
}
|
||||
},
|
||||
filenameEditor: {
|
||||
background: backgroundColor(theme, 500, "active"),
|
||||
text: text(theme, "mono", "primary", { size: "sm" }),
|
||||
selection: player(theme, 1).selection,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user