Compare commits

...
Sign in to create a new pull request.

15 commits

Author SHA1 Message Date
Piotr Osiewicz
72e42a0d89 Draw rest of the owl 2023-11-07 00:10:59 +01:00
Piotr Osiewicz
750f901f9c Search 2023-11-06 23:48:09 +01:00
Piotr Osiewicz
7d6ce9432a Fix activity_indicator, language_tools, project_panel, project_symbols, semantic_index 2023-11-06 23:38:18 +01:00
Piotr Osiewicz
b381277c73 Migrate diagnostics and file_finder 2023-11-06 23:15:18 +01:00
Piotr Osiewicz
7968891f9e Update Cargo.lock 2023-11-06 18:37:44 +01:00
Piotr Osiewicz
efcaf48723 Merge branch 'main' into editor_renovation 2023-11-06 18:36:32 +01:00
Piotr Osiewicz
69a77e5ec8 Extensions compile 2023-11-06 14:49:18 +01:00
Piotr Osiewicz
d17d450e2d Checkpoint, extensions are almost compiling 2023-11-06 14:25:12 +01:00
Piotr Osiewicz
f3dcea3623 WIP 2023-11-06 12:58:13 +01:00
Piotr Osiewicz
a2a6d77cdb Keep going, most of the editor_extensions compile now 2023-11-03 16:54:17 +01:00
Piotr Osiewicz
bff46f7e55 Merge branch 'main' into editor_renovation 2023-11-03 15:37:27 +01:00
Piotr Osiewicz
eae0e30277 Add editor_extensions to the workspace 2023-11-03 15:36:57 +01:00
Piotr Osiewicz
144d011a07 Make editor compile
Co-authored-by: Kirill <kirill@zed.dev>
2023-11-03 15:14:44 +01:00
Piotr Osiewicz
fcb2094fae WIP
Co-authored-by: Kirill <kirill@zed.dev>
2023-11-03 14:47:08 +01:00
Piotr Osiewicz
cadf8d0160 Extract a separate trait for Project and Workspace interfaces 2023-11-02 20:30:54 +01:00
58 changed files with 2321 additions and 1215 deletions

77
Cargo.lock generated
View file

@ -8,6 +8,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"auto_update", "auto_update",
"editor", "editor",
"editor_extensions",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"language", "language",
@ -328,6 +329,7 @@ dependencies = [
"collections", "collections",
"ctor", "ctor",
"editor", "editor",
"editor_extensions",
"env_logger 0.9.3", "env_logger 0.9.3",
"fs", "fs",
"futures 0.3.28", "futures 0.3.28",
@ -1725,6 +1727,7 @@ dependencies = [
"db", "db",
"drag_and_drop", "drag_and_drop",
"editor", "editor",
"editor_extensions",
"feature_flags", "feature_flags",
"feedback", "feedback",
"futures 0.3.28", "futures 0.3.28",
@ -2459,6 +2462,7 @@ dependencies = [
"client", "client",
"collections", "collections",
"editor", "editor",
"editor_extensions",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"language", "language",
@ -2466,6 +2470,7 @@ dependencies = [
"lsp", "lsp",
"postage", "postage",
"project", "project",
"project_types",
"schemars", "schemars",
"serde", "serde",
"serde_derive", "serde_derive",
@ -2612,6 +2617,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"anyhow", "anyhow",
"async-trait",
"client", "client",
"clock", "clock",
"collections", "collections",
@ -2637,6 +2643,7 @@ dependencies = [
"parking_lot 0.11.2", "parking_lot 0.11.2",
"postage", "postage",
"project", "project",
"project_types",
"rand 0.8.5", "rand 0.8.5",
"rich_text", "rich_text",
"rpc", "rpc",
@ -2658,6 +2665,7 @@ dependencies = [
"unindent", "unindent",
"util", "util",
"workspace", "workspace",
"workspace_types",
] ]
[[package]] [[package]]
@ -2714,6 +2722,31 @@ dependencies = [
"workspace2", "workspace2",
] ]
[[package]]
name = "editor_extensions"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"client",
"collections",
"db",
"editor",
"futures 0.3.28",
"gpui",
"language",
"lsp",
"project",
"project_types",
"rpc",
"smallvec",
"text",
"theme",
"util",
"workspace",
"workspace_types",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.9.0" version = "1.9.0"
@ -2910,6 +2943,7 @@ dependencies = [
"anyhow", "anyhow",
"client", "client",
"editor", "editor",
"editor_extensions",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"human_bytes", "human_bytes",
@ -2950,6 +2984,7 @@ dependencies = [
"collections", "collections",
"ctor", "ctor",
"editor", "editor",
"editor_extensions",
"env_logger 0.9.3", "env_logger 0.9.3",
"fuzzy", "fuzzy",
"gpui", "gpui",
@ -2958,6 +2993,7 @@ dependencies = [
"picker", "picker",
"postage", "postage",
"project", "project",
"project_types",
"serde_json", "serde_json",
"settings", "settings",
"text", "text",
@ -4521,6 +4557,7 @@ dependencies = [
"client", "client",
"collections", "collections",
"editor", "editor",
"editor_extensions",
"env_logger 0.9.3", "env_logger 0.9.3",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
@ -6356,6 +6393,7 @@ dependencies = [
"postage", "postage",
"prettier", "prettier",
"pretty_assertions", "pretty_assertions",
"project_types",
"rand 0.8.5", "rand 0.8.5",
"regex", "regex",
"rpc", "rpc",
@ -6450,6 +6488,7 @@ dependencies = [
"postage", "postage",
"pretty_assertions", "pretty_assertions",
"project", "project",
"project_types",
"schemars", "schemars",
"serde", "serde",
"serde_derive", "serde_derive",
@ -6467,6 +6506,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"editor", "editor",
"editor_extensions",
"futures 0.3.28", "futures 0.3.28",
"fuzzy", "fuzzy",
"gpui", "gpui",
@ -6476,6 +6516,7 @@ dependencies = [
"picker", "picker",
"postage", "postage",
"project", "project",
"project_types",
"settings", "settings",
"smol", "smol",
"text", "text",
@ -6484,6 +6525,23 @@ dependencies = [
"workspace", "workspace",
] ]
[[package]]
name = "project_types"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"gpui",
"itertools 0.10.5",
"language",
"lsp",
"rpc",
"schemars",
"serde",
"serde_json",
"settings",
]
[[package]] [[package]]
name = "prometheus" name = "prometheus"
version = "0.13.3" version = "0.13.3"
@ -7671,6 +7729,7 @@ dependencies = [
"client", "client",
"collections", "collections",
"editor", "editor",
"editor_extensions",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"language", "language",
@ -7740,6 +7799,7 @@ dependencies = [
"postage", "postage",
"pretty_assertions", "pretty_assertions",
"project", "project",
"project_types",
"rand 0.8.5", "rand 0.8.5",
"rpc", "rpc",
"rusqlite", "rusqlite",
@ -10914,6 +10974,7 @@ dependencies = [
"parking_lot 0.11.2", "parking_lot 0.11.2",
"postage", "postage",
"project", "project",
"project_types",
"schemars", "schemars",
"serde", "serde",
"serde_derive", "serde_derive",
@ -10924,6 +10985,7 @@ dependencies = [
"theme", "theme",
"util", "util",
"uuid 1.4.1", "uuid 1.4.1",
"workspace_types",
] ]
[[package]] [[package]]
@ -10964,6 +11026,20 @@ dependencies = [
"uuid 1.4.1", "uuid 1.4.1",
] ]
[[package]]
name = "workspace_types"
version = "0.1.0"
dependencies = [
"anyhow",
"gpui",
"language",
"lsp",
"project_types",
"rpc",
"serde",
"text",
]
[[package]] [[package]]
name = "ws2_32-sys" name = "ws2_32-sys"
version = "0.2.1" version = "0.2.1"
@ -11075,6 +11151,7 @@ dependencies = [
"db", "db",
"diagnostics", "diagnostics",
"editor", "editor",
"editor_extensions",
"env_logger 0.9.3", "env_logger 0.9.3",
"feature_flags", "feature_flags",
"feedback", "feedback",

View file

@ -31,6 +31,7 @@ members = [
"crates/diagnostics", "crates/diagnostics",
"crates/drag_and_drop", "crates/drag_and_drop",
"crates/editor", "crates/editor",
"crates/editor_extensions",
"crates/feature_flags", "crates/feature_flags",
"crates/feature_flags2", "crates/feature_flags2",
"crates/feedback", "crates/feedback",
@ -76,6 +77,7 @@ members = [
"crates/project2", "crates/project2",
"crates/project_panel", "crates/project_panel",
"crates/project_symbols", "crates/project_symbols",
"crates/project_types",
"crates/recent_projects", "crates/recent_projects",
"crates/rope", "crates/rope",
"crates/rpc", "crates/rpc",

View file

@ -11,6 +11,7 @@ doctest = false
[dependencies] [dependencies]
auto_update = { path = "../auto_update" } auto_update = { path = "../auto_update" }
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
language = { path = "../language" } language = { path = "../language" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
project = { path = "../project" } project = { path = "../project" }

View file

@ -1,5 +1,6 @@
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage}; use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
use editor::Editor; use editor::Editor;
use editor_extensions::FollowableEditor;
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
actions, anyhow, actions, anyhow,
@ -103,9 +104,9 @@ impl ActivityIndicator {
); );
}); });
workspace.add_item( workspace.add_item(
Box::new( Box::new(cx.add_view(|cx| {
cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)), FollowableEditor::for_buffer(buffer, project.clone(), cx)
), })),
cx, cx,
); );
} }

View file

@ -13,6 +13,7 @@ ai = { path = "../ai" }
client = { path = "../client" } client = { path = "../client" }
collections = { path = "../collections"} collections = { path = "../collections"}
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
fs = { path = "../fs" } fs = { path = "../fs" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
language = { path = "../language" } language = { path = "../language" }

View file

@ -24,6 +24,7 @@ use editor::{
scroll::autoscroll::{Autoscroll, AutoscrollStrategy}, scroll::autoscroll::{Autoscroll, AutoscrollStrategy},
Anchor, Editor, MoveDown, MoveUp, MultiBufferSnapshot, ToOffset, ToPoint, Anchor, Editor, MoveDown, MoveUp, MultiBufferSnapshot, ToOffset, ToPoint,
}; };
use editor_extensions::FollowableEditor;
use fs::Fs; use fs::Fs;
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
@ -2196,7 +2197,7 @@ struct ConversationEditor {
conversation: ModelHandle<Conversation>, conversation: ModelHandle<Conversation>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
workspace: WeakViewHandle<Workspace>, workspace: WeakViewHandle<Workspace>,
editor: ViewHandle<Editor>, editor: ViewHandle<FollowableEditor>,
blocks: HashSet<BlockId>, blocks: HashSet<BlockId>,
scroll_position: Option<ScrollPosition>, scroll_position: Option<ScrollPosition>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
@ -2222,10 +2223,11 @@ impl ConversationEditor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let editor = cx.add_view(|cx| { let editor = cx.add_view(|cx| {
let mut editor = Editor::for_buffer(conversation.read(cx).buffer.clone(), None, cx); let mut editor = FollowableEditor::for_raw_buffer(conversation.read(cx).buffer.clone(), cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor.0.update(cx, |this, cx| {this.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx); this.set_show_gutter(false, cx);
editor.set_show_wrap_guides(false, cx); this.set_show_wrap_guides(false, cx);
});
editor editor
}); });
@ -2277,11 +2279,11 @@ impl ConversationEditor {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !new_selections.is_empty() { if !new_selections.is_empty() {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.change_selections( editor.0.update(cx, |this, cx| this.change_selections(
Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)), Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
cx, cx,
|selections| selections.select_ranges(new_selections), |selections| selections.select_ranges(new_selections),
); ));
}); });
// Avoid scrolling to the new cursor position so the assistant's output is stable. // Avoid scrolling to the new cursor position so the assistant's output is stable.
cx.defer(|this, _| this.scroll_position = None); cx.defer(|this, _| this.scroll_position = None);
@ -2310,7 +2312,7 @@ impl ConversationEditor {
} }
fn cursors(&self, cx: &AppContext) -> Vec<usize> { fn cursors(&self, cx: &AppContext) -> Vec<usize> {
let selections = self.editor.read(cx).selections.all::<usize>(cx); let selections = self.editor.read(cx).0.read(cx).selections.all::<usize>(cx);
selections selections
.into_iter() .into_iter()
.map(|selection| selection.head()) .map(|selection| selection.head())
@ -2339,14 +2341,14 @@ impl ConversationEditor {
ConversationEvent::StreamedCompletion => { ConversationEvent::StreamedCompletion => {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
if let Some(scroll_position) = self.scroll_position { if let Some(scroll_position) = self.scroll_position {
let snapshot = editor.snapshot(cx); let snapshot = editor.0.update(cx, |this, cx| this.snapshot(cx));
let cursor_point = scroll_position.cursor.to_display_point(&snapshot); let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
let scroll_top = let scroll_top =
cursor_point.row() as f32 - scroll_position.offset_before_cursor.y(); cursor_point.row() as f32 - scroll_position.offset_before_cursor.y();
editor.set_scroll_position( editor.0.update(cx, |this, cx| this.set_scroll_position(
vec2f(scroll_position.offset_before_cursor.x(), scroll_top), vec2f(scroll_position.offset_before_cursor.x(), scroll_top),
cx, cx,
); ));
} }
}); });
} }
@ -2355,7 +2357,7 @@ impl ConversationEditor {
fn handle_editor_event( fn handle_editor_event(
&mut self, &mut self,
_: ViewHandle<Editor>, _: ViewHandle<FollowableEditor>,
event: &editor::Event, event: &editor::Event,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
@ -2377,15 +2379,15 @@ impl ConversationEditor {
fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> { fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx); let snapshot = editor.0.update(cx, |this, cx| this.snapshot(cx));
let cursor = editor.selections.newest_anchor().head(); let cursor = editor.0.read(cx).selections.newest_anchor().head();
let cursor_row = cursor.to_display_point(&snapshot.display_snapshot).row() as f32; let cursor_row = cursor.to_display_point(&snapshot.display_snapshot).row() as f32;
let scroll_position = editor let scroll_position = editor.0.read(cx)
.scroll_manager .scroll_manager
.anchor() .anchor()
.scroll_position(&snapshot.display_snapshot); .scroll_position(&snapshot.display_snapshot);
let scroll_bottom = scroll_position.y() + editor.visible_line_count().unwrap_or(0.); let scroll_bottom = scroll_position.y() + editor.0.read(cx).visible_line_count().unwrap_or(0.);
if (scroll_position.y()..scroll_bottom).contains(&cursor_row) { if (scroll_position.y()..scroll_bottom).contains(&cursor_row) {
Some(ScrollPosition { Some(ScrollPosition {
cursor, cursor,
@ -2402,7 +2404,7 @@ impl ConversationEditor {
fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) { fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx); let buffer = editor.0.read(cx).buffer().read(cx).snapshot(cx);
let excerpt_id = *buffer.as_singleton().unwrap().0; let excerpt_id = *buffer.as_singleton().unwrap().0;
let old_blocks = std::mem::take(&mut self.blocks); let old_blocks = std::mem::take(&mut self.blocks);
let new_blocks = self let new_blocks = self
@ -2505,8 +2507,10 @@ impl ConversationEditor {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
editor.remove_blocks(old_blocks, None, cx); let ids = editor.0.update(cx, |this, cx| {
let ids = editor.insert_blocks(new_blocks, None, cx); this.remove_blocks(old_blocks, None, cx);
this.insert_blocks(new_blocks, None, cx)
});
self.blocks = HashSet::from_iter(ids); self.blocks = HashSet::from_iter(ids);
}); });
} }
@ -2568,14 +2572,14 @@ impl ConversationEditor {
conversation.update(cx, |conversation, cx| { conversation.update(cx, |conversation, cx| {
conversation conversation
.editor .editor
.update(cx, |editor, cx| editor.insert(&text, cx)) .update(cx, |editor, cx| editor.0.update(cx, |this, cx| this.insert(&text, cx)))
}); });
}); });
} }
} }
fn copy(&mut self, _: &editor::Copy, cx: &mut ViewContext<Self>) { fn copy(&mut self, _: &editor::Copy, cx: &mut ViewContext<Self>) {
let editor = self.editor.read(cx); let editor = self.editor.read(cx).0.read(cx);
let conversation = self.conversation.read(cx); let conversation = self.conversation.read(cx);
if editor.selections.count() == 1 { if editor.selections.count() == 1 {
let selection = editor.selections.newest::<usize>(cx); let selection = editor.selections.newest::<usize>(cx);
@ -2610,9 +2614,9 @@ impl ConversationEditor {
fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) { fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
self.conversation.update(cx, |conversation, cx| { self.conversation.update(cx, |conversation, cx| {
let selections = self.editor.read(cx).selections.disjoint_anchors(); let selections = self.editor.read(cx).0.read(cx).selections.disjoint_anchors();
for selection in selections.into_iter() { for selection in selections.into_iter() {
let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx); let buffer = self.editor.read(cx).0.read(cx).buffer().read(cx).snapshot(cx);
let range = selection let range = selection
.map(|endpoint| endpoint.to_offset(&buffer)) .map(|endpoint| endpoint.to_offset(&buffer))
.range(); .range();

View file

@ -32,6 +32,7 @@ collections = { path = "../collections" }
context_menu = { path = "../context_menu" } context_menu = { path = "../context_menu" }
drag_and_drop = { path = "../drag_and_drop" } drag_and_drop = { path = "../drag_and_drop" }
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
feedback = { path = "../feedback" } feedback = { path = "../feedback" }
fuzzy = { path = "../fuzzy" } fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }

View file

@ -7,6 +7,7 @@ use client::{
}; };
use collections::HashMap; use collections::HashMap;
use editor::{CollaborationHub, Editor}; use editor::{CollaborationHub, Editor};
use editor_extensions::FollowableEditor;
use gpui::{ use gpui::{
actions, actions,
elements::{ChildView, Label}, elements::{ChildView, Label},
@ -35,7 +36,7 @@ pub fn init(cx: &mut AppContext) {
} }
pub struct ChannelView { pub struct ChannelView {
pub editor: ViewHandle<Editor>, pub editor: ViewHandle<FollowableEditor>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
channel_store: ModelHandle<ChannelStore>, channel_store: ModelHandle<ChannelStore>,
channel_buffer: ModelHandle<ChannelBuffer>, channel_buffer: ModelHandle<ChannelBuffer>,
@ -137,16 +138,18 @@ impl ChannelView {
) -> Self { ) -> Self {
let buffer = channel_buffer.read(cx).buffer(); let buffer = channel_buffer.read(cx).buffer();
let editor = cx.add_view(|cx| { let editor = cx.add_view(|cx| {
let mut editor = Editor::for_buffer(buffer, None, cx); let mut editor = FollowableEditor::for_raw_buffer(buffer, cx);
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub( editor.0.update(cx, |this, cx| {
this.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
channel_buffer.clone(), channel_buffer.clone(),
))); )));
editor.set_read_only( this.set_read_only(
!channel_buffer !channel_buffer
.read(cx) .read(cx)
.channel(cx) .channel(cx)
.is_some_and(|c| c.can_edit_notes()), .is_some_and(|c| c.can_edit_notes()),
); );
});
editor editor
}); });
let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone())); let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()));
@ -176,12 +179,12 @@ impl ChannelView {
) { ) {
match event { match event {
ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| { ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
editor.set_read_only(true); editor.0.update(cx, |this, cx| this.set_read_only(true));
cx.notify(); cx.notify();
}), }),
ChannelBufferEvent::ChannelChanged => { ChannelBufferEvent::ChannelChanged => {
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.set_read_only(!self.channel(cx).is_some_and(|c| c.can_edit_notes())); editor.0.update(cx, |this, cx| this.set_read_only(!self.channel(cx).is_some_and(|c| c.can_edit_notes())));
cx.emit(editor::Event::TitleChanged); cx.emit(editor::Event::TitleChanged);
cx.notify() cx.notify()
}); });
@ -320,7 +323,7 @@ impl Item for ChannelView {
} }
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
editor::Editor::to_item_events(event) FollowableEditor::to_item_events(event)
} }
} }
@ -430,7 +433,7 @@ impl FollowableItem for ChannelView {
} }
fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool { fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool {
Editor::should_unfollow_on_event(event, cx) FollowableEditor::should_unfollow_on_event(event, cx)
} }
fn is_project_item(&self, _cx: &AppContext) -> bool { fn is_project_item(&self, _cx: &AppContext) -> bool {

View file

@ -20,6 +20,7 @@ use context_menu::{ContextMenu, ContextMenuItem};
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use drag_and_drop::{DragAndDrop, Draggable}; use drag_and_drop::{DragAndDrop, Draggable};
use editor::{Cancel, Editor}; use editor::{Cancel, Editor};
use editor_extensions::FollowableEditor;
use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt};
use futures::StreamExt; use futures::StreamExt;
use fuzzy::{match_strings, StringMatchCandidate}; use fuzzy::{match_strings, StringMatchCandidate};
@ -274,7 +275,7 @@ pub struct CollabPanel {
pending_serialization: Task<Option<()>>, pending_serialization: Task<Option<()>>,
context_menu: ViewHandle<ContextMenu>, context_menu: ViewHandle<ContextMenu>,
filter_editor: ViewHandle<Editor>, filter_editor: ViewHandle<Editor>,
channel_name_editor: ViewHandle<Editor>, channel_name_editor: ViewHandle<FollowableEditor>,
channel_editing_state: Option<ChannelEditingState>, channel_editing_state: Option<ChannelEditingState>,
entries: Vec<ListEntry>, entries: Vec<ListEntry>,
selection: Option<usize>, selection: Option<usize>,
@ -409,7 +410,7 @@ impl CollabPanel {
.detach(); .detach();
let channel_name_editor = cx.add_view(|cx| { let channel_name_editor = cx.add_view(|cx| {
Editor::single_line( FollowableEditor::single_line(
Some(Arc::new(|theme| { Some(Arc::new(|theme| {
theme.collab_panel.user_query_editor.clone() theme.collab_panel.user_query_editor.clone()
})), })),
@ -1433,7 +1434,7 @@ impl CollabPanel {
fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool { fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
if let Some(_) = self.channel_editing_state.take() { if let Some(_) = self.channel_editing_state.take() {
self.channel_name_editor.update(cx, |editor, cx| { self.channel_name_editor.update(cx, |editor, cx| {
editor.set_text("", cx); editor.0.update(cx, |this, cx| this.set_text("", cx));
}); });
true true
} else { } else {
@ -2822,7 +2823,7 @@ impl CollabPanel {
fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) { fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
if self.channel_editing_state.is_some() { if self.channel_editing_state.is_some() {
self.channel_name_editor.update(cx, |editor, cx| { self.channel_name_editor.update(cx, |editor, cx| {
editor.insert(" ", cx); editor.0.update(cx, |this, cx| this.insert(" ", cx));
}); });
} }
} }
@ -2838,7 +2839,7 @@ impl CollabPanel {
if pending_name.is_some() { if pending_name.is_some() {
return false; return false;
} }
let channel_name = self.channel_name_editor.read(cx).text(cx); let channel_name = self.channel_name_editor.read(cx).0.read(cx).text(cx);
*pending_name = Some(channel_name.clone()); *pending_name = Some(channel_name.clone());
@ -2856,7 +2857,7 @@ impl CollabPanel {
if pending_name.is_some() { if pending_name.is_some() {
return false; return false;
} }
let channel_name = self.channel_name_editor.read(cx).text(cx); let channel_name = self.channel_name_editor.read(cx).0.read(cx).text(cx);
*pending_name = Some(channel_name.clone()); *pending_name = Some(channel_name.clone());
self.channel_store self.channel_store
@ -3025,8 +3026,11 @@ impl CollabPanel {
pending_name: None, pending_name: None,
}); });
self.channel_name_editor.update(cx, |editor, cx| { self.channel_name_editor.update(cx, |editor, cx| {
editor.set_text(channel.name.clone(), cx); editor.0.update(cx, |this, cx| {
editor.select_all(&Default::default(), cx); this.set_text(channel.name.clone(), cx);
this.select_all(&Default::default(), cx);
})
}); });
cx.focus(self.channel_name_editor.as_any()); cx.focus(self.channel_name_editor.as_any());
self.update_entries(false, cx); self.update_entries(false, cx);

View file

@ -118,6 +118,7 @@ pub async fn open_test_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M>
#[macro_export] #[macro_export]
macro_rules! define_connection { macro_rules! define_connection {
(pub static ref $id:ident: $t:ident<()> = $migrations:expr;) => { (pub static ref $id:ident: $t:ident<()> = $migrations:expr;) => {
#[derive(Clone)]
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>); pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
impl ::std::ops::Deref for $t { impl ::std::ops::Deref for $t {
@ -149,6 +150,7 @@ macro_rules! define_connection {
} }
}; };
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => { (pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
#[derive(Clone)]
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>); pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
impl ::std::ops::Deref for $t { impl ::std::ops::Deref for $t {

View file

@ -11,10 +11,12 @@ doctest = false
[dependencies] [dependencies]
collections = { path = "../collections" } collections = { path = "../collections" }
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
language = { path = "../language" } language = { path = "../language" }
lsp = { path = "../lsp" } lsp = { path = "../lsp" }
project = { path = "../project" } project = { path = "../project" }
project_types = { path = "../project_types" }
settings = { path = "../settings" } settings = { path = "../settings" }
theme = { path = "../theme" } theme = { path = "../theme" }
util = { path = "../util" } util = { path = "../util" }

View file

@ -11,6 +11,7 @@ use editor::{
scroll::autoscroll::Autoscroll, scroll::autoscroll::Autoscroll,
Editor, ExcerptId, ExcerptRange, MultiBuffer, ToOffset, Editor, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
}; };
use editor_extensions::FollowableEditor;
use futures::future::try_join_all; use futures::future::try_join_all;
use gpui::{ use gpui::{
actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity, actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity,
@ -21,8 +22,9 @@ use language::{
SelectionGoal, SelectionGoal,
}; };
use lsp::LanguageServerId; use lsp::LanguageServerId;
use project::{DiagnosticSummary, Project, ProjectPath}; use project::{DiagnosticSummary, Project};
use project_diagnostics_settings::ProjectDiagnosticsSettings; use project_diagnostics_settings::ProjectDiagnosticsSettings;
use project_types::ProjectPath;
use serde_json::json; use serde_json::json;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
@ -58,7 +60,7 @@ type Event = editor::Event;
struct ProjectDiagnosticsEditor { struct ProjectDiagnosticsEditor {
project: ModelHandle<Project>, project: ModelHandle<Project>,
workspace: WeakViewHandle<Workspace>, workspace: WeakViewHandle<Workspace>,
editor: ViewHandle<Editor>, editor: ViewHandle<FollowableEditor>,
summary: DiagnosticSummary, summary: DiagnosticSummary,
excerpts: ModelHandle<MultiBuffer>, excerpts: ModelHandle<MultiBuffer>,
path_states: Vec<PathState>, path_states: Vec<PathState>,
@ -172,7 +174,7 @@ impl ProjectDiagnosticsEditor {
.or_default() .or_default()
.insert(path.clone()); .insert(path.clone());
let no_multiselections = this.editor.update(cx, |editor, cx| { let no_multiselections = this.editor.update(cx, |editor, cx| {
editor.selections.all::<usize>(cx).len() <= 1 editor.0.read(cx).selections.all::<usize>(cx).len() <= 1
}); });
if no_multiselections && !this.is_dirty(cx) { if no_multiselections && !this.is_dirty(cx) {
this.update_excerpts(Some(*language_server_id), cx); this.update_excerpts(Some(*language_server_id), cx);
@ -184,8 +186,10 @@ impl ProjectDiagnosticsEditor {
let excerpts = cx.add_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id())); let excerpts = cx.add_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
let editor = cx.add_view(|cx| { let editor = cx.add_view(|cx| {
let mut editor = let mut editor =
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), cx); FollowableEditor::for_multibuffer(excerpts.clone(), project_handle.clone(), cx);
editor.set_vertical_scroll_margin(5, cx); editor
.0
.update(cx, |this, cx| this.set_vertical_scroll_margin(5, cx));
editor editor
}); });
let editor_event_subscription = cx.subscribe(&editor, |this, _, event, cx| { let editor_event_subscription = cx.subscribe(&editor, |this, _, event, cx| {
@ -527,21 +531,25 @@ impl ProjectDiagnosticsEditor {
}); });
self.editor.update(cx, |editor, cx| { self.editor.update(cx, |editor, cx| {
editor.remove_blocks(blocks_to_remove, None, cx); editor.0.update(cx, |this, cx| {
let block_ids = editor.insert_blocks( this.remove_blocks(blocks_to_remove, None, cx)
blocks_to_add.into_iter().map(|block| { });
let (excerpt_id, text_anchor) = block.position; let block_ids = editor.0.update(cx, |this, cx| {
BlockProperties { this.insert_blocks(
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor), blocks_to_add.into_iter().map(|block| {
height: block.height, let (excerpt_id, text_anchor) = block.position;
style: block.style, BlockProperties {
render: block.render, position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
disposition: block.disposition, height: block.height,
} style: block.style,
}), render: block.render,
Some(Autoscroll::fit()), disposition: block.disposition,
cx, }
); }),
Some(Autoscroll::fit()),
cx,
)
});
let mut block_ids = block_ids.into_iter(); let mut block_ids = block_ids.into_iter();
for group_state in &mut groups_to_add { for group_state in &mut groups_to_add {
@ -582,13 +590,14 @@ impl ProjectDiagnosticsEditor {
}]; }];
} else { } else {
groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice(); groups = self.path_states.get(path_ix)?.diagnostic_groups.as_slice();
new_excerpt_ids_by_selection_id = new_excerpt_ids_by_selection_id = editor.0.update(cx, |this, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.refresh()); this.change_selections(Some(Autoscroll::fit()), cx, |s| s.refresh())
selections = editor.selections.all::<usize>(cx); });
selections = editor.0.read(cx).selections.all::<usize>(cx);
} }
// If any selection has lost its position, move it to start of the next primary diagnostic. // If any selection has lost its position, move it to start of the next primary diagnostic.
let snapshot = editor.snapshot(cx); let snapshot = editor.0.update(cx, |this, cx| this.snapshot(cx));
for selection in &mut selections { for selection in &mut selections {
if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) { if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) {
let group_ix = match groups.binary_search_by(|probe| { let group_ix = match groups.binary_search_by(|probe| {
@ -612,8 +621,10 @@ impl ProjectDiagnosticsEditor {
} }
} }
} }
editor.change_selections(None, cx, |s| { editor.0.update(cx, |this, cx| {
s.select(selections); this.change_selections(None, cx, |s| {
s.select(selections);
})
}); });
Some(()) Some(())
}); });
@ -703,12 +714,12 @@ impl Item for ProjectDiagnosticsEditor {
} }
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
Editor::to_item_events(event) FollowableEditor::to_item_events(event)
} }
fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) { fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
self.editor.update(cx, |editor, _| { self.editor.update(cx, |editor, cx| {
editor.set_nav_history(Some(nav_history)); editor.set_nav_history(nav_history, cx);
}); });
} }
@ -751,7 +762,7 @@ impl Item for ProjectDiagnosticsEditor {
} }
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> { fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
self.editor.breadcrumbs(theme, cx) self.editor.read(cx).breadcrumbs(theme, cx)
} }
fn breadcrumb_location(&self) -> ToolbarItemLocation { fn breadcrumb_location(&self) -> ToolbarItemLocation {

View file

@ -30,13 +30,14 @@ db = { path = "../db" }
drag_and_drop = { path = "../drag_and_drop" } drag_and_drop = { path = "../drag_and_drop" }
collections = { path = "../collections" } collections = { path = "../collections" }
context_menu = { path = "../context_menu" } context_menu = { path = "../context_menu" }
workspace_types = {path = "../workspace_types"}
project_types = {path = "../project_types"}
fuzzy = { path = "../fuzzy" } fuzzy = { path = "../fuzzy" }
git = { path = "../git" } git = { path = "../git" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
language = { path = "../language" } language = { path = "../language" }
lsp = { path = "../lsp" } lsp = { path = "../lsp" }
multi_buffer = { path = "../multi_buffer" } multi_buffer = { path = "../multi_buffer" }
project = { path = "../project" }
rpc = { path = "../rpc" } rpc = { path = "../rpc" }
rich_text = { path = "../rich_text" } rich_text = { path = "../rich_text" }
settings = { path = "../settings" } settings = { path = "../settings" }
@ -46,9 +47,9 @@ text = { path = "../text" }
theme = { path = "../theme" } theme = { path = "../theme" }
util = { path = "../util" } util = { path = "../util" }
sqlez = { path = "../sqlez" } sqlez = { path = "../sqlez" }
workspace = { path = "../workspace" }
aho-corasick = "1.1" aho-corasick = "1.1"
async-trait.workspace = true
anyhow.workspace = true anyhow.workspace = true
convert_case = "0.6.0" convert_case = "0.6.0"
futures.workspace = true futures.workspace = true

View file

@ -42,7 +42,7 @@ pub struct Inlay {
} }
impl Inlay { impl Inlay {
pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self { pub fn hint(id: usize, position: Anchor, hint: &project_types::InlayHint) -> Self {
let mut text = hint.text(); let mut text = hint.text();
if hint.padding_right && !text.ends_with(' ') { if hint.padding_right && !text.ends_with(' ') {
text.push(' '); text.push(' ');
@ -1172,7 +1172,7 @@ mod tests {
InlayId, MultiBuffer, InlayId, MultiBuffer,
}; };
use gpui::AppContext; use gpui::AppContext;
use project::{InlayHint, InlayHintLabel, ResolveState}; use project_types::{InlayHint, InlayHintLabel, ResolveState};
use rand::prelude::*; use rand::prelude::*;
use settings::SettingsStore; use settings::SettingsStore;
use std::{cmp::Reverse, env, sync::Arc}; use std::{cmp::Reverse, env, sync::Arc};

File diff suppressed because it is too large Load diff

View file

@ -39,9 +39,8 @@ use json::json;
use language::{ use language::{
language_settings::ShowWhitespaceSetting, Bias, CursorShape, OffsetUtf16, Selection, language_settings::ShowWhitespaceSetting, Bias, CursorShape, OffsetUtf16, Selection,
}; };
use project::{ use project_types::{
project_settings::{GitGutterSetting, ProjectSettings}, project_settings::{GitGutterSetting, ProjectSettings},
ProjectPath,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
@ -54,7 +53,6 @@ use std::{
}; };
use text::Point; use text::Point;
use theme::SelectionStyle; use theme::SelectionStyle;
use workspace::item::Item;
enum FoldMarkers {} enum FoldMarkers {}
@ -1159,7 +1157,7 @@ impl EditorElement {
}) })
}; };
let background_ranges = editor let background_ranges = editor
.background_highlight_row_ranges::<crate::items::BufferSearchHighlights>( .background_highlight_row_ranges::<crate::BufferSearchHighlights>(
start_anchor..end_anchor, start_anchor..end_anchor,
&layout.position_map.snapshot, &layout.position_map.snapshot,
50000, 50000,
@ -1662,61 +1660,65 @@ impl EditorElement {
let include_root = editor let include_root = editor
.project .project
.as_ref() .as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1) .map(|project| project.visible_worktrees_count(cx) > 1)
.unwrap_or_default(); .unwrap_or_default();
let jump_icon = project::File::from_dyn(buffer.file()).map(|file| { //
let jump_path = ProjectPath { let jump_icon =
worktree_id: file.worktree_id(cx), editor
path: file.path.clone(), .project
};
let jump_anchor = range
.primary
.as_ref() .as_ref()
.map_or(range.context.start, |primary| primary.start); .zip(buffer.file())
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); .map(|(project, buffer_file)| {
let jump_path = project.project_file(&**buffer_file);
//::from_dyn(buffer.file())
enum JumpIcon {} let jump_anchor = range
MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| { .primary
let style = style.jump_icon.style_for(state); .as_ref()
Svg::new("icons/arrow_up_right.svg") .map_or(range.context.start, |primary| primary.start);
.with_color(style.color) let jump_position =
.constrained() language::ToPoint::to_point(&jump_anchor, buffer);
.with_width(style.icon_width)
enum JumpIcon {}
MouseEventHandler::new::<JumpIcon, _>(
(*id).into(),
cx,
|state, _| {
let style = style.jump_icon.style_for(state);
Svg::new("icons/arrow_up_right.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.contained()
.with_style(style.container)
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
},
)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, editor, cx| {
if let Some((workspace, _)) = editor.workspace.as_mut() {
Editor::jump(
&**workspace,
jump_path.clone(),
jump_position,
jump_anchor,
cx,
);
}
})
.with_tooltip::<JumpIcon>(
(*id).into(),
"Jump to Buffer".to_string(),
Some(Box::new(crate::OpenExcerpts)),
tooltip_style.clone(),
cx,
)
.aligned() .aligned()
.contained() .flex_float()
.with_style(style.container) });
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, editor, cx| {
if let Some(workspace) = editor
.workspace
.as_ref()
.and_then(|(workspace, _)| workspace.upgrade(cx))
{
workspace.update(cx, |workspace, cx| {
Editor::jump(
workspace,
jump_path.clone(),
jump_position,
jump_anchor,
cx,
);
});
}
})
.with_tooltip::<JumpIcon>(
(*id).into(),
"Jump to Buffer".to_string(),
Some(Box::new(crate::OpenExcerpts)),
tooltip_style.clone(),
cx,
)
.aligned()
.flex_float()
});
if *starts_new_buffer { if *starts_new_buffer {
let editor_font_size = style.text.font_size; let editor_font_size = style.text.font_size;

View file

@ -1,23 +1,23 @@
use crate::{ use crate::{
display_map::{InlayOffset, ToDisplayPoint}, display_map::{InlayOffset, ToDisplayPoint},
link_go_to_definition::{InlayHighlight, RangeInEditor}, link_go_to_definition::{InlayHighlight, RangeInEditor},
types::Workspace,
Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle,
ExcerptId, RangeToAnchorExt, ExcerptId, Project, RangeToAnchorExt,
}; };
use futures::FutureExt; use futures::FutureExt;
use gpui::{ use gpui::{
actions, actions,
elements::{Flex, MouseEventHandler, Padding, ParentElement, Text}, elements::{Flex, MouseEventHandler, Padding, ParentElement, Text},
platform::{CursorStyle, MouseButton}, platform::{CursorStyle, MouseButton},
AnyElement, AppContext, Element, ModelHandle, Task, ViewContext, WeakViewHandle, AnyElement, AppContext, Element, Task, ViewContext,
}; };
use language::{ use language::{
markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown, markdown, Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry, ParsedMarkdown,
}; };
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use project_types::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
use std::{ops::Range, sync::Arc, time::Duration}; use std::{ops::Range, sync::Arc, time::Duration};
use util::TryFutureExt; use util::TryFutureExt;
use workspace::Workspace;
pub const HOVER_DELAY_MILLIS: u64 = 350; pub const HOVER_DELAY_MILLIS: u64 = 350;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
@ -103,11 +103,11 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
cx.background() cx.background()
.timer(Duration::from_millis(HOVER_DELAY_MILLIS)) .timer(Duration::from_millis(HOVER_DELAY_MILLIS))
.await; .await;
this.update(&mut cx, |this, _| { let language_registry = this.update(&mut cx, |this, cx| {
this.hover_state.diagnostic_popover = None; this.hover_state.diagnostic_popover = None;
project.languages(cx)
})?; })?;
let language_registry = project.update(&mut cx, |p, _| p.languages().clone());
let blocks = vec![inlay_hover.tooltip]; let blocks = vec![inlay_hover.tooltip];
let parsed_content = parse_blocks(&blocks, &language_registry, None).await; let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
@ -252,11 +252,7 @@ fn show_hover(
}; };
// query the LSP for hover info // query the LSP for hover info
let hover_request = cx.update(|cx| { let hover_request = cx.update(|cx| project.hover(&buffer, buffer_position, cx));
project.update(cx, |project, cx| {
project.hover(&buffer, buffer_position, cx)
})
});
if let Some(delay) = delay { if let Some(delay) = delay {
delay.await; delay.await;
@ -310,7 +306,7 @@ fn show_hover(
anchor..anchor anchor..anchor
}; };
let language_registry = project.update(&mut cx, |p, _| p.languages().clone()); let language_registry = cx.update(|cx| project.languages(cx));
let blocks = hover_result.contents; let blocks = hover_result.contents;
let language = hover_result.language; let language = hover_result.language;
let parsed_content = parse_blocks(&blocks, &language_registry, language).await; let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
@ -423,7 +419,7 @@ impl HoverState {
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
style: &EditorStyle, style: &EditorStyle,
visible_rows: Range<u32>, visible_rows: Range<u32>,
workspace: Option<WeakViewHandle<Workspace>>, workspace: Option<Arc<dyn Workspace>>,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> { ) -> Option<(DisplayPoint, Vec<AnyElement<Editor>>)> {
// If there is a diagnostic, position the popovers based on that. // If there is a diagnostic, position the popovers based on that.
@ -462,7 +458,7 @@ impl HoverState {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct InfoPopover { pub struct InfoPopover {
pub project: ModelHandle<Project>, pub project: Arc<dyn Project>,
symbol_range: RangeInEditor, symbol_range: RangeInEditor,
pub blocks: Vec<HoverBlock>, pub blocks: Vec<HoverBlock>,
parsed_content: ParsedMarkdown, parsed_content: ParsedMarkdown,
@ -472,7 +468,7 @@ impl InfoPopover {
pub fn render( pub fn render(
&mut self, &mut self,
style: &EditorStyle, style: &EditorStyle,
workspace: Option<WeakViewHandle<Workspace>>, workspace: Option<Arc<dyn Workspace>>,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> AnyElement<Editor> { ) -> AnyElement<Editor> {
MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| { MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {

View file

@ -14,7 +14,7 @@ use futures::future;
use gpui::{ModelContext, ModelHandle, Task, ViewContext}; use gpui::{ModelContext, ModelHandle, Task, ViewContext};
use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
use parking_lot::RwLock; use parking_lot::RwLock;
use project::{InlayHint, ResolveState}; use project_types::{InlayHint, ResolveState};
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use language::language_settings::InlayHintSettings; use language::language_settings::InlayHintSettings;
@ -539,14 +539,12 @@ impl InlayHintCache {
.buffer(buffer_id) .buffer(buffer_id)
.and_then(|buffer| { .and_then(|buffer| {
let project = editor.project.as_ref()?; let project = editor.project.as_ref()?;
Some(project.update(cx, |project, cx| { Some(project.resolve_inlay_hint(
project.resolve_inlay_hint( hint_to_resolve,
hint_to_resolve, buffer,
buffer, server_id,
server_id, cx,
cx, ))
)
}))
}) })
})?; })?;
if let Some(resolved_hint_task) = resolved_hint_task { if let Some(resolved_hint_task) = resolved_hint_task {
@ -896,9 +894,9 @@ async fn fetch_and_update_hints(
.buffer(query.buffer_id) .buffer(query.buffer_id)
.and_then(|buffer| { .and_then(|buffer| {
let project = editor.project.as_ref()?; let project = editor.project.as_ref()?;
Some(project.update(cx, |project, cx| { Some(
project.inlay_hints(buffer, fetch_range.clone(), cx) project.inlay_hints(buffer, fetch_range.clone(), cx)
})) )
}) })
}) })
.ok() .ok()

View file

@ -7,7 +7,7 @@ use crate::{
use gpui::{Task, ViewContext}; use gpui::{Task, ViewContext};
use language::{Bias, ToOffset}; use language::{Bias, ToOffset};
use lsp::LanguageServerId; use lsp::LanguageServerId;
use project::{ use project_types::{
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
ResolveState, ResolveState,
}; };
@ -227,7 +227,7 @@ pub fn update_inlay_link_and_hover_points(
extra_shift_right += 1; extra_shift_right += 1;
} }
match cached_hint.label { match cached_hint.label {
project::InlayHintLabel::String(_) => { project_types::InlayHintLabel::String(_) => {
if let Some(tooltip) = cached_hint.tooltip { if let Some(tooltip) = cached_hint.tooltip {
hover_popover::hover_at_inlay( hover_popover::hover_at_inlay(
editor, editor,
@ -257,7 +257,7 @@ pub fn update_inlay_link_and_hover_points(
hover_updated = true; hover_updated = true;
} }
} }
project::InlayHintLabel::LabelParts(label_parts) => { project_types::InlayHintLabel::LabelParts(label_parts) => {
let hint_start = let hint_start =
snapshot.anchor_to_inlay_offset(hovered_hint.position); snapshot.anchor_to_inlay_offset(hovered_hint.position);
if let Some((hovered_hint_part, part_range)) = if let Some((hovered_hint_part, part_range)) =
@ -396,16 +396,14 @@ pub fn show_link_definition(
let result = match &trigger_point { let result = match &trigger_point {
TriggerPoint::Text(_) => { TriggerPoint::Text(_) => {
// query the LSP for definition info // query the LSP for definition info
cx.update(|cx| { cx.update(|cx| match definition_kind {
project.update(cx, |project, cx| match definition_kind { LinkDefinitionKind::Symbol => {
LinkDefinitionKind::Symbol => { project.definition(&buffer, buffer_position, cx)
project.definition(&buffer, buffer_position, cx) }
}
LinkDefinitionKind::Type => { LinkDefinitionKind::Type => {
project.type_definition(&buffer, buffer_position, cx) project.type_definition(&buffer, buffer_position, cx)
} }
})
}) })
.await .await
.ok() .ok()

View file

@ -4,6 +4,7 @@ pub mod scroll_amount;
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -13,14 +14,13 @@ use gpui::{
}; };
use language::{Bias, Point}; use language::{Bias, Point};
use util::ResultExt; use util::ResultExt;
use workspace::WorkspaceId; use workspace_types::WorkspaceId;
use crate::{ use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint}, display_map::{DisplaySnapshot, ToDisplayPoint},
hover_popover::hide_hover, hover_popover::hide_hover,
persistence::DB, types, Anchor, DisplayPoint, Editor, EditorMode, Event, InlayHintRefreshReason,
Anchor, DisplayPoint, Editor, EditorMode, Event, InlayHintRefreshReason, MultiBufferSnapshot, MultiBufferSnapshot, ToPoint,
ToPoint,
}; };
use self::{ use self::{
@ -176,7 +176,7 @@ impl ScrollManager {
map: &DisplaySnapshot, map: &DisplaySnapshot,
local: bool, local: bool,
autoscroll: bool, autoscroll: bool,
workspace_id: Option<i64>, workspace_id: Option<&(Arc<dyn crate::types::Workspace>, i64)>,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
let (new_anchor, top_row) = if scroll_position.y() <= 0. { let (new_anchor, top_row) = if scroll_position.y() <= 0. {
@ -215,19 +215,20 @@ impl ScrollManager {
top_row: u32, top_row: u32,
local: bool, local: bool,
autoscroll: bool, autoscroll: bool,
workspace_id: Option<i64>, workspace_id: Option<&(Arc<dyn crate::types::Workspace>, i64)>,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
self.anchor = anchor; self.anchor = anchor;
cx.emit(Event::ScrollPositionChanged { local, autoscroll }); cx.emit(Event::ScrollPositionChanged { local, autoscroll });
self.show_scrollbar(cx); self.show_scrollbar(cx);
self.autoscroll_request.take(); self.autoscroll_request.take();
if let Some(workspace_id) = workspace_id { if let Some((workspace, workspace_id)) = workspace_id {
let item_id = cx.view_id(); let item_id = cx.view_id();
let db = workspace.db();
let workspace_id = *workspace_id;
cx.background() cx.background()
.spawn(async move { .spawn(async move {
DB.save_scroll_position( db.save_scroll_position(
item_id, item_id,
workspace_id, workspace_id,
top_row, top_row,
@ -324,7 +325,7 @@ impl Editor {
let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
hide_hover(self, cx); hide_hover(self, cx);
let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); let workspace_id = self.workspace.as_ref();
self.scroll_manager.set_scroll_position( self.scroll_manager.set_scroll_position(
scroll_position, scroll_position,
&map, &map,
@ -344,7 +345,7 @@ impl Editor {
pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) { pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
hide_hover(self, cx); hide_hover(self, cx);
let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); let workspace_id = self.workspace.as_ref();
let top_row = scroll_anchor let top_row = scroll_anchor
.anchor .anchor
.to_point(&self.buffer().read(cx).snapshot(cx)) .to_point(&self.buffer().read(cx).snapshot(cx))
@ -353,13 +354,13 @@ impl Editor {
.set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx); .set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx);
} }
pub(crate) fn set_scroll_anchor_remote( pub fn set_scroll_anchor_remote(
&mut self, &mut self,
scroll_anchor: ScrollAnchor, scroll_anchor: ScrollAnchor,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
hide_hover(self, cx); hide_hover(self, cx);
let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); let workspace_id = self.workspace.as_ref();
let top_row = scroll_anchor let top_row = scroll_anchor
.anchor .anchor
.to_point(&self.buffer().read(cx).snapshot(cx)) .to_point(&self.buffer().read(cx).snapshot(cx))
@ -415,11 +416,12 @@ impl Editor {
pub fn read_scroll_position_from_db( pub fn read_scroll_position_from_db(
&mut self, &mut self,
db: &dyn types::Db,
item_id: usize, item_id: usize,
workspace_id: WorkspaceId, workspace_id: WorkspaceId,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) { ) {
let scroll_position = DB.get_scroll_position(item_id, workspace_id); let scroll_position = db.get_scroll_position(item_id, workspace_id);
if let Ok(Some((top_row, x, y))) = scroll_position { if let Ok(Some((top_row, x, y))) = scroll_position {
let top_anchor = self let top_anchor = self
.buffer() .buffer()

View file

@ -247,7 +247,7 @@ impl Editor {
cx.notify(); cx.notify();
} }
pub(crate) fn request_autoscroll_remotely( pub fn request_autoscroll_remotely(
&mut self, &mut self,
autoscroll: Autoscroll, autoscroll: Autoscroll,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,

229
crates/editor/src/types.rs Normal file
View file

@ -0,0 +1,229 @@
use crate::Editor;
use client::Client;
use anyhow::Result;
use client::{proto::PeerId, Collaborator, ParticipantIndex};
use collections::{HashMap, HashSet};
use gpui::geometry::vector::Vector2F;
use gpui::WeakViewHandle;
use gpui::{AppContext, ModelHandle, Subscription, Task, ViewContext, ViewHandle};
use language::{
Buffer, CachedLspAdapter, CodeAction, Completion, LanguageRegistry, LanguageServerName,
};
use lsp::{LanguageServer, LanguageServerId};
use project_types::ProjectPath;
use project_types::{FormatTrigger, ProjectTransaction};
use std::ops::Range;
use std::path::PathBuf;
use std::sync::Arc;
use workspace_types::{ItemId, SplitDirection, WorkspaceId};
#[async_trait::async_trait]
pub trait Db: 'static + Send + Sync {
async fn save_scroll_position(
&self,
item_id: ItemId,
workspace_id: WorkspaceId,
top_row: u32,
vertical_offset: f32,
horizontal_offset: f32,
) -> Result<()>;
fn get_scroll_position(
&self,
item_id: ItemId,
workspace_id: WorkspaceId,
) -> Result<Option<(u32, f32, f32)>>;
async fn save_path(
&self,
item_id: ItemId,
workspace_id: WorkspaceId,
path: PathBuf,
) -> Result<()>;
}
pub type DisableUpdateHistoryGuard = Box<dyn DisableUpdateHistory>;
pub trait DisableUpdateHistory {
fn release(self, cx: &mut AppContext);
}
pub trait CollaborationHub {
fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
fn user_participant_indices<'a>(
&self,
cx: &'a AppContext,
) -> &'a HashMap<u64, ParticipantIndex>;
}
pub trait Workspace: 'static {
fn db(&self) -> Arc<dyn Db>;
fn open_abs_path(&self, abs_path: PathBuf, visible: bool, cx: &mut AppContext);
fn open_path(
&self,
path: ProjectPath,
focus_item: bool,
cx: &mut AppContext,
) -> Task<Result<ViewHandle<Editor>>>;
fn active_editor(&self, cx: &mut AppContext) -> Option<ViewHandle<Editor>>;
fn project(&self, cx: &mut AppContext) -> Arc<dyn Project>;
fn disable_update_history_for_current_pane(
&self,
cx: &mut AppContext,
) -> Option<DisableUpdateHistoryGuard>;
fn split_buffer(
&self,
buffer: ModelHandle<Buffer>,
cx: &mut AppContext,
) -> ViewHandle<crate::Editor>;
fn open_buffer(
&self,
buffer: ModelHandle<Buffer>,
cx: &mut AppContext,
) -> ViewHandle<crate::Editor>;
fn add_item(&self, item: Box<ViewHandle<Editor>>, cx: &mut AppContext);
fn split_item(
&self,
split_direction: SplitDirection,
item: Box<ViewHandle<Editor>>,
cx: &mut AppContext,
);
}
pub trait Project: 'static + std::fmt::Debug {
fn apply_code_action(
&self,
buffer_handle: ModelHandle<Buffer>,
action: CodeAction,
push_to_history: bool,
cx: &mut AppContext,
) -> Task<Result<ProjectTransaction>>;
fn inlay_hints(
&self,
buffer_handle: ModelHandle<Buffer>,
range: Range<text::Anchor>,
cx: &mut AppContext,
) -> Task<anyhow::Result<Vec<project_types::InlayHint>>>;
fn visible_worktrees_count(&self, cx: &AppContext) -> usize;
fn resolve_inlay_hint(
&self,
hint: project_types::InlayHint,
buffer_handle: ModelHandle<Buffer>,
server_id: LanguageServerId,
cx: &mut AppContext,
) -> Task<anyhow::Result<project_types::InlayHint>>;
fn languages(&self, cx: &AppContext) -> Arc<LanguageRegistry>;
fn hover(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Option<project_types::Hover>>>;
fn definition(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<project_types::LocationLink>>>;
fn type_definition(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<project_types::LocationLink>>>;
fn completions(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<Completion>>>;
fn as_hub(&self) -> Box<dyn CollaborationHub>;
fn is_remote(&self, cx: &AppContext) -> bool;
fn is_local(&self, cx: &AppContext) -> bool {
!self.is_remote(cx)
}
fn remote_id(&self, cx: &AppContext) -> Option<u64>;
fn language_servers_for_buffer(
&self,
buffer: &Buffer,
cx: &AppContext,
) -> Vec<(Arc<CachedLspAdapter>, Arc<LanguageServer>)>;
fn on_type_format(
&self,
buffer: ModelHandle<Buffer>,
position: text::Anchor,
trigger: String,
push_to_history: bool,
cx: &mut AppContext,
) -> Task<Result<Option<text::Transaction>>>;
fn client(&self, cx: &AppContext) -> Arc<Client>;
fn language_server_for_id(
&self,
id: LanguageServerId,
cx: &AppContext,
) -> Option<Arc<LanguageServer>>;
fn code_actions(
&self,
buffer_handle: &ModelHandle<Buffer>,
range: Range<text::Anchor>,
cx: &mut AppContext,
) -> Task<Result<Vec<CodeAction>>>;
fn document_highlights(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<project_types::DocumentHighlight>>>;
fn format(
&self,
buffers: HashSet<ModelHandle<Buffer>>,
push_to_history: bool,
trigger: FormatTrigger,
cx: &mut AppContext,
) -> Task<anyhow::Result<ProjectTransaction>>;
fn restart_language_servers_for_buffers(
&self,
buffers: HashSet<ModelHandle<Buffer>>,
cx: &mut AppContext,
) -> Option<()>;
fn prepare_rename(
&self,
buffer: ModelHandle<Buffer>,
position: usize,
cx: &mut AppContext,
) -> Task<Result<Option<Range<text::Anchor>>>>;
fn references(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<project_types::Location>>>;
fn apply_additional_edits_for_completion(
&self,
buffer_handle: ModelHandle<Buffer>,
completion: Completion,
push_to_history: bool,
cx: &mut AppContext,
) -> Task<Result<Option<text::Transaction>>>;
fn language_server_for_buffer<'a>(
&self,
buffer: &Buffer,
server_id: LanguageServerId,
cx: &'a AppContext,
) -> Option<(&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)>;
fn open_local_buffer_via_lsp(
&self,
abs_path: lsp::Url,
language_server_id: LanguageServerId,
language_server_name: LanguageServerName,
cx: &mut AppContext,
) -> Task<Result<ModelHandle<Buffer>>>;
fn subscribe(
&self,
is_singleton: bool,
cx: &mut ViewContext<crate::Editor>,
) -> Vec<Subscription>;
fn project_file(&self, file: &dyn language::File) -> ProjectPath;
}

View file

@ -0,0 +1,27 @@
[package]
name = "editor_extensions"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
collections = {path = "../collections"}
client = {path = "../client"}
db = {path = "../db"}
editor = {path = "../editor"}
gpui = {path = "../gpui"}
lsp = {path = "../lsp"}
language = {path = "../language"}
project = {path = "../project"}
project_types = {path = "../project_types"}
text = {path = "../text"}
workspace = {path = "../workspace"}
workspace_types = {path = "../workspace_types"}
util = {path = "../util"}
theme = {path = "../theme"}
smallvec.workspace = true
rpc = {path = "../rpc"}
futures.workspace = true
async-trait.workspace = true

View file

@ -1,10 +1,13 @@
use crate::{ use crate::persistence::DB;
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition, use crate::{FollowableEditor, ProjectHandle, WeakWorkspaceHandle};
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, use anyhow::{anyhow, bail, Context, Result};
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
};
use anyhow::{Context, Result};
use collections::HashSet; use collections::HashSet;
use editor::{
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition,
movement::surrounding_word, scroll::ScrollAnchor, Anchor, Autoscroll, Editor, Event, ExcerptId,
ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
};
use editor::{BufferSearchHighlights, MAX_TAB_TITLE_LEN};
use futures::future::try_join_all; use futures::future::try_join_all;
use gpui::{ use gpui::{
elements::*, elements::*,
@ -16,7 +19,8 @@ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point, proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
SelectionGoal, SelectionGoal,
}; };
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use project::{search::SearchQuery, Item as _, Project};
use project_types::{FormatTrigger, ProjectPath};
use rpc::proto::{self, update_view, PeerId}; use rpc::proto::{self, update_view, PeerId};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
@ -37,15 +41,14 @@ use workspace::item::{BreadcrumbText, FollowableItemHandle, ItemHandle};
use workspace::{ use workspace::{
item::{FollowableItem, Item, ItemEvent, ProjectItem}, item::{FollowableItem, Item, ItemEvent, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
ItemId, ItemNavHistory, Pane, StatusItemView, ToolbarItemLocation, ViewId, Workspace, ItemNavHistory, Pane, StatusItemView, ToolbarItemLocation, Workspace,
WorkspaceId,
}; };
use workspace_types::*;
pub const MAX_TAB_TITLE_LEN: usize = 24; impl FollowableItem for FollowableEditor {
impl FollowableItem for Editor {
fn remote_id(&self) -> Option<ViewId> { fn remote_id(&self) -> Option<ViewId> {
self.remote_id todo!();
//self.0.remote_id
} }
fn from_state_proto( fn from_state_proto(
@ -86,13 +89,20 @@ impl FollowableItem for Editor {
let ids_match = editor.remote_id(&client, cx) == Some(remote_id); let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
let singleton_buffer_matches = state.singleton let singleton_buffer_matches = state.singleton
&& buffers.first() && buffers.first()
== editor.read(cx).buffer.read(cx).as_singleton().as_ref(); == editor
.read(cx)
.0
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.as_ref();
ids_match || singleton_buffer_matches ids_match || singleton_buffer_matches
}) })
})?; })?;
let editor = if let Some(editor) = editor { let editor = if let Some(editor) = editor {
editor editor.update(&mut cx, |this, _| this.0.clone())
} else { } else {
pane.update(&mut cx, |_, cx| { pane.update(&mut cx, |_, cx| {
let multibuffer = cx.add_model(|cx| { let multibuffer = cx.add_model(|cx| {
@ -129,12 +139,20 @@ impl FollowableItem for Editor {
}); });
cx.add_view(|cx| { cx.add_view(|cx| {
let mut editor = let mut editor = Editor::for_multibuffer(
Editor::for_multibuffer(multibuffer, Some(project.clone()), cx); multibuffer,
Some(Arc::new(ProjectHandle(project.clone()))),
cx,
);
editor.remote_id = Some(remote_id); editor.remote_id = Some(remote_id);
editor editor
}) })
})? })
.ok()
};
let Some(editor) = editor else {
bail!("Failed to initialize editor")
}; };
update_editor_from_message( update_editor_from_message(
@ -152,34 +170,37 @@ impl FollowableItem for Editor {
) )
.await?; .await?;
Ok(editor) pane.update(&mut cx, |_, cx| cx.add_view(|_| FollowableEditor(editor)))
})) }))
} }
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) { fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
self.leader_peer_id = leader_peer_id; self.0.update(cx, |this, cx| {
if self.leader_peer_id.is_some() { this.leader_peer_id = leader_peer_id;
self.buffer.update(cx, |buffer, cx| { if this.leader_peer_id.is_some() {
buffer.remove_active_selections(cx); this.buffer().update(cx, |buffer, cx| {
}); buffer.remove_active_selections(cx);
} else { });
self.buffer.update(cx, |buffer, cx| { } else {
if self.focused { this.buffer().update(cx, |buffer, cx| {
buffer.set_active_selections( if this.focused {
&self.selections.disjoint_anchors(), buffer.set_active_selections(
self.selections.line_mode, &this.selections.disjoint_anchors(),
self.cursor_shape, this.selections.line_mode,
cx, this.cursor_shape,
); cx,
} );
}); }
} });
cx.notify(); }
cx.notify();
});
} }
fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> { fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
let buffer = self.buffer.read(cx); let this = self.0.read(cx);
let scroll_anchor = self.scroll_manager.anchor(); let buffer = this.buffer().read(cx);
let scroll_anchor = this.scroll_manager.anchor();
let excerpts = buffer let excerpts = buffer
.read(cx) .read(cx)
.excerpts() .excerpts()
@ -206,13 +227,13 @@ impl FollowableItem for Editor {
scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor)), scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor)),
scroll_x: scroll_anchor.offset.x(), scroll_x: scroll_anchor.offset.x(),
scroll_y: scroll_anchor.offset.y(), scroll_y: scroll_anchor.offset.y(),
selections: self selections: this
.selections .selections
.disjoint_anchors() .disjoint_anchors()
.iter() .iter()
.map(serialize_selection) .map(serialize_selection)
.collect(), .collect(),
pending_selection: self pending_selection: this
.selections .selections
.pending_anchor() .pending_anchor()
.as_ref() .as_ref()
@ -228,7 +249,7 @@ impl FollowableItem for Editor {
) -> bool { ) -> bool {
let update = let update =
update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default())); update.get_or_insert_with(|| proto::update_view::Variant::Editor(Default::default()));
let this = self.0.read(cx);
match update { match update {
proto::update_view::Variant::Editor(update) => match event { proto::update_view::Variant::Editor(update) => match event {
Event::ExcerptsAdded { Event::ExcerptsAdded {
@ -259,20 +280,20 @@ impl FollowableItem for Editor {
true true
} }
Event::ScrollPositionChanged { .. } => { Event::ScrollPositionChanged { .. } => {
let scroll_anchor = self.scroll_manager.anchor(); let scroll_anchor = this.scroll_manager.anchor();
update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor)); update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor));
update.scroll_x = scroll_anchor.offset.x(); update.scroll_x = scroll_anchor.offset.x();
update.scroll_y = scroll_anchor.offset.y(); update.scroll_y = scroll_anchor.offset.y();
true true
} }
Event::SelectionsChanged { .. } => { Event::SelectionsChanged { .. } => {
update.selections = self update.selections = this
.selections .selections
.disjoint_anchors() .disjoint_anchors()
.iter() .iter()
.map(serialize_selection) .map(serialize_selection)
.collect(); .collect();
update.pending_selection = self update.pending_selection = this
.selections .selections
.pending_anchor() .pending_anchor()
.as_ref() .as_ref()
@ -292,8 +313,10 @@ impl FollowableItem for Editor {
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let update_view::Variant::Editor(message) = message; let update_view::Variant::Editor(message) = message;
let project = project.clone(); let project = project.clone();
cx.spawn(|this, mut cx| async move { self.0.update(cx, |this, cx| {
update_editor_from_message(this, project, message, &mut cx).await cx.spawn(|this, mut cx| async move {
update_editor_from_message(this, project, message, &mut cx).await
})
}) })
} }
@ -333,7 +356,7 @@ async fn update_editor_from_message(
// Update the editor's excerpts. // Update the editor's excerpts.
this.update(cx, |editor, cx| { this.update(cx, |editor, cx| {
editor.buffer.update(cx, |multibuffer, cx| { editor.buffer().update(cx, |multibuffer, cx| {
let mut removed_excerpt_ids = message let mut removed_excerpt_ids = message
.deleted_excerpts .deleted_excerpts
.into_iter() .into_iter()
@ -390,7 +413,7 @@ async fn update_editor_from_message(
// Deserialize the editor state. // Deserialize the editor state.
let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| { let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
let buffer = editor.buffer.read(cx).read(cx); let buffer = editor.buffer().read(cx).read(cx);
let selections = message let selections = message
.selections .selections
.into_iter() .into_iter()
@ -408,7 +431,7 @@ async fn update_editor_from_message(
// Wait until the buffer has received all of the operations referenced by // Wait until the buffer has received all of the operations referenced by
// the editor's new state. // the editor's new state.
this.update(cx, |editor, cx| { this.update(cx, |editor, cx| {
editor.buffer.update(cx, |buffer, cx| { editor.buffer().update(cx, |buffer, cx| {
buffer.wait_for_anchors( buffer.wait_for_anchors(
selections selections
.iter() .iter()
@ -515,11 +538,12 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
}) })
} }
impl Item for Editor { impl Item for FollowableEditor {
fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool { fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
if let Ok(data) = data.downcast::<NavigationData>() { if let Ok(data) = data.downcast::<NavigationData>() {
let newest_selection = self.selections.newest::<Point>(cx); let this = self.0.read(cx);
let buffer = self.buffer.read(cx).read(cx); let newest_selection = this.selections.newest::<Point>(cx);
let buffer = this.buffer().read(cx).read(cx);
let offset = if buffer.can_resolve(&data.cursor_anchor) { let offset = if buffer.can_resolve(&data.cursor_anchor) {
data.cursor_anchor.to_point(&buffer) data.cursor_anchor.to_point(&buffer)
} else { } else {
@ -538,12 +562,15 @@ impl Item for Editor {
if newest_selection.head() == offset { if newest_selection.head() == offset {
false false
} else { } else {
let nav_history = self.nav_history.take(); self.0.update(cx, |this, cx| {
self.set_scroll_anchor(scroll_anchor, cx); let nav_history = this.nav_history.take();
self.change_selections(Some(Autoscroll::fit()), cx, |s| { this.set_scroll_anchor(scroll_anchor, cx);
s.select_ranges([offset..offset]) this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([offset..offset])
});
this.nav_history = nav_history;
}); });
self.nav_history = nav_history;
true true
} }
} else { } else {
@ -553,6 +580,8 @@ impl Item for Editor {
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> { fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
let file_path = self let file_path = self
.0
.read(cx)
.buffer() .buffer()
.read(cx) .read(cx)
.as_singleton()? .as_singleton()?
@ -567,7 +596,7 @@ impl Item for Editor {
} }
fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<str>> { fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<str>> {
match path_for_buffer(&self.buffer, detail, true, cx)? { match path_for_buffer(&self.0.read(cx).buffer(), detail, true, cx)? {
Cow::Borrowed(path) => Some(path.to_string_lossy()), Cow::Borrowed(path) => Some(path.to_string_lossy()),
Cow::Owned(path) => Some(path.to_string_lossy().to_string().into()), Cow::Owned(path) => Some(path.to_string_lossy().to_string().into()),
} }
@ -579,10 +608,11 @@ impl Item for Editor {
style: &theme::Tab, style: &theme::Tab,
cx: &AppContext, cx: &AppContext,
) -> AnyElement<T> { ) -> AnyElement<T> {
let this = self.0.read(cx);
Flex::row() Flex::row()
.with_child(Label::new(self.title(cx).to_string(), style.label.clone()).into_any()) .with_child(Label::new(this.title(cx).to_string(), style.label.clone()).into_any())
.with_children(detail.and_then(|detail| { .with_children(detail.and_then(|detail| {
let path = path_for_buffer(&self.buffer, detail, false, cx)?; let path = path_for_buffer(&this.buffer(), detail, false, cx)?;
let description = path.to_string_lossy(); let description = path.to_string_lossy();
Some( Some(
Label::new( Label::new(
@ -599,13 +629,15 @@ impl Item for Editor {
} }
fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) { fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
self.buffer self.0
.read(cx)
.buffer()
.read(cx) .read(cx)
.for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx))); .for_each_buffer(|buffer| f(buffer.id(), buffer.read(cx)));
} }
fn is_singleton(&self, cx: &AppContext) -> bool { fn is_singleton(&self, cx: &AppContext) -> bool {
self.buffer.read(cx).is_singleton() self.0.read(cx).buffer().read(cx).is_singleton()
} }
fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self> fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
@ -615,30 +647,35 @@ impl Item for Editor {
Some(self.clone(cx)) Some(self.clone(cx))
} }
fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) { fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
self.nav_history = Some(history); self.0
.update(cx, |this, cx| this.set_nav_history(Some(Box::new(history))));
} }
fn deactivated(&mut self, cx: &mut ViewContext<Self>) { fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
let selection = self.selections.newest_anchor(); self.0.update(cx, |this, cx| {
self.push_to_nav_history(selection.head(), None, cx); let selection = this.selections.newest_anchor();
this.push_to_nav_history(selection.head(), None, cx)
});
} }
fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) { fn workspace_deactivated(&mut self, cx: &mut ViewContext<Self>) {
hide_link_definition(self, cx); self.0.update(cx, |this, cx| {
self.link_go_to_definition_state.last_trigger_point = None; hide_link_definition(this, cx);
this.link_go_to_definition_state.last_trigger_point = None
});
} }
fn is_dirty(&self, cx: &AppContext) -> bool { fn is_dirty(&self, cx: &AppContext) -> bool {
self.buffer().read(cx).read(cx).is_dirty() self.0.read(cx).buffer().read(cx).read(cx).is_dirty()
} }
fn has_conflict(&self, cx: &AppContext) -> bool { fn has_conflict(&self, cx: &AppContext) -> bool {
self.buffer().read(cx).read(cx).has_conflict() self.0.read(cx).buffer().read(cx).read(cx).has_conflict()
} }
fn can_save(&self, cx: &AppContext) -> bool { fn can_save(&self, cx: &AppContext) -> bool {
let buffer = &self.buffer().read(cx); let buffer = &self.0.read(cx).buffer().read(cx);
if let Some(buffer) = buffer.as_singleton() { if let Some(buffer) = buffer.as_singleton() {
buffer.read(cx).project_path(cx).is_some() buffer.read(cx).project_path(cx).is_some()
} else { } else {
@ -651,40 +688,47 @@ impl Item for Editor {
project: ModelHandle<Project>, project: ModelHandle<Project>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
self.report_editor_event("save", None, cx); self.0.update(cx, |this, cx| {
let format = self.perform_format(project.clone(), FormatTrigger::Save, cx); this.report_editor_event("save", None, cx);
let buffers = self.buffer().clone().read(cx).all_buffers(); let format = this.perform_format(
cx.spawn(|_, mut cx| async move { Arc::new(ProjectHandle(project.clone())),
format.await?; FormatTrigger::Save,
cx,
);
let buffers = this.buffer().clone().read(cx).all_buffers();
cx.spawn(|_, mut cx| async move {
format.await?;
if buffers.len() == 1 { if buffers.len() == 1 {
project project
.update(&mut cx, |project, cx| project.save_buffers(buffers, cx)) .update(&mut cx, |project, cx| project.save_buffers(buffers, cx))
.await?; .await?;
} else { } else {
// For multi-buffers, only save those ones that contain changes. For clean buffers // For multi-buffers, only save those ones that contain changes. For clean buffers
// we simulate saving by calling `Buffer::did_save`, so that language servers or // we simulate saving by calling `Buffer::did_save`, so that language servers or
// other downstream listeners of save events get notified. // other downstream listeners of save events get notified.
let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| { let (dirty_buffers, clean_buffers) = buffers.into_iter().partition(|buffer| {
buffer.read_with(&cx, |buffer, _| buffer.is_dirty() || buffer.has_conflict()) buffer
}); .read_with(&cx, |buffer, _| buffer.is_dirty() || buffer.has_conflict())
project
.update(&mut cx, |project, cx| {
project.save_buffers(dirty_buffers, cx)
})
.await?;
for buffer in clean_buffers {
buffer.update(&mut cx, |buffer, cx| {
let version = buffer.saved_version().clone();
let fingerprint = buffer.saved_version_fingerprint();
let mtime = buffer.saved_mtime();
buffer.did_save(version, fingerprint, mtime, cx);
}); });
}
}
Ok(()) project
.update(&mut cx, |project, cx| {
project.save_buffers(dirty_buffers, cx)
})
.await?;
for buffer in clean_buffers {
buffer.update(&mut cx, |buffer, cx| {
let version = buffer.saved_version().clone();
let fingerprint = buffer.saved_version_fingerprint();
let mtime = buffer.saved_mtime();
buffer.did_save(version, fingerprint, mtime, cx);
});
}
}
Ok(())
})
}) })
} }
@ -694,19 +738,21 @@ impl Item for Editor {
abs_path: PathBuf, abs_path: PathBuf,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let buffer = self self.0.update(cx, |this, cx| {
.buffer() let buffer = this
.read(cx) .buffer()
.as_singleton() .read(cx)
.expect("cannot call save_as on an excerpt list"); .as_singleton()
.expect("cannot call save_as on an excerpt list");
let file_extension = abs_path let file_extension = abs_path
.extension() .extension()
.map(|a| a.to_string_lossy().to_string()); .map(|a| a.to_string_lossy().to_string());
self.report_editor_event("save", file_extension, cx); this.report_editor_event("save", file_extension, cx);
project.update(cx, |project, cx| { project.update(cx, |project, cx| {
project.save_buffer_as(buffer, abs_path, cx) project.save_buffer_as(buffer, abs_path, cx)
})
}) })
} }
@ -715,23 +761,26 @@ impl Item for Editor {
project: ModelHandle<Project>, project: ModelHandle<Project>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let buffer = self.buffer().clone(); let this = self.0.read(cx);
let buffers = self.buffer.read(cx).all_buffers(); let buffer = this.buffer().clone();
let buffers = buffer.read(cx).all_buffers();
let reload_buffers = let reload_buffers =
project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx)); project.update(cx, |project, cx| project.reload_buffers(buffers, true, cx));
cx.spawn(|this, mut cx| async move { self.0.update(cx, |_, cx| {
let transaction = reload_buffers.log_err().await; cx.spawn(|this, mut cx| async move {
this.update(&mut cx, |editor, cx| { let transaction = reload_buffers.log_err().await;
editor.request_autoscroll(Autoscroll::fit(), cx) this.update(&mut cx, |editor, cx| {
})?; editor.request_autoscroll(Autoscroll::fit(), cx)
buffer.update(&mut cx, |buffer, cx| { })?;
if let Some(transaction) = transaction { buffer.update(&mut cx, |buffer, cx| {
if !buffer.is_singleton() { if let Some(transaction) = transaction {
buffer.push_transaction(&transaction.0, cx); if !buffer.is_singleton() {
buffer.push_transaction(&transaction.0, cx);
}
} }
} });
}); Ok(())
Ok(()) })
}) })
} }
@ -765,8 +814,8 @@ impl Item for Editor {
Some(Box::new(handle.clone())) Some(Box::new(handle.clone()))
} }
fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> { fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
self.pixel_position_of_newest_cursor self.0.read(cx).pixel_position_of_newest_cursor
} }
fn breadcrumb_location(&self) -> ToolbarItemLocation { fn breadcrumb_location(&self) -> ToolbarItemLocation {
@ -774,41 +823,43 @@ impl Item for Editor {
} }
fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> { fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
let cursor = self.selections.newest_anchor().head(); self.0
let multibuffer = &self.buffer().read(cx); .read_with(cx, |this, cx| {
let (buffer_id, symbols) = let cursor = this.selections.newest_anchor().head();
multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?; let multibuffer = &this.buffer().read(cx);
let buffer = multibuffer.buffer(buffer_id)?; let (buffer_id, symbols) =
multibuffer.symbols_containing(cursor, Some(&theme.editor.syntax), cx)?;
let buffer = multibuffer.buffer(buffer_id)?;
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let filename = buffer let filename = buffer
.snapshot() .snapshot()
.resolve_file_path( .resolve_file_path(
cx, cx,
self.project this.project
.as_ref() .as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1) .map(|project| project.visible_worktrees_count(cx) > 1)
.unwrap_or_default(), .unwrap_or_default(),
) )
.map(|path| path.to_string_lossy().to_string()) .map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| "untitled".to_string()); .unwrap_or_else(|| "untitled".to_string());
let mut breadcrumbs = vec![BreadcrumbText { let mut breadcrumbs = vec![BreadcrumbText {
text: filename, text: filename,
highlights: None, highlights: None,
}]; }];
breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText { breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
text: symbol.text, text: symbol.text,
highlights: Some(symbol.highlight_ranges), highlights: Some(symbol.highlight_ranges),
})); }));
Some(breadcrumbs) Some(breadcrumbs)
})
.flatten()
} }
fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) { fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
let workspace_id = workspace.database_id(); let workspace_id = workspace.database_id();
let item_id = cx.view_id(); let item_id = cx.view_id();
self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
fn serialize( fn serialize(
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
workspace_id: WorkspaceId, workspace_id: WorkspaceId,
@ -828,18 +879,24 @@ impl Item for Editor {
} }
} }
if let Some(buffer) = self.buffer().read(cx).as_singleton() { self.0.update(cx, |this, cx| {
serialize(buffer.clone(), workspace_id, item_id, cx); this.workspace = Some((
Arc::new(WeakWorkspaceHandle(workspace.weak_handle())),
workspace.database_id(),
));
if let Some(buffer) = this.buffer().read(cx).as_singleton() {
serialize(buffer.clone(), workspace_id, item_id, cx);
cx.subscribe(&buffer, |this, buffer, event, cx| { cx.subscribe(&buffer, |this, buffer, event, cx| {
if let Some((_, workspace_id)) = this.workspace.as_ref() { if let Some((_, workspace_id)) = this.workspace.as_ref() {
if let language::Event::FileHandleChanged = event { if let language::Event::FileHandleChanged = event {
serialize(buffer, *workspace_id, cx.view_id(), cx); serialize(buffer, *workspace_id, cx.view_id(), cx);
}
} }
} })
}) .detach();
.detach(); }
} });
} }
fn serialized_item_kind() -> Option<&'static str> { fn serialized_item_kind() -> Option<&'static str> {
@ -849,7 +906,7 @@ impl Item for Editor {
fn deserialize( fn deserialize(
project: ModelHandle<Project>, project: ModelHandle<Project>,
_workspace: WeakViewHandle<Workspace>, _workspace: WeakViewHandle<Workspace>,
workspace_id: workspace::WorkspaceId, workspace_id: WorkspaceId,
item_id: ItemId, item_id: ItemId,
cx: &mut ViewContext<Pane>, cx: &mut ViewContext<Pane>,
) -> Task<Result<ViewHandle<Self>>> { ) -> Task<Result<ViewHandle<Self>>> {
@ -879,9 +936,20 @@ impl Item for Editor {
.context("Project item at stored path was not a buffer")?; .context("Project item at stored path was not a buffer")?;
Ok(pane.update(&mut cx, |_, cx| { Ok(pane.update(&mut cx, |_, cx| {
cx.add_view(|cx| { cx.add_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx); FollowableEditor(cx.add_view(|cx| {
editor.read_scroll_position_from_db(item_id, workspace_id, cx); let mut editor = Editor::for_buffer(
editor buffer,
Some(Arc::new(ProjectHandle(project))),
cx,
);
editor.read_scroll_position_from_db(
&*DB,
item_id,
workspace_id,
cx,
);
editor
}))
}) })
})?) })?)
}) })
@ -890,7 +958,7 @@ impl Item for Editor {
} }
} }
impl ProjectItem for Editor { impl ProjectItem for FollowableEditor {
type Item = Buffer; type Item = Buffer;
fn for_project_item( fn for_project_item(
@ -898,23 +966,26 @@ impl ProjectItem for Editor {
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
Self::for_buffer(buffer, Some(project), cx) Self(
cx.add_view(|cx| {
Editor::for_buffer(buffer, Some(Arc::new(ProjectHandle(project))), cx)
}),
)
} }
} }
pub(crate) enum BufferSearchHighlights {} impl SearchableItem for FollowableEditor {
impl SearchableItem for Editor {
type Match = Range<Anchor>; type Match = Range<Anchor>;
fn to_search_event( fn to_search_event(
&mut self, &mut self,
event: &Self::Event, event: &Self::Event,
_: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<SearchEvent> { ) -> Option<SearchEvent> {
match event { match event {
Event::BufferEdited => Some(SearchEvent::MatchesInvalidated), Event::BufferEdited => Some(SearchEvent::MatchesInvalidated),
Event::SelectionsChanged { .. } => { Event::SelectionsChanged { .. } => {
if self.selections.disjoint_anchors().len() == 1 { if self.0.read(cx).selections.disjoint_anchors().len() == 1 {
Some(SearchEvent::ActiveMatchChanged) Some(SearchEvent::ActiveMatchChanged)
} else { } else {
None None
@ -925,20 +996,27 @@ impl SearchableItem for Editor {
} }
fn clear_matches(&mut self, cx: &mut ViewContext<Self>) { fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
self.clear_background_highlights::<BufferSearchHighlights>(cx); self.0.update(cx, |this, cx| {
this.clear_background_highlights::<BufferSearchHighlights>(cx)
});
} }
fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) { fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
self.highlight_background::<BufferSearchHighlights>( self.0.update(cx, |this, cx| {
matches, this.highlight_background::<BufferSearchHighlights>(
|theme| theme.search.match_background, matches,
cx, |theme| theme.search.match_background,
); cx,
)
});
} }
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String { fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
let display_map = self.snapshot(cx).display_snapshot; let (display_map, selection) = self.0.update(cx, |this, cx| {
let selection = self.selections.newest::<usize>(cx); let display_map = this.snapshot(cx).display_snapshot;
let selection = this.selections.newest::<usize>(cx);
(display_map, selection)
});
if selection.start == selection.end { if selection.start == selection.end {
let point = selection.start.to_display_point(&display_map); let point = selection.start.to_display_point(&display_map);
let range = surrounding_word(&display_map, point); let range = surrounding_word(&display_map, point);
@ -964,20 +1042,24 @@ impl SearchableItem for Editor {
matches: Vec<Range<Anchor>>, matches: Vec<Range<Anchor>>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.unfold_ranges([matches[index].clone()], false, true, cx); self.0.update(cx, |this, cx| {
let range = self.range_for_match(&matches[index]); this.unfold_ranges([matches[index].clone()], false, true, cx);
self.change_selections(Some(Autoscroll::fit()), cx, |s| { let range = this.range_for_match(&matches[index]);
s.select_ranges([range]); this.change_selections(Some(Autoscroll::fit()), cx, |s| {
}) s.select_ranges([range]);
})
});
} }
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) { fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
self.unfold_ranges(matches.clone(), false, false, cx); self.0.update(cx, |this, cx| {
let mut ranges = Vec::new(); this.unfold_ranges(matches.clone(), false, false, cx);
for m in &matches { let mut ranges = Vec::new();
ranges.push(self.range_for_match(&m)) for m in &matches {
} ranges.push(this.range_for_match(&m))
self.change_selections(None, cx, |s| s.select_ranges(ranges)); }
this.change_selections(None, cx, |s| s.select_ranges(ranges));
});
} }
fn replace( fn replace(
&mut self, &mut self,
@ -985,7 +1067,7 @@ impl SearchableItem for Editor {
query: &SearchQuery, query: &SearchQuery,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let text = self.buffer.read(cx); let text = self.0.read(cx).buffer().read(cx);
let text = text.snapshot(cx); let text = text.snapshot(cx);
let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>(); let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>();
let text: Cow<_> = if text.len() == 1 { let text: Cow<_> = if text.len() == 1 {
@ -996,8 +1078,10 @@ impl SearchableItem for Editor {
}; };
if let Some(replacement) = query.replacement_for(&text) { if let Some(replacement) = query.replacement_for(&text) {
self.transact(cx, |this, cx| { self.0.update(cx, |this, cx| {
this.edit([(identifier.clone(), Arc::from(&*replacement))], cx); this.transact(cx, |this, cx| {
this.edit([(identifier.clone(), Arc::from(&*replacement))], cx);
})
}); });
} }
} }
@ -1009,9 +1093,10 @@ impl SearchableItem for Editor {
count: usize, count: usize,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> usize { ) -> usize {
let buffer = self.buffer().read(cx).snapshot(cx); let this = self.0.read(cx);
let current_index_position = if self.selections.disjoint_anchors().len() == 1 { let buffer = this.buffer().read(cx).snapshot(cx);
self.selections.newest_anchor().head() let current_index_position = if this.selections.disjoint_anchors().len() == 1 {
this.selections.newest_anchor().head()
} else { } else {
matches[current_index].start matches[current_index].start
}; };
@ -1055,7 +1140,7 @@ impl SearchableItem for Editor {
query: Arc<project::search::SearchQuery>, query: Arc<project::search::SearchQuery>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Vec<Range<Anchor>>> { ) -> Task<Vec<Range<Anchor>>> {
let buffer = self.buffer().read(cx).snapshot(cx); let buffer = self.0.read(cx).buffer().read(cx).snapshot(cx);
cx.background().spawn(async move { cx.background().spawn(async move {
let mut ranges = Vec::new(); let mut ranges = Vec::new();
if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() { if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
@ -1098,10 +1183,11 @@ impl SearchableItem for Editor {
matches: Vec<Range<Anchor>>, matches: Vec<Range<Anchor>>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<usize> { ) -> Option<usize> {
let this = self.0.read(cx);
active_match_index( active_match_index(
&matches, &matches,
&self.selections.newest_anchor().head(), &this.selections.newest_anchor().head(),
&self.buffer().read(cx).snapshot(cx), &this.buffer().read(cx).snapshot(cx),
) )
} }
} }
@ -1289,11 +1375,11 @@ mod tests {
impl language::File for TestFile { impl language::File for TestFile {
fn path(&self) -> &Arc<Path> { fn path(&self) -> &Arc<Path> {
&self.path &self.0.path
} }
fn full_path(&self, _: &gpui::AppContext) -> PathBuf { fn full_path(&self, _: &gpui::AppContext) -> PathBuf {
self.full_path.clone() self.0.full_path.clone()
} }
fn as_local(&self) -> Option<&dyn language::LocalFile> { fn as_local(&self) -> Option<&dyn language::LocalFile> {

View file

@ -0,0 +1,703 @@
mod items;
mod persistence;
use editor::MultiBuffer;
use gpui::elements::ChildView;
use gpui::elements::ParentElement;
use gpui::keymap_matcher::KeymapContext;
use gpui::WindowContext;
use gpui::{
AnyViewHandle, AppContext, Element, ModelHandle, Subscription, Task, ViewContext, ViewHandle,
WeakViewHandle,
};
pub use items::*;
use anyhow::{anyhow, Result};
use client::{Client, Collaborator, ParticipantIndex};
use collections::{HashMap, HashSet};
use editor::scroll::autoscroll::Autoscroll;
use editor::{
CollaborationHub, ConfirmRename, Editor, Event, InlayHintRefreshReason, OpenExcerpts, Project,
};
use language::{
Buffer, CachedLspAdapter, CodeAction, Completion, LanguageRegistry, LanguageServerName,
};
use lsp::{LanguageServer, LanguageServerId};
use project_types::*;
use rpc::proto::PeerId;
use std::mem;
use std::ops::Range;
use std::path::PathBuf;
use std::sync::Arc;
use util::ResultExt;
use workspace::item::ItemHandle;
use workspace::Workspace;
use workspace_types::*;
pub fn init(cx: &mut AppContext) {
cx.add_action(new_file);
cx.add_action(new_file_in_direction);
cx.add_action(open_excerpts);
cx.add_action(confirm_rename);
workspace::register_project_item::<FollowableEditor>(cx);
workspace::register_followable_item::<FollowableEditor>(cx);
workspace::register_deserializable_item::<FollowableEditor>(cx);
}
impl Project for ProjectHandle {
fn apply_code_action(
&self,
buffer_handle: ModelHandle<Buffer>,
mut action: CodeAction,
push_to_history: bool,
cx: &mut AppContext,
) -> Task<Result<ProjectTransaction>> {
self.0.update(cx, |this, cx| {
this.apply_code_action(buffer_handle, action, push_to_history, cx)
})
}
fn inlay_hints(
&self,
buffer_handle: ModelHandle<Buffer>,
range: Range<text::Anchor>,
cx: &mut AppContext,
) -> Task<anyhow::Result<Vec<InlayHint>>> {
self.0
.update(cx, |this, cx| this.inlay_hints(buffer_handle, range, cx))
}
fn visible_worktrees_count(&self, cx: &AppContext) -> usize {
self.0.read(cx).visible_worktrees(cx).count()
}
fn resolve_inlay_hint(
&self,
hint: InlayHint,
buffer_handle: ModelHandle<Buffer>,
server_id: LanguageServerId,
cx: &mut AppContext,
) -> Task<anyhow::Result<InlayHint>> {
self.0.update(cx, |this, cx| {
this.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
})
}
fn languages(&self, cx: &AppContext) -> Arc<LanguageRegistry> {
self.0.read(cx).languages().clone()
}
fn hover(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Option<Hover>>> {
self.0
.update(cx, |this, cx| this.hover(buffer, position, cx))
}
fn definition(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<LocationLink>>> {
self.0
.update(cx, |this, cx| this.definition(buffer, position, cx))
}
fn type_definition(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<LocationLink>>> {
self.0
.update(cx, |this, cx| this.type_definition(buffer, position, cx))
}
fn completions(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<Completion>>> {
self.0
.update(cx, |this, cx| this.completions(buffer, position, cx))
}
fn as_hub(&self) -> Box<dyn CollaborationHub> {
Box::new(self.clone())
}
fn is_remote(&self, cx: &AppContext) -> bool {
self.0.read(cx).is_remote()
}
fn remote_id(&self, cx: &AppContext) -> Option<u64> {
self.0.read(cx).remote_id()
}
fn language_servers_for_buffer(
&self,
buffer: &Buffer,
cx: &AppContext,
) -> Vec<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
self.0
.read(cx)
.language_servers_for_buffer(buffer, cx)
.map(|(adapter, server)| (adapter.clone(), server.clone()))
.collect()
}
fn on_type_format(
&self,
buffer: ModelHandle<Buffer>,
position: text::Anchor,
trigger: String,
push_to_history: bool,
cx: &mut AppContext,
) -> Task<Result<Option<text::Transaction>>> {
self.0.update(cx, |this, cx| {
this.on_type_format(buffer, position, trigger, push_to_history, cx)
})
}
fn client(&self, cx: &AppContext) -> Arc<Client> {
self.0.read(cx).client().clone()
}
fn language_server_for_id(
&self,
id: LanguageServerId,
cx: &AppContext,
) -> Option<Arc<LanguageServer>> {
self.0.read(cx).language_server_for_id(id).clone()
}
fn code_actions(
&self,
buffer_handle: &ModelHandle<Buffer>,
range: Range<text::Anchor>,
cx: &mut AppContext,
) -> Task<Result<Vec<CodeAction>>> {
self.0
.update(cx, |this, cx| this.code_actions(buffer_handle, range, cx))
}
fn document_highlights(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<DocumentHighlight>>> {
self.0.update(cx, |this, cx| {
this.document_highlights(buffer, position, cx)
})
}
fn format(
&self,
buffers: HashSet<ModelHandle<Buffer>>,
push_to_history: bool,
trigger: FormatTrigger,
cx: &mut AppContext,
) -> Task<anyhow::Result<ProjectTransaction>> {
self.0.update(cx, |this, cx| {
this.format(buffers, push_to_history, trigger, cx)
})
}
fn restart_language_servers_for_buffers(
&self,
buffers: HashSet<ModelHandle<Buffer>>,
cx: &mut AppContext,
) -> Option<()> {
self.0.update(cx, |this, cx| {
this.restart_language_servers_for_buffers(buffers, cx)
})
}
fn prepare_rename(
&self,
buffer: ModelHandle<Buffer>,
position: usize,
cx: &mut AppContext,
) -> Task<Result<Option<Range<text::Anchor>>>> {
self.0
.update(cx, |this, cx| this.prepare_rename(buffer, position, cx))
}
fn references(
&self,
buffer: &ModelHandle<Buffer>,
position: text::Anchor,
cx: &mut AppContext,
) -> Task<Result<Vec<Location>>> {
self.0
.update(cx, |this, cx| this.references(buffer, position, cx))
}
fn apply_additional_edits_for_completion(
&self,
buffer_handle: ModelHandle<Buffer>,
completion: Completion,
push_to_history: bool,
cx: &mut AppContext,
) -> Task<Result<Option<text::Transaction>>> {
self.0.update(cx, |this, cx| {
this.apply_additional_edits_for_completion(
buffer_handle,
completion,
push_to_history,
cx,
)
})
}
fn language_server_for_buffer<'a>(
&self,
buffer: &Buffer,
server_id: LanguageServerId,
cx: &'a AppContext,
) -> Option<(&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
self.0
.read(cx)
.language_server_for_buffer(buffer, server_id, cx)
}
fn open_local_buffer_via_lsp(
&self,
abs_path: lsp::Url,
language_server_id: LanguageServerId,
language_server_name: LanguageServerName,
cx: &mut AppContext,
) -> Task<Result<ModelHandle<Buffer>>> {
self.0.update(cx, |this, cx| {
this.open_local_buffer_via_lsp(abs_path, language_server_id, language_server_name, cx)
})
}
fn subscribe(&self, is_singleton: bool, cx: &mut ViewContext<Editor>) -> Vec<Subscription> {
let mut project_subscriptions = Vec::with_capacity(2);
if is_singleton {
project_subscriptions.push(cx.observe(&self.0, |_, _, cx| {
cx.emit(Event::TitleChanged);
}));
}
project_subscriptions.push(cx.subscribe(&self.0, |editor, _, event, cx| {
if let project::Event::RefreshInlayHints = event {
editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
};
}));
project_subscriptions
}
fn project_file(&self, file: &dyn language::File) -> ProjectPath {
todo!()
//project::File::from_dyn(file).map
}
}
#[derive(Clone, Debug, PartialEq)]
struct ProjectHandle(pub ModelHandle<project::Project>);
#[derive(Clone, Debug, PartialEq)]
struct WeakWorkspaceHandle(pub WeakViewHandle<workspace::Workspace>);
impl CollaborationHub for ProjectHandle {
fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
self.0.read(cx).collaborators()
}
fn user_participant_indices<'a>(
&self,
cx: &'a AppContext,
) -> &'a HashMap<u64, ParticipantIndex> {
self.0.read(cx).user_store().read(cx).participant_indices()
}
}
impl editor::Workspace for WeakWorkspaceHandle {
fn open_abs_path(&self, abs_path: PathBuf, visible: bool, cx: &mut AppContext) {
self.0
.update(cx, |workspace, cx| {
workspace.open_abs_path(abs_path, visible, cx)
})
.map_or_else(|err| Task::ready(Err(err)), |ok| ok)
.detach_and_log_err(cx)
}
fn open_path(
&self,
path: ProjectPath,
focus_item: bool,
cx: &mut AppContext,
) -> Task<Result<ViewHandle<Editor>>> {
let open_task = self
.0
.update(cx, |this, cx| this.open_path(path, None, focus_item, cx));
cx.spawn(move |mut cx| async move {
Ok(open_task?
.await?
.downcast::<Editor>()
.ok_or_else(|| anyhow!("opened item was not an editor"))?)
})
}
fn active_editor(&self, cx: &mut AppContext) -> Option<ViewHandle<Editor>> {
self.0
.update(cx, |workspace, cx| {
workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
})
.log_err()
.flatten()
}
fn project(&self, cx: &mut AppContext) -> Arc<dyn Project> {
Arc::new(ProjectHandle(
self.0
.update(cx, |this, cx| this.project().clone())
.unwrap(),
))
}
fn disable_update_history_for_current_pane(
&self,
cx: &mut AppContext,
) -> Option<editor::DisableUpdateHistoryGuard> {
let pane = self
.0
.update(cx, |this, cx| this.active_pane().clone())
.log_err()?;
pane.update(cx, |pane, cx| {
pane.disable_history();
pane.enable_history()
});
let pane = pane.downgrade();
struct Guard {
pane: WeakViewHandle<workspace::Pane>,
}
impl editor::DisableUpdateHistory for Guard {
fn release(self, cx: &mut AppContext) {
self.pane.update(cx, |this, _| this.enable_history());
}
}
Some(Box::new(Guard { pane }))
}
fn split_buffer(&self, buffer: ModelHandle<Buffer>, cx: &mut AppContext) -> ViewHandle<Editor> {
self.0
.update(cx, |this, cx| {
this.split_project_item::<FollowableEditor>(buffer, cx)
})
.unwrap()
.read(cx)
.0
.clone()
}
fn open_buffer(&self, buffer: ModelHandle<Buffer>, cx: &mut AppContext) -> ViewHandle<Editor> {
self.0
.update(cx, |this, cx| {
this.open_project_item::<FollowableEditor>(buffer, cx)
})
.unwrap()
.read(cx)
.0
.clone()
}
fn add_item(&self, item: Box<ViewHandle<Editor>>, cx: &mut AppContext) {
self.0.update(cx, |this, cx| {
this.add_item(Box::new(cx.add_view(|_| FollowableEditor(*item))), cx)
});
}
fn split_item(
&self,
split_direction: SplitDirection,
item: Box<ViewHandle<Editor>>,
cx: &mut AppContext,
) {
self.0.update(cx, |this, cx| {
this.split_item(
split_direction,
Box::new(cx.add_view(|_| FollowableEditor(*item))),
cx,
)
});
}
fn db(&self) -> Arc<dyn editor::Db> {
Arc::new(persistence::DB.clone())
}
}
pub struct FollowableEditor(pub ViewHandle<editor::Editor>);
impl gpui::Entity for FollowableEditor {
type Event = <Editor as gpui::Entity>::Event;
}
impl FollowableEditor {
pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
Self(cx.add_view(|cx| self.0.update(cx, |this, cx| this.clone(cx))))
}
pub fn for_multibuffer(
buffer: ModelHandle<MultiBuffer>,
project: ModelHandle<project::Project>,
cx: &mut ViewContext<Self>,
) -> Self {
Self(cx.add_view(|cx| {
Editor::for_multibuffer(buffer, Some(Arc::new(ProjectHandle(project))), cx)
}))
}
pub fn for_buffer(
buffer: ModelHandle<Buffer>,
project: ModelHandle<project::Project>,
cx: &mut ViewContext<Self>,
) -> Self {
Self(
cx.add_view(|cx| {
Editor::for_buffer(buffer, Some(Arc::new(ProjectHandle(project))), cx)
}),
)
}
pub fn for_raw_buffer(
buffer: ModelHandle<Buffer>,
cx: &mut ViewContext<Self>,
) -> Self {
Self(
cx.add_view(|cx| {
Editor::for_buffer(buffer, None, cx)
}),
)
}
pub fn single_line(
field_editor_style: Option<Arc<editor::GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
Self(
cx.add_view(|cx| {
Editor::single_line(field_editor_style, cx)
}),
)
}
}
impl gpui::View for FollowableEditor {
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
ChildView::new(&self.0, cx).into_any()
}
fn ui_name() -> &'static str {
Editor::ui_name()
}
fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
self.0.update(cx, |this, cx| this.focus_in(focused, cx))
}
fn focus_out(&mut self, handle: AnyViewHandle, cx: &mut ViewContext<Self>) {
self.0.update(cx, |this, cx| this.focus_out(handle, cx))
}
fn modifiers_changed(
&mut self,
event: &gpui::platform::ModifiersChangedEvent,
cx: &mut ViewContext<Self>,
) -> bool {
self.0
.update(cx, |this, cx| this.modifiers_changed(event, cx))
}
fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
self.0
.read_with(cx, |this, cx| this.update_keymap_context(keymap, cx));
}
fn text_for_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<String> {
self.0
.read_with(cx, |this, cx| this.text_for_range(range_utf16, cx))
.flatten()
}
fn selected_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
self.0
.read_with(cx, |this, cx| this.selected_text_range(cx))
.flatten()
}
fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
self.0
.read_with(cx, |this, cx| this.marked_text_range(cx))
.flatten()
}
fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
self.0.update(cx, |this, cx| this.unmark_text(cx));
}
fn replace_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
text: &str,
cx: &mut ViewContext<Self>,
) {
self.0.update(cx, |this, cx| {
this.replace_text_in_range(range_utf16, text, cx)
})
}
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
text: &str,
new_selected_range_utf16: Option<Range<usize>>,
cx: &mut ViewContext<Self>,
) {
self.0.update(cx, |this, cx| {
this.replace_and_mark_text_in_range(range_utf16, text, new_selected_range_utf16, cx)
})
}
}
pub fn new_file(
workspace: &mut Workspace,
_: &workspace_types::NewFile,
cx: &mut ViewContext<Workspace>,
) {
let project = workspace.project().clone();
if project.read(cx).is_remote() {
cx.propagate_action();
} else if let Some(buffer) = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.log_err()
{
workspace.add_item(
Box::new(cx.add_view(|cx| {
FollowableEditor(cx.add_view(|cx| {
Editor::for_buffer(buffer, Some(Arc::new(ProjectHandle(project.clone()))), cx)
}))
})),
cx,
);
}
}
pub fn new_file_in_direction(
workspace: &mut Workspace,
action: &workspace::NewFileInDirection,
cx: &mut ViewContext<Workspace>,
) {
let project = workspace.project().clone();
if project.read(cx).is_remote() {
cx.propagate_action();
} else if let Some(buffer) = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.log_err()
{
workspace.split_item(
action.0,
Box::new(cx.add_view(|cx| {
FollowableEditor(cx.add_view(|cx| {
Editor::for_buffer(buffer, Some(Arc::new(ProjectHandle(project.clone()))), cx)
}))
})),
cx,
);
}
}
fn open_excerpts(workspace: &mut Workspace, _: &OpenExcerpts, cx: &mut ViewContext<Workspace>) {
let active_item = workspace.active_item(cx);
let editor_handle = if let Some(editor) = active_item
.as_ref()
.and_then(|item| item.act_as::<Editor>(cx))
{
editor
} else {
cx.propagate_action();
return;
};
let editor = editor_handle.read(cx);
let buffer = editor.buffer().read(cx);
if buffer.is_singleton() {
cx.propagate_action();
return;
}
let mut new_selections_by_buffer = HashMap::default();
for selection in editor.selections.all::<usize>(cx) {
for (buffer, mut range, _) in
buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
{
if selection.reversed {
mem::swap(&mut range.start, &mut range.end);
}
new_selections_by_buffer
.entry(buffer)
.or_insert(Vec::new())
.push(range)
}
}
editor_handle.update(cx, |editor, cx| {
editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx);
});
let pane = workspace.active_pane().clone();
pane.update(cx, |pane, _| pane.disable_history());
// We defer the pane interaction because we ourselves are a workspace item
// and activating a new item causes the pane to call a method on us reentrantly,
// which panics if we're on the stack.
cx.defer(move |workspace, cx| {
for (buffer, ranges) in new_selections_by_buffer.into_iter() {
let editor = workspace.open_project_item::<FollowableEditor>(buffer, cx);
editor.update(cx, |this, cx| {
this.0.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
s.select_ranges(ranges);
});
})
});
}
pane.update(cx, |pane, _| pane.enable_history());
});
}
pub fn confirm_rename(
workspace: &mut Workspace,
_: &ConfirmRename,
cx: &mut ViewContext<Workspace>,
) -> Option<Task<Result<()>>> {
let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
let (buffer, range, old_name, new_name) = editor.update(cx, |editor, cx| {
let rename = editor.take_rename(false, cx)?;
let buffer = editor.buffer().read(cx);
let (start_buffer, start) =
buffer.text_anchor_for_position(rename.range.start.clone(), cx)?;
let (end_buffer, end) = buffer.text_anchor_for_position(rename.range.end.clone(), cx)?;
if start_buffer == end_buffer {
let new_name = rename.editor.read(cx).text(cx);
Some((start_buffer, start..end, rename.old_name, new_name))
} else {
None
}
})?;
let rename = workspace.project().clone().update(cx, |project, cx| {
project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
});
let editor = editor.downgrade();
Some(cx.spawn(|workspace, mut cx| async move {
let project_transaction = rename.await?;
Editor::open_project_transaction(
&editor,
&WeakWorkspaceHandle(workspace),
project_transaction,
format!("Rename: {old_name}{new_name}"),
cx.clone(),
)
.await?;
editor.update(&mut cx, |editor, cx| {
editor.refresh_document_highlights(cx);
})?;
Ok(())
}))
}

View file

@ -1,9 +1,12 @@
use anyhow::Result;
use async_trait::async_trait;
use std::path::PathBuf; use std::path::PathBuf;
use db::sqlez_macros::sql; use db::sqlez_macros::sql;
use db::{define_connection, query}; use db::{define_connection, query};
use workspace::{ItemId, WorkspaceDb, WorkspaceId}; use workspace::WorkspaceDb;
use workspace_types::{ItemId, WorkspaceId};
define_connection!( define_connection!(
// Current schema shape using pseudo-rust syntax: // Current schema shape using pseudo-rust syntax:
@ -81,3 +84,39 @@ impl EditorDb {
} }
} }
} }
#[async_trait]
impl editor::Db for EditorDb {
async fn save_scroll_position(
&self,
item_id: ItemId,
workspace_id: WorkspaceId,
top_row: u32,
vertical_offset: f32,
horizontal_offset: f32,
) -> Result<()> {
self.save_scroll_position(
item_id,
workspace_id,
top_row,
vertical_offset,
horizontal_offset,
)
.await
}
fn get_scroll_position(
&self,
item_id: ItemId,
workspace_id: WorkspaceId,
) -> Result<Option<(u32, f32, f32)>> {
self.get_scroll_position(item_id, workspace_id)
}
async fn save_path(
&self,
item_id: ItemId,
workspace_id: WorkspaceId,
path: PathBuf,
) -> Result<()> {
self.save_path(item_id, workspace_id, path).await
}
}

View file

@ -13,6 +13,7 @@ test-support = []
[dependencies] [dependencies]
client = { path = "../client" } client = { path = "../client" }
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
language = { path = "../language" } language = { path = "../language" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
project = { path = "../project" } project = { path = "../project" }

View file

@ -2,6 +2,7 @@ use crate::system_specs::SystemSpecs;
use anyhow::bail; use anyhow::bail;
use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
use editor::{Anchor, Editor}; use editor::{Anchor, Editor};
use editor_extensions::FollowableEditor;
use futures::AsyncReadExt; use futures::AsyncReadExt;
use gpui::{ use gpui::{
actions, actions,
@ -58,7 +59,7 @@ struct FeedbackRequestBody<'a> {
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct FeedbackEditor { pub(crate) struct FeedbackEditor {
system_specs: SystemSpecs, system_specs: SystemSpecs,
editor: ViewHandle<Editor>, editor: ViewHandle<FollowableEditor>,
project: ModelHandle<Project>, project: ModelHandle<Project>,
pub allow_submission: bool, pub allow_submission: bool,
} }
@ -71,8 +72,8 @@ impl FeedbackEditor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let editor = cx.add_view(|cx| { let editor = cx.add_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx); let mut editor = FollowableEditor::for_buffer(buffer, project.clone(), cx);
editor.set_vertical_scroll_margin(5, cx); editor.0.update(cx, |this, cx| this.set_vertical_scroll_margin(5, cx));
editor editor
}); });
@ -92,7 +93,7 @@ impl FeedbackEditor {
return Task::ready(Ok(())); return Task::ready(Ok(()));
} }
let feedback_text = self.editor.read(cx).text(cx); let feedback_text = self.editor.read(cx).0.read(cx).text(cx);
let feedback_char_count = feedback_text.chars().count(); let feedback_char_count = feedback_text.chars().count();
let feedback_text = feedback_text.trim().to_string(); let feedback_text = feedback_text.trim().to_string();
@ -339,7 +340,7 @@ impl Item for FeedbackEditor {
{ {
let buffer = self let buffer = self
.editor .editor
.read(cx) .read(cx).0.read(cx)
.buffer() .buffer()
.read(cx) .read(cx)
.as_singleton() .as_singleton()
@ -373,7 +374,7 @@ impl Item for FeedbackEditor {
} }
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> { fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
Editor::to_item_events(event) FollowableEditor::to_item_events(event)
} }
} }

View file

@ -10,12 +10,14 @@ doctest = false
[dependencies] [dependencies]
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
collections = { path = "../collections" } collections = { path = "../collections" }
fuzzy = { path = "../fuzzy" } fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
menu = { path = "../menu" } menu = { path = "../menu" }
picker = { path = "../picker" } picker = { path = "../picker" }
project = { path = "../project" } project = { path = "../project" }
project_types = { path = "../project_types" }
settings = { path = "../settings" } settings = { path = "../settings" }
text = { path = "../text" } text = { path = "../text" }
util = { path = "../util" } util = { path = "../util" }

View file

@ -1,11 +1,13 @@
use collections::HashMap; use collections::HashMap;
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; use editor::{scroll::autoscroll::Autoscroll, Bias};
use editor_extensions::FollowableEditor;
use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{ use gpui::{
actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; use project::{PathMatchCandidateSet, Project};
use project_types::{ProjectPath, WorktreeId};
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::{
@ -636,16 +638,22 @@ impl PickerDelegate for FileFinderDelegate {
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
let item = open_task.await.log_err()?; let item = open_task.await.log_err()?;
if let Some(row) = row { if let Some(row) = row {
if let Some(active_editor) = item.downcast::<Editor>() { if let Some(active_editor) = item.downcast::<FollowableEditor>() {
active_editor active_editor
.downgrade() .downgrade()
.update(&mut cx, |editor, cx| { .update(&mut cx, |editor, cx| {
let snapshot = editor.snapshot(cx).display_snapshot; let snapshot = editor
.0
.update(cx, |this, cx| this.snapshot(cx).display_snapshot);
let point = snapshot let point = snapshot
.buffer_snapshot .buffer_snapshot
.clip_point(Point::new(row, col), Bias::Left); .clip_point(Point::new(row, col), Bias::Left);
editor.change_selections(Some(Autoscroll::center()), cx, |s| { editor.0.update(cx, |this, cx| {
s.select_ranges([point..point]) this.change_selections(
Some(Autoscroll::center()),
cx,
|s| s.select_ranges([point..point]),
)
}); });
}) })
.log_err(); .log_err();

View file

@ -11,6 +11,7 @@ doctest = false
[dependencies] [dependencies]
collections = { path = "../collections" } collections = { path = "../collections" }
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
settings = { path = "../settings" } settings = { path = "../settings" }
theme = { path = "../theme" } theme = { path = "../theme" }
language = { path = "../language" } language = { path = "../language" }

View file

@ -1,5 +1,6 @@
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
use editor::{Editor, MoveToEnd}; use editor::{Editor, MoveToEnd};
use editor_extensions::FollowableEditor;
use futures::{channel::mpsc, StreamExt}; use futures::{channel::mpsc, StreamExt};
use gpui::{ use gpui::{
actions, actions,
@ -49,7 +50,7 @@ struct LanguageServerRpcState {
} }
pub struct LspLogView { pub struct LspLogView {
pub(crate) editor: ViewHandle<Editor>, pub(crate) editor: ViewHandle<FollowableEditor>,
editor_subscription: Subscription, editor_subscription: Subscription,
log_store: ModelHandle<LogStore>, log_store: ModelHandle<LogStore>,
current_server_id: Option<LanguageServerId>, current_server_id: Option<LanguageServerId>,
@ -388,9 +389,11 @@ impl LspLogView {
} else { } else {
this.current_server_id = None; this.current_server_id = None;
this.editor.update(cx, |editor, cx| { this.editor.update(cx, |editor, cx| {
editor.set_read_only(false); editor.0.update(cx, |this, cx| {
editor.clear(cx); this.set_read_only(false);
editor.set_read_only(true); this.clear(cx);
this.set_read_only(true);
});
}); });
cx.notify(); cx.notify();
} }
@ -417,10 +420,12 @@ impl LspLogView {
|| (!*is_rpc && !log_view.is_showing_rpc_trace) || (!*is_rpc && !log_view.is_showing_rpc_trace)
{ {
log_view.editor.update(cx, |editor, cx| { log_view.editor.update(cx, |editor, cx| {
editor.set_read_only(false); editor.0.update(cx, |this, cx| {
editor.handle_input(entry.trim(), cx); this.set_read_only(false);
editor.handle_input("\n", cx); this.handle_input(entry.trim(), cx);
editor.set_read_only(true); this.handle_input("\n", cx);
this.set_read_only(true);
});
}); });
} }
} }
@ -445,13 +450,15 @@ impl LspLogView {
fn editor_for_logs( fn editor_for_logs(
log_contents: String, log_contents: String,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> (ViewHandle<Editor>, Subscription) { ) -> (ViewHandle<FollowableEditor>, Subscription) {
let editor = cx.add_view(|cx| { let editor = cx.add_view(|cx| {
let mut editor = Editor::multi_line(None, cx); FollowableEditor(cx.add_view(|cx| {
editor.set_text(log_contents, cx); let mut editor = Editor::multi_line(None, cx);
editor.move_to_end(&MoveToEnd, cx); editor.set_text(log_contents, cx);
editor.set_read_only(true); editor.move_to_end(&MoveToEnd, cx);
editor editor.set_read_only(true);
editor
}))
}); });
let editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); let editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()));
(editor, editor_subscription) (editor, editor_subscription)
@ -534,6 +541,8 @@ impl LspLogView {
let (editor, editor_subscription) = Self::editor_for_logs(rpc_log, cx); let (editor, editor_subscription) = Self::editor_for_logs(rpc_log, cx);
let language = self.project.read(cx).languages().language_for_name("JSON"); let language = self.project.read(cx).languages().language_for_name("JSON");
editor editor
.read(cx)
.0
.read(cx) .read(cx)
.buffer() .buffer()
.read(cx) .read(cx)
@ -620,7 +629,7 @@ impl Item for LspLogView {
} }
impl SearchableItem for LspLogView { impl SearchableItem for LspLogView {
type Match = <Editor as SearchableItem>::Match; type Match = <FollowableEditor as SearchableItem>::Match;
fn to_search_event( fn to_search_event(
&mut self, &mut self,
@ -811,9 +820,11 @@ impl View for LspLogToolbarItemView {
if let Some(log_view) = this.log_view.as_ref() { if let Some(log_view) = this.log_view.as_ref() {
log_view.update(cx, |log_view, cx| { log_view.update(cx, |log_view, cx| {
log_view.editor.update(cx, |editor, cx| { log_view.editor.update(cx, |editor, cx| {
editor.set_read_only(false); editor.0.update(cx, |this, cx| {
editor.clear(cx); this.set_read_only(false);
editor.set_read_only(true); this.clear(cx);
this.set_read_only(true);
});
}); });
}) })
} }
@ -1015,7 +1026,7 @@ impl Entity for LogStore {
} }
impl Entity for LspLogView { impl Entity for LspLogView {
type Event = editor::Event; type Event = <FollowableEditor as Entity>::Event;
} }
impl Entity for LspLogToolbarItemView { impl Entity for LspLogToolbarItemView {

View file

@ -34,6 +34,7 @@ language = { path = "../language" }
lsp = { path = "../lsp" } lsp = { path = "../lsp" }
node_runtime = { path = "../node_runtime" } node_runtime = { path = "../node_runtime" }
prettier = { path = "../prettier" } prettier = { path = "../prettier" }
project_types = {path = "../project_types"}
rpc = { path = "../rpc" } rpc = { path = "../rpc" }
settings = { path = "../settings" } settings = { path = "../settings" }
sum_tree = { path = "../sum_tree" } sum_tree = { path = "../sum_tree" }

View file

@ -1,6 +1,6 @@
mod ignore; mod ignore;
mod lsp_command; mod lsp_command;
pub mod project_settings; pub use project_types::project_settings;
pub mod search; pub mod search;
pub mod terminals; pub mod terminals;
pub mod worktree; pub mod worktree;
@ -29,11 +29,8 @@ use gpui::{
executor::Background, AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, executor::Background, AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity,
ModelContext, ModelHandle, Task, WeakModelHandle, ModelContext, ModelHandle, Task, WeakModelHandle,
}; };
use itertools::Itertools;
use language::{ use language::{
language_settings::{ language_settings::{language_settings, FormatOnSave, Formatter, LanguageSettings},
language_settings, FormatOnSave, Formatter, InlayHintKind, LanguageSettings,
},
point_to_lsp, point_to_lsp,
proto::{ proto::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
@ -47,8 +44,8 @@ use language::{
}; };
use log::error; use log::error;
use lsp::{ use lsp::{
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, LanguageServer,
DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf, LanguageServerBinary, LanguageServerId, OneOf,
}; };
use lsp_command::*; use lsp_command::*;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
@ -56,6 +53,7 @@ use parking_lot::Mutex;
use postage::watch; use postage::watch;
use prettier::Prettier; use prettier::Prettier;
use project_settings::{LspSettings, ProjectSettings}; use project_settings::{LspSettings, ProjectSettings};
use project_types::*;
use rand::prelude::*; use rand::prelude::*;
use search::SearchQuery; use search::SearchQuery;
use serde::Serialize; use serde::Serialize;
@ -338,135 +336,12 @@ pub struct LanguageServerProgress {
pub last_update_at: Instant, pub last_update_at: Instant,
} }
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ProjectPath {
pub worktree_id: WorktreeId,
pub path: Arc<Path>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)] #[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
pub struct DiagnosticSummary { pub struct DiagnosticSummary {
pub error_count: usize, pub error_count: usize,
pub warning_count: usize, pub warning_count: usize,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Location {
pub buffer: ModelHandle<Buffer>,
pub range: Range<language::Anchor>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlayHint {
pub position: language::Anchor,
pub label: InlayHintLabel,
pub kind: Option<InlayHintKind>,
pub padding_left: bool,
pub padding_right: bool,
pub tooltip: Option<InlayHintTooltip>,
pub resolve_state: ResolveState,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveState {
Resolved,
CanResolve(LanguageServerId, Option<lsp::LSPAny>),
Resolving,
}
impl InlayHint {
pub fn text(&self) -> String {
match &self.label {
InlayHintLabel::String(s) => s.to_owned(),
InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InlayHintLabel {
String(String),
LabelParts(Vec<InlayHintLabelPart>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlayHintLabelPart {
pub value: String,
pub tooltip: Option<InlayHintLabelPartTooltip>,
pub location: Option<(LanguageServerId, lsp::Location)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InlayHintTooltip {
String(String),
MarkupContent(MarkupContent),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InlayHintLabelPartTooltip {
String(String),
MarkupContent(MarkupContent),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MarkupContent {
pub kind: HoverBlockKind,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct LocationLink {
pub origin: Option<Location>,
pub target: Location,
}
#[derive(Debug)]
pub struct DocumentHighlight {
pub range: Range<language::Anchor>,
pub kind: DocumentHighlightKind,
}
#[derive(Clone, Debug)]
pub struct Symbol {
pub language_server_name: LanguageServerName,
pub source_worktree_id: WorktreeId,
pub path: ProjectPath,
pub label: CodeLabel,
pub name: String,
pub kind: lsp::SymbolKind,
pub range: Range<Unclipped<PointUtf16>>,
pub signature: [u8; 32],
}
#[derive(Clone, Debug, PartialEq)]
pub struct HoverBlock {
pub text: String,
pub kind: HoverBlockKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HoverBlockKind {
PlainText,
Markdown,
Code { language: String },
}
#[derive(Debug)]
pub struct Hover {
pub contents: Vec<HoverBlock>,
pub range: Option<Range<language::Anchor>>,
pub language: Option<Arc<Language>>,
}
impl Hover {
pub fn is_empty(&self) -> bool {
self.contents.iter().all(|block| block.text.is_empty())
}
}
#[derive(Default)]
pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
impl DiagnosticSummary { impl DiagnosticSummary {
fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self { fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
let mut this = Self { let mut this = Self {
@ -528,26 +403,11 @@ impl ProjectEntryId {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormatTrigger {
Save,
Manual,
}
struct ProjectLspAdapterDelegate { struct ProjectLspAdapterDelegate {
project: ModelHandle<Project>, project: ModelHandle<Project>,
http_client: Arc<dyn HttpClient>, http_client: Arc<dyn HttpClient>,
} }
impl FormatTrigger {
fn from_proto(value: i32) -> FormatTrigger {
match value {
0 => FormatTrigger::Save,
1 => FormatTrigger::Manual,
_ => FormatTrigger::Save,
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum SearchMatchCandidate { enum SearchMatchCandidate {
OpenBuffer { OpenBuffer {
@ -9021,15 +8881,6 @@ impl Entity for Project {
} }
} }
impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
fn from((worktree_id, path): (WorktreeId, P)) -> Self {
Self {
worktree_id,
path: path.as_ref().into(),
}
}
}
impl ProjectLspAdapterDelegate { impl ProjectLspAdapterDelegate {
fn new(project: &Project, cx: &ModelContext<Project>) -> Arc<Self> { fn new(project: &Project, cx: &ModelContext<Project>) -> Arc<Self> {
Arc::new(Self { Arc::new(Self {

View file

@ -36,6 +36,7 @@ use postage::{
prelude::{Sink as _, Stream as _}, prelude::{Sink as _, Stream as _},
watch, watch,
}; };
use project_types::WorktreeId;
use smol::channel::{self, Sender}; use smol::channel::{self, Sender};
use std::{ use std::{
any::Any, any::Any,
@ -56,10 +57,6 @@ use std::{
}; };
use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
use util::{paths::HOME, ResultExt}; use util::{paths::HOME, ResultExt};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub struct WorktreeId(usize);
pub enum Worktree { pub enum Worktree {
Local(LocalWorktree), Local(LocalWorktree),
Remote(RemoteWorktree), Remote(RemoteWorktree),
@ -407,7 +404,7 @@ impl Worktree {
) -> ModelHandle<Self> { ) -> ModelHandle<Self> {
cx.add_model(|cx: &mut ModelContext<Self>| { cx.add_model(|cx: &mut ModelContext<Self>| {
let snapshot = Snapshot { let snapshot = Snapshot {
id: WorktreeId(worktree.id as usize), id: WorktreeId::from_usize(worktree.id as usize),
abs_path: Arc::from(PathBuf::from(worktree.abs_path)), abs_path: Arc::from(PathBuf::from(worktree.abs_path)),
root_name: worktree.root_name.clone(), root_name: worktree.root_name.clone(),
root_char_bag: worktree root_char_bag: worktree
@ -2493,30 +2490,6 @@ async fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
Ok(builder.build()?) Ok(builder.build()?)
} }
impl WorktreeId {
pub fn from_usize(handle_id: usize) -> Self {
Self(handle_id)
}
pub(crate) fn from_proto(id: u64) -> Self {
Self(id as usize)
}
pub fn to_proto(&self) -> u64 {
self.0 as u64
}
pub fn to_usize(&self) -> usize {
self.0
}
}
impl fmt::Display for WorktreeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Deref for Worktree { impl Deref for Worktree {
type Target = Snapshot; type Target = Snapshot;

View file

@ -17,6 +17,7 @@ editor = { path = "../editor" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
menu = { path = "../menu" } menu = { path = "../menu" }
project = { path = "../project" } project = { path = "../project" }
project_types = { path = "../project_types" }
settings = { path = "../settings" } settings = { path = "../settings" }
theme = { path = "../theme" } theme = { path = "../theme" }
util = { path = "../util" } util = { path = "../util" }

View file

@ -23,9 +23,10 @@ use gpui::{
}; };
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{ use project::{
repository::GitFileStatus, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath, repository::GitFileStatus, Entry, EntryKind, Fs, Project, ProjectEntryId,
Worktree, WorktreeId, Worktree,
}; };
use project_types::{ProjectPath, WorktreeId};
use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings}; use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::SettingsStore; use settings::SettingsStore;

View file

@ -10,10 +10,12 @@ doctest = false
[dependencies] [dependencies]
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
fuzzy = { path = "../fuzzy" } fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
picker = { path = "../picker" } picker = { path = "../picker" }
project = { path = "../project" } project = { path = "../project" }
project_types = { path = "../project_types" }
text = { path = "../text" } text = { path = "../text" }
settings = { path = "../settings" } settings = { path = "../settings" }
workspace = { path = "../workspace" } workspace = { path = "../workspace" }

View file

@ -1,14 +1,16 @@
use editor::{ use editor::{
combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll, combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll,
styled_runs_for_code_label, Bias, Editor, styled_runs_for_code_label, Bias,
}; };
use editor_extensions::FollowableEditor;
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle, actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
}; };
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate, PickerEvent}; use picker::{Picker, PickerDelegate, PickerEvent};
use project::{Project, Symbol}; use project::{Project};
use project_types::Symbol;
use std::{borrow::Cow, cmp::Reverse, sync::Arc}; use std::{borrow::Cow, cmp::Reverse, sync::Arc};
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::Workspace;
@ -123,15 +125,15 @@ impl PickerDelegate for ProjectSymbolsDelegate {
.clip_point_utf16(symbol.range.start, Bias::Left); .clip_point_utf16(symbol.range.start, Bias::Left);
let editor = if secondary { let editor = if secondary {
workspace.split_project_item::<Editor>(buffer, cx) workspace.split_project_item::<FollowableEditor>(buffer, cx)
} else { } else {
workspace.open_project_item::<Editor>(buffer, cx) workspace.open_project_item::<FollowableEditor>(buffer, cx)
}; };
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::center()), cx, |s| { editor.0.update(cx, |this, cx| this.change_selections(Some(Autoscroll::center()), cx, |s| {
s.select_ranges([position..position]) s.select_ranges([position..position])
}); }));
}); });
})?; })?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())

View file

@ -0,0 +1,19 @@
[package]
name = "project_types"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
gpui = {path = "../gpui"}
lsp = {path = "../lsp"}
language = {path = "../language"}
itertools = "0.10"
settings = {path = "../settings"}
serde.workspace = true
serde_json.workspace = true
schemars.workspace = true
collections = {path = "../collections"}
rpc = {path = "../rpc"}

View file

@ -0,0 +1,185 @@
pub mod project_settings;
use collections::HashMap;
use gpui::ModelHandle;
use itertools::Itertools;
use language::{
language_settings::InlayHintKind, Buffer, CodeLabel, Language, LanguageServerId,
LanguageServerName, PointUtf16, Unclipped,
};
use lsp::DocumentHighlightKind;
use std::{ops::Range, path::Path, sync::Arc};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
pub struct WorktreeId(usize);
impl WorktreeId {
pub fn from_usize(handle_id: usize) -> Self {
Self(handle_id)
}
pub fn from_proto(id: u64) -> Self {
Self(id as usize)
}
pub fn to_proto(&self) -> u64 {
self.0 as u64
}
pub fn to_usize(&self) -> usize {
self.0
}
}
impl std::fmt::Display for WorktreeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ProjectPath {
pub worktree_id: WorktreeId,
pub path: Arc<Path>,
}
impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
fn from((worktree_id, path): (WorktreeId, P)) -> Self {
Self {
worktree_id,
path: path.as_ref().into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Location {
pub buffer: ModelHandle<Buffer>,
pub range: Range<language::Anchor>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlayHint {
pub position: language::Anchor,
pub label: InlayHintLabel,
pub kind: Option<InlayHintKind>,
pub padding_left: bool,
pub padding_right: bool,
pub tooltip: Option<InlayHintTooltip>,
pub resolve_state: ResolveState,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveState {
Resolved,
CanResolve(LanguageServerId, Option<lsp::LSPAny>),
Resolving,
}
impl InlayHint {
pub fn text(&self) -> String {
match &self.label {
InlayHintLabel::String(s) => s.to_owned(),
InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InlayHintLabel {
String(String),
LabelParts(Vec<InlayHintLabelPart>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlayHintLabelPart {
pub value: String,
pub tooltip: Option<InlayHintLabelPartTooltip>,
pub location: Option<(LanguageServerId, lsp::Location)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InlayHintTooltip {
String(String),
MarkupContent(MarkupContent),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InlayHintLabelPartTooltip {
String(String),
MarkupContent(MarkupContent),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MarkupContent {
pub kind: HoverBlockKind,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct LocationLink {
pub origin: Option<Location>,
pub target: Location,
}
#[derive(Debug)]
pub struct DocumentHighlight {
pub range: Range<language::Anchor>,
pub kind: DocumentHighlightKind,
}
#[derive(Clone, Debug)]
pub struct Symbol {
pub language_server_name: LanguageServerName,
pub source_worktree_id: WorktreeId,
pub path: ProjectPath,
pub label: CodeLabel,
pub name: String,
pub kind: lsp::SymbolKind,
pub range: Range<Unclipped<PointUtf16>>,
pub signature: [u8; 32],
}
#[derive(Clone, Debug, PartialEq)]
pub struct HoverBlock {
pub text: String,
pub kind: HoverBlockKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HoverBlockKind {
PlainText,
Markdown,
Code { language: String },
}
#[derive(Debug)]
pub struct Hover {
pub contents: Vec<HoverBlock>,
pub range: Option<Range<language::Anchor>>,
pub language: Option<Arc<Language>>,
}
impl Hover {
pub fn is_empty(&self) -> bool {
self.contents.iter().all(|block| block.text.is_empty())
}
}
#[derive(Default)]
pub struct ProjectTransaction(pub HashMap<ModelHandle<Buffer>, language::Transaction>);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormatTrigger {
Save,
Manual,
}
impl FormatTrigger {
pub fn from_proto(value: i32) -> FormatTrigger {
match value {
0 => FormatTrigger::Save,
1 => FormatTrigger::Manual,
_ => FormatTrigger::Save,
}
}
}

View file

@ -12,6 +12,7 @@ doctest = false
bitflags = "1" bitflags = "1"
collections = { path = "../collections" } collections = { path = "../collections" }
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
language = { path = "../language" } language = { path = "../language" }
menu = { path = "../menu" } menu = { path = "../menu" }

View file

@ -9,9 +9,10 @@ use crate::{
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use collections::HashMap; use collections::HashMap;
use editor::{ use editor::{
items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
SelectAll, MAX_TAB_TITLE_LEN, SelectAll, MAX_TAB_TITLE_LEN,
}; };
use editor_extensions::{active_match_index, FollowableEditor};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
actions, actions,
@ -134,7 +135,7 @@ pub struct ProjectSearchView {
model: ModelHandle<ProjectSearch>, model: ModelHandle<ProjectSearch>,
query_editor: ViewHandle<Editor>, query_editor: ViewHandle<Editor>,
replacement_editor: ViewHandle<Editor>, replacement_editor: ViewHandle<Editor>,
results_editor: ViewHandle<Editor>, results_editor: ViewHandle<FollowableEditor>,
semantic_state: Option<SemanticState>, semantic_state: Option<SemanticState>,
semantic_permissioned: Option<bool>, semantic_permissioned: Option<bool>,
search_options: SearchOptions, search_options: SearchOptions,
@ -650,8 +651,8 @@ impl Item for ProjectSearchView {
} }
fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) { fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
self.results_editor.update(cx, |editor, _| { self.results_editor.update(cx, |editor, cx| {
editor.set_nav_history(Some(nav_history)); editor.set_nav_history(nav_history,cx);
}); });
} }
@ -665,7 +666,7 @@ impl Item for ProjectSearchView {
ViewEvent::UpdateTab => { ViewEvent::UpdateTab => {
smallvec::smallvec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab] smallvec::smallvec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab]
} }
ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event), ViewEvent::EditorEvent(editor_event) => FollowableEditor::to_item_events(editor_event),
ViewEvent::Dismiss => smallvec::smallvec![ItemEvent::CloseItem], ViewEvent::Dismiss => smallvec::smallvec![ItemEvent::CloseItem],
_ => SmallVec::new(), _ => SmallVec::new(),
} }
@ -965,8 +966,8 @@ impl ProjectSearchView {
editor editor
}); });
let results_editor = cx.add_view(|cx| { let results_editor = cx.add_view(|cx| {
let mut editor = Editor::for_multibuffer(excerpts, Some(project.clone()), cx); let mut editor = FollowableEditor::for_multibuffer(excerpts, project.clone(), cx);
editor.set_searchable(false); editor.0.update(cx, |this, _| this.set_searchable(false));
editor editor
}); });
cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab)) cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab))
@ -1100,7 +1101,7 @@ impl ProjectSearchView {
.or_else(|| workspace.item_of_type::<ProjectSearchView>(cx)); .or_else(|| workspace.item_of_type::<ProjectSearchView>(cx));
let query = workspace.active_item(cx).and_then(|item| { let query = workspace.active_item(cx).and_then(|item| {
let editor = item.act_as::<Editor>(cx)?; let editor = item.act_as::<FollowableEditor>(cx)?;
let query = editor.query_suggestion(cx); let query = editor.query_suggestion(cx);
if query.is_empty() { if query.is_empty() {
None None
@ -1246,11 +1247,14 @@ impl ProjectSearchView {
let range_to_select = match_ranges[new_index].clone(); let range_to_select = match_ranges[new_index].clone();
self.results_editor.update(cx, |editor, cx| { self.results_editor.update(cx, |editor, cx| {
let range_to_select = editor.range_for_match(&range_to_select); editor.0.update(cx, |this, cx| {
editor.unfold_ranges([range_to_select.clone()], false, true, cx); let range_to_select = this.range_for_match(&range_to_select);
editor.change_selections(Some(Autoscroll::fit()), cx, |s| { this.unfold_ranges([range_to_select.clone()], false, true, cx);
s.select_ranges([range_to_select]) this.change_selections(Some(Autoscroll::fit()), cx, |s| {
}); s.select_ranges([range_to_select])
});
})
}); });
} }
} }
@ -1291,16 +1295,16 @@ impl ProjectSearchView {
let range_to_select = match_ranges let range_to_select = match_ranges
.first() .first()
.clone() .clone()
.map(|range| editor.range_for_match(range)); .map(|range| editor.0.update(cx, |this, cx| this.range_for_match(range)));
editor.change_selections(Some(Autoscroll::fit()), cx, |s| { editor.0.update(cx, |this, cx| this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(range_to_select) s.select_ranges(range_to_select)
}); }));
} }
editor.highlight_background::<Self>( editor.0.update(cx, |this, cx| this.highlight_background::<Self>(
match_ranges, match_ranges,
|theme| theme.search.match_background, |theme| theme.search.match_background,
cx, cx,
); ));
}); });
if is_new_search && self.query_editor.is_focused(cx) { if is_new_search && self.query_editor.is_focused(cx) {
self.focus_results_editor(cx); self.focus_results_editor(cx);
@ -1315,8 +1319,8 @@ impl ProjectSearchView {
let results_editor = self.results_editor.read(cx); let results_editor = self.results_editor.read(cx);
let new_index = active_match_index( let new_index = active_match_index(
&self.model.read(cx).match_ranges, &self.model.read(cx).match_ranges,
&results_editor.selections.newest_anchor().head(), &results_editor.0.read(cx).selections.newest_anchor().head(),
&results_editor.buffer().read(cx).snapshot(cx), &results_editor.0.read(cx).buffer().read(cx).snapshot(cx),
); );
if self.active_match_index != new_index { if self.active_match_index != new_index {
self.active_match_index = new_index; self.active_match_index = new_index;

View file

@ -14,6 +14,7 @@ collections = { path = "../collections" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
language = { path = "../language" } language = { path = "../language" }
project = { path = "../project" } project = { path = "../project" }
project_types = { path = "../project_types" }
workspace = { path = "../workspace" } workspace = { path = "../workspace" }
util = { path = "../util" } util = { path = "../util" }
picker = { path = "../picker" } picker = { path = "../picker" }

View file

@ -21,7 +21,8 @@ use ordered_float::OrderedFloat;
use parking_lot::Mutex; use parking_lot::Mutex;
use parsing::{CodeContextRetriever, Span, SpanDigest, PARSEABLE_ENTIRE_FILE_TYPES}; use parsing::{CodeContextRetriever, Span, SpanDigest, PARSEABLE_ENTIRE_FILE_TYPES};
use postage::watch; use postage::watch;
use project::{Fs, PathChange, Project, ProjectEntryId, Worktree, WorktreeId}; use project::{Fs, PathChange, Project, ProjectEntryId, Worktree};
use project_types::WorktreeId;
use smol::channel; use smol::channel;
use std::{ use std::{
cmp::Reverse, cmp::Reverse,

View file

@ -32,10 +32,12 @@ language = { path = "../language" }
menu = { path = "../menu" } menu = { path = "../menu" }
node_runtime = { path = "../node_runtime" } node_runtime = { path = "../node_runtime" }
project = { path = "../project" } project = { path = "../project" }
project_types = {path = "../project_types"}
settings = { path = "../settings" } settings = { path = "../settings" }
terminal = { path = "../terminal" } terminal = { path = "../terminal" }
theme = { path = "../theme" } theme = { path = "../theme" }
util = { path = "../util" } util = { path = "../util" }
workspace_types = {path = "../workspace_types"}
async-recursion = "1.0.0" async-recursion = "1.0.0"
itertools = "0.10" itertools = "0.10"

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders, pane, searchable::SearchableItemHandle, FollowableItemBuilders, ItemNavHistory, Pane,
ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
}; };
use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings}; use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
use anyhow::Result; use anyhow::Result;
@ -14,7 +14,8 @@ use gpui::{
fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View, fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
ViewContext, ViewHandle, WeakViewHandle, WindowContext, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
}; };
use project::{Project, ProjectEntryId, ProjectPath}; use project::{Project, ProjectEntryId};
use project_types::ProjectPath;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::Setting; use settings::Setting;
@ -34,6 +35,7 @@ use std::{
time::Duration, time::Duration,
}; };
use theme::Theme; use theme::Theme;
use workspace_types::ItemId;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ItemSettings { pub struct ItemSettings {

View file

@ -28,7 +28,8 @@ use gpui::{
ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle, ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
WindowContext, WindowContext,
}; };
use project::{Project, ProjectEntryId, ProjectPath}; use project::{Project, ProjectEntryId};
use project_types::ProjectPath;
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
any::Any, any::Any,
@ -1963,20 +1964,26 @@ impl View for Pane {
} }
} }
impl ItemNavHistory { impl workspace_types::NavigationHistorySink for ItemNavHistory {
pub fn push<D: 'static + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) { fn push_any(&mut self, data: Option<Box<dyn Any>>, cx: &mut WindowContext) {
self.history.push(data, self.item.clone(), cx); self.history.push(data, self.item.clone(), cx);
} }
pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
self.history.pop(NavigationMode::GoingBack, cx)
}
pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
self.history.pop(NavigationMode::GoingForward, cx)
}
} }
trait NavigationHistorySource {
fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry>;
fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry>;
}
impl NavigationHistorySource for ItemNavHistory {
fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
self.history.pop(NavigationMode::GoingForward, cx)
}
fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
self.history.pop(NavigationMode::GoingBack, cx)
}
}
impl NavHistory { impl NavHistory {
pub fn for_each_entry( pub fn for_each_entry(
&self, &self,
@ -2035,9 +2042,9 @@ impl NavHistory {
entry entry
} }
pub fn push<D: 'static + Any>( pub fn push(
&mut self, &mut self,
data: Option<D>, data: Option<Box<dyn Any>>,
item: Rc<dyn WeakItemHandle>, item: Rc<dyn WeakItemHandle>,
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
@ -2050,7 +2057,7 @@ impl NavHistory {
} }
state.backward_stack.push_back(NavigationEntry { state.backward_stack.push_back(NavigationEntry {
item, item,
data: data.map(|data| Box::new(data) as Box<dyn Any>), data,
timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
}); });
state.forward_stack.clear(); state.forward_stack.clear();
@ -2061,7 +2068,7 @@ impl NavHistory {
} }
state.forward_stack.push_back(NavigationEntry { state.forward_stack.push_back(NavigationEntry {
item, item,
data: data.map(|data| Box::new(data) as Box<dyn Any>), data,
timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
}); });
} }
@ -2071,7 +2078,7 @@ impl NavHistory {
} }
state.backward_stack.push_back(NavigationEntry { state.backward_stack.push_back(NavigationEntry {
item, item,
data: data.map(|data| Box::new(data) as Box<dyn Any>), data,
timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
}); });
} }
@ -2081,7 +2088,7 @@ impl NavHistory {
} }
state.closed_stack.push_back(NavigationEntry { state.closed_stack.push_back(NavigationEntry {
item, item,
data: data.map(|data| Box::new(data) as Box<dyn Any>), data,
timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst), timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
}); });
} }

View file

@ -9,9 +9,9 @@ use gpui::{
AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle, AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
}; };
use project::Project; use project::Project;
use serde::Deserialize;
use std::{cell::RefCell, rc::Rc, sync::Arc}; use std::{cell::RefCell, rc::Rc, sync::Arc};
use theme::Theme; use theme::Theme;
use workspace_types::SplitDirection;
const HANDLE_HITBOX_SIZE: f32 = 4.0; const HANDLE_HITBOX_SIZE: f32 = 4.0;
const HORIZONTAL_MIN_SIZE: f32 = 80.; const HORIZONTAL_MIN_SIZE: f32 = 80.;
@ -534,59 +534,6 @@ impl PaneAxis {
} }
} }
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub enum SplitDirection {
Up,
Down,
Left,
Right,
}
impl SplitDirection {
pub fn all() -> [Self; 4] {
[Self::Up, Self::Down, Self::Left, Self::Right]
}
pub fn edge(&self, rect: RectF) -> f32 {
match self {
Self::Up => rect.min_y(),
Self::Down => rect.max_y(),
Self::Left => rect.min_x(),
Self::Right => rect.max_x(),
}
}
// Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection
pub fn along_edge(&self, rect: RectF, size: f32) -> RectF {
match self {
Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)),
Self::Down => RectF::new(
rect.lower_left() - Vector2F::new(0., size),
Vector2F::new(rect.width(), size),
),
Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())),
Self::Right => RectF::new(
rect.upper_right() - Vector2F::new(size, 0.),
Vector2F::new(size, rect.height()),
),
}
}
pub fn axis(&self) -> Axis {
match self {
Self::Up | Self::Down => Axis::Vertical,
Self::Left | Self::Right => Axis::Horizontal,
}
}
pub fn increasing(&self) -> bool {
match self {
Self::Left | Self::Up => false,
Self::Down | Self::Right => true,
}
}
}
mod element { mod element {
use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};

View file

@ -15,6 +15,7 @@ use std::{
}; };
use util::ResultExt; use util::ResultExt;
use uuid::Uuid; use uuid::Uuid;
use workspace_types::ItemId;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceLocation(Arc<Vec<PathBuf>>); pub struct WorkspaceLocation(Arc<Vec<PathBuf>>);
@ -284,7 +285,6 @@ impl SerializedPane {
pub type GroupId = i64; pub type GroupId = i64;
pub type PaneId = i64; pub type PaneId = i64;
pub type ItemId = usize;
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct SerializedItem { pub struct SerializedItem {

View file

@ -17,6 +17,7 @@ use std::{
borrow::Cow, borrow::Cow,
sync::{Arc, Weak}, sync::{Arc, Weak},
}; };
use workspace_types::NavigationHistorySink;
pub enum Event { pub enum Event {
Close, Close,
@ -100,7 +101,7 @@ impl Item for SharedScreen {
} }
fn deactivated(&mut self, cx: &mut ViewContext<Self>) { fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
if let Some(nav_history) = self.nav_history.as_mut() { if let Some(nav_history) = self.nav_history.as_mut() {
nav_history.push::<()>(None, cx); nav_history.push_any(None, cx);
} }
} }

View file

@ -24,7 +24,6 @@ use futures::{
FutureExt, StreamExt, FutureExt, StreamExt,
}; };
use gpui::{ use gpui::{
actions,
elements::*, elements::*,
geometry::{ geometry::{
rect::RectF, rect::RectF,
@ -67,12 +66,10 @@ use notifications::{NotificationHandle, NotifyResultExt};
pub use pane::*; pub use pane::*;
pub use pane_group::*; pub use pane_group::*;
use persistence::{model::SerializedItem, DB}; use persistence::{model::SerializedItem, DB};
pub use persistence::{ pub use persistence::{model::WorkspaceLocation, WorkspaceDb, DB as WORKSPACE_DB};
model::{ItemId, WorkspaceLocation},
WorkspaceDb, DB as WORKSPACE_DB,
};
use postage::prelude::Stream; use postage::prelude::Stream;
use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use project::{Project, ProjectEntryId, Worktree};
use project_types::{ProjectPath, WorktreeId};
use serde::Deserialize; use serde::Deserialize;
use shared_screen::SharedScreen; use shared_screen::SharedScreen;
use status_bar::StatusBar; use status_bar::StatusBar;
@ -81,6 +78,7 @@ use theme::{Theme, ThemeSettings};
pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
use util::ResultExt; use util::ResultExt;
pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings}; pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
pub use workspace_types::*;
lazy_static! { lazy_static! {
static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE") static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
@ -116,36 +114,6 @@ impl<T: Modal> ModalHandle for ViewHandle<T> {
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct RemoveWorktreeFromProject(pub WorktreeId); pub struct RemoveWorktreeFromProject(pub WorktreeId);
actions!(
workspace,
[
Open,
NewFile,
NewWindow,
CloseWindow,
CloseInactiveTabsAndPanes,
AddFolderToProject,
Unfollow,
SaveAs,
ReloadActiveItem,
ActivatePreviousPane,
ActivateNextPane,
FollowNextCollaborator,
NewTerminal,
NewCenterTerminal,
ToggleTerminalFocus,
NewSearch,
Feedback,
Restart,
Welcome,
ToggleZoom,
ToggleLeftDock,
ToggleRightDock,
ToggleBottomDock,
CloseAllDocks,
]
);
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub struct OpenPaths { pub struct OpenPaths {
pub paths: Vec<PathBuf>, pub paths: Vec<PathBuf>,
@ -246,8 +214,6 @@ impl_actions!(
] ]
); );
pub type WorkspaceId = i64;
pub fn init_settings(cx: &mut AppContext) { pub fn init_settings(cx: &mut AppContext) {
settings::register::<WorkspaceSettings>(cx); settings::register::<WorkspaceSettings>(cx);
settings::register::<item::ItemSettings>(cx); settings::register::<item::ItemSettings>(cx);
@ -598,12 +564,6 @@ struct ActiveModal {
previously_focused_view_id: Option<usize>, previously_focused_view_id: Option<usize>,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ViewId {
pub creator: PeerId,
pub id: u64,
}
#[derive(Default)] #[derive(Default)]
struct FollowerState { struct FollowerState {
leader_id: PeerId, leader_id: PeerId,
@ -4071,24 +4031,6 @@ impl Entity for WorkspaceStore {
type Event = (); type Event = ();
} }
impl ViewId {
pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
Ok(Self {
creator: message
.creator
.ok_or_else(|| anyhow!("creator is missing"))?,
id: message.id,
})
}
pub(crate) fn to_proto(&self) -> proto::ViewId {
proto::ViewId {
creator: Some(self.creator),
id: self.id,
}
}
}
pub trait WorkspaceHandle { pub trait WorkspaceHandle {
fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>; fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
} }

View file

@ -0,0 +1,16 @@
[package]
name = "workspace_types"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
gpui = {path = "../gpui"}
text = {path = "../text"}
lsp = {path = "../lsp"}
rpc = {path = "../rpc"}
language = {path = "../language"}
project_types = {path = "../project_types"}
serde.workspace = true

View file

@ -0,0 +1,131 @@
use std::any::Any;
use anyhow::anyhow;
use gpui::{
actions,
geometry::{rect::RectF, vector::Vector2F},
Axis, WindowContext,
};
use serde::Deserialize;
actions!(
workspace,
[
Open,
NewFile,
NewWindow,
CloseWindow,
CloseInactiveTabsAndPanes,
AddFolderToProject,
Unfollow,
SaveAs,
ReloadActiveItem,
ActivatePreviousPane,
ActivateNextPane,
FollowNextCollaborator,
NewTerminal,
NewCenterTerminal,
ToggleTerminalFocus,
NewSearch,
Feedback,
Restart,
Welcome,
ToggleZoom,
ToggleLeftDock,
ToggleRightDock,
ToggleBottomDock,
CloseAllDocks,
]
);
pub type WorkspaceId = i64;
pub type ItemId = usize;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub enum SplitDirection {
Up,
Down,
Left,
Right,
}
impl SplitDirection {
pub fn all() -> [Self; 4] {
[Self::Up, Self::Down, Self::Left, Self::Right]
}
pub fn edge(&self, rect: RectF) -> f32 {
match self {
Self::Up => rect.min_y(),
Self::Down => rect.max_y(),
Self::Left => rect.min_x(),
Self::Right => rect.max_x(),
}
}
// Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection
pub fn along_edge(&self, rect: RectF, size: f32) -> RectF {
match self {
Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)),
Self::Down => RectF::new(
rect.lower_left() - Vector2F::new(0., size),
Vector2F::new(rect.width(), size),
),
Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())),
Self::Right => RectF::new(
rect.upper_right() - Vector2F::new(size, 0.),
Vector2F::new(size, rect.height()),
),
}
}
pub fn axis(&self) -> Axis {
match self {
Self::Up | Self::Down => Axis::Vertical,
Self::Left | Self::Right => Axis::Horizontal,
}
}
pub fn increasing(&self) -> bool {
match self {
Self::Left | Self::Up => false,
Self::Down | Self::Right => true,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ViewId {
pub creator: rpc::proto::PeerId,
pub id: u64,
}
impl ViewId {
pub fn from_proto(message: rpc::proto::ViewId) -> anyhow::Result<Self> {
Ok(Self {
creator: message
.creator
.ok_or_else(|| anyhow!("creator is missing"))?,
id: message.id,
})
}
pub fn to_proto(&self) -> rpc::proto::ViewId {
rpc::proto::ViewId {
creator: Some(self.creator),
id: self.id,
}
}
}
pub trait NavigationHistorySink {
fn push_any(&mut self, data: Option<Box<dyn Any>>, cx: &mut WindowContext);
}
impl dyn NavigationHistorySink {
pub fn push<D: 'static + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
self.push_any(data.map(|data| Box::new(data) as _), cx)
}
}

View file

@ -38,6 +38,7 @@ copilot_button = { path = "../copilot_button" }
diagnostics = { path = "../diagnostics" } diagnostics = { path = "../diagnostics" }
db = { path = "../db" } db = { path = "../db" }
editor = { path = "../editor" } editor = { path = "../editor" }
editor_extensions = { path = "../editor_extensions" }
feedback = { path = "../feedback" } feedback = { path = "../feedback" }
file_finder = { path = "../file_finder" } file_finder = { path = "../file_finder" }
search = { path = "../search" } search = { path = "../search" }

View file

@ -93,7 +93,7 @@ fn main() {
if cx.has_global::<Weak<AppState>>() { if cx.has_global::<Weak<AppState>>() {
if let Some(app_state) = cx.global::<Weak<AppState>>().upgrade() { if let Some(app_state) = cx.global::<Weak<AppState>>().upgrade() {
workspace::open_new(&app_state, cx, |workspace, cx| { workspace::open_new(&app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx) editor_extensions::new_file(workspace, &Default::default(), cx)
}) })
.detach(); .detach();
} }
@ -343,7 +343,7 @@ async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncApp
} else { } else {
cx.update(|cx| { cx.update(|cx| {
workspace::open_new(app_state, cx, |workspace, cx| { workspace::open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx) editor_extensions::new_file(workspace, &Default::default(), cx)
}) })
.detach(); .detach();
}); });

View file

@ -15,6 +15,7 @@ use collab_ui::CollabTitlebarItem; // TODO: Add back toggle collab ui shortcut
use collections::VecDeque; use collections::VecDeque;
pub use editor; pub use editor;
use editor::{Editor, MultiBuffer}; use editor::{Editor, MultiBuffer};
use editor_extensions::FollowableEditor;
use anyhow::anyhow; use anyhow::anyhow;
use feedback::{ use feedback::{
@ -195,7 +196,11 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
}); });
workspace.add_item( workspace.add_item(
Box::new(cx.add_view(|cx| { Box::new(cx.add_view(|cx| {
Editor::for_multibuffer(buffer, Some(project.clone()), cx) FollowableEditor::for_multibuffer(
buffer,
project.clone(),
cx,
)
})), })),
cx, cx,
); );
@ -246,7 +251,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
move |_: &NewWindow, cx: &mut AppContext| { move |_: &NewWindow, cx: &mut AppContext| {
if let Some(app_state) = app_state.upgrade() { if let Some(app_state) = app_state.upgrade() {
open_new(&app_state, cx, |workspace, cx| { open_new(&app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx) editor_extensions::new_file(workspace, &Default::default(), cx)
}) })
.detach(); .detach();
} }
@ -257,7 +262,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
move |_: &NewFile, cx: &mut AppContext| { move |_: &NewFile, cx: &mut AppContext| {
if let Some(app_state) = app_state.upgrade() { if let Some(app_state) = app_state.upgrade() {
open_new(&app_state, cx, |workspace, cx| { open_new(&app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx) editor_extensions::new_file(workspace, &Default::default(), cx)
}) })
.detach(); .detach();
} }
@ -332,7 +337,7 @@ pub fn initialize_workspace(
let feedback_button = cx.add_view(|_| { let feedback_button = cx.add_view(|_| {
feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace) feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
}); });
let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new()); let cursor_position = cx.add_view(|_| editor_extensions::CursorPosition::new());
workspace.status_bar().update(cx, |status_bar, cx| { workspace.status_bar().update(cx, |status_bar, cx| {
status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(diagnostic_summary, cx);
status_bar.add_left_item(activity_indicator, cx); status_bar.add_left_item(activity_indicator, cx);
@ -535,11 +540,9 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
MultiBuffer::singleton(buffer, cx).with_title("Log".into()) MultiBuffer::singleton(buffer, cx).with_title("Log".into())
}); });
workspace.add_item( workspace.add_item(
Box::new( Box::new(cx.add_view(|cx| {
cx.add_view(|cx| { FollowableEditor::for_multibuffer(buffer, project, cx)
Editor::for_multibuffer(buffer, Some(project), cx) })),
}),
),
cx, cx,
); );
}) })
@ -706,7 +709,7 @@ fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Works
MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into()) MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into())
}); });
workspace.add_item( workspace.add_item(
Box::new(cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))), Box::new(cx.add_view(|cx| FollowableEditor::for_multibuffer(buffer, project, cx))),
cx, cx,
); );
}).log_err()?; }).log_err()?;
@ -741,7 +744,7 @@ fn open_bundled_file(
}); });
workspace.add_item( workspace.add_item(
Box::new(cx.add_view(|cx| { Box::new(cx.add_view(|cx| {
Editor::for_multibuffer(buffer, Some(project.clone()), cx) FollowableEditor::for_multibuffer(buffer, project.clone(), cx)
})), })),
cx, cx,
); );