Merge branch 'main' into collab-panel

This commit is contained in:
Max Brunsfeld 2023-08-07 11:41:41 -07:00
commit c537cf2a57
92 changed files with 2015 additions and 1591 deletions

12
Cargo.lock generated
View file

@ -1651,6 +1651,15 @@ dependencies = [
"theme",
]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "copilot"
version = "0.1.0"
@ -2316,6 +2325,7 @@ dependencies = [
"clock",
"collections",
"context_menu",
"convert_case",
"copilot",
"ctor",
"db",
@ -9817,7 +9827,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.98.0"
version = "0.99.0"
dependencies = [
"activity_indicator",
"ai",

View file

@ -570,8 +570,9 @@ impl TestClient {
// We use a workspace container so that we don't need to remove the window in order to
// drop the workspace and we can use a ViewHandle instead.
let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None });
let workspace = cx.add_view(window_id, |cx| {
let window = cx.add_window(|_| WorkspaceContainer { workspace: None });
let container = window.root(cx);
let workspace = window.add_view(cx, |cx| {
Workspace::new(0, project.clone(), self.app_state.clone(), cx)
});
container.update(cx, |container, cx| {

View file

@ -7,8 +7,7 @@ use client::{User, RECEIVE_TIMEOUT};
use collections::HashSet;
use editor::{
test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions,
Undo,
ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToggleCodeActions, Undo,
};
use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions};
use futures::StreamExt as _;
@ -1208,7 +1207,7 @@ async fn test_share_project(
cx_c: &mut TestAppContext,
) {
deterministic.forbid_parking();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let window_b = cx_b.add_window(|_| EmptyView);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@ -1316,7 +1315,7 @@ async fn test_share_project(
.await
.unwrap();
let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
// Client A sees client B's selection
deterministic.run_until_parked();
@ -1499,8 +1498,9 @@ async fn test_host_disconnect(
deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
let (window_id_b, workspace_b) =
let window_b =
cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@ -1509,9 +1509,7 @@ async fn test_host_disconnect(
.unwrap()
.downcast::<Editor>()
.unwrap();
assert!(cx_b
.read_window(window_id_b, |cx| editor_b.is_focused(cx))
.unwrap());
assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.is_window_edited(workspace_b.window_id()));
@ -1525,7 +1523,7 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
// Ensure client B's edited state is reset and that the whole window is blurred.
cx_b.read_window(window_id_b, |cx| {
window_b.read_with(cx_b, |cx| {
assert_eq!(cx.focused_view_id(), None);
});
assert!(!cx_b.is_window_edited(workspace_b.window_id()));
@ -3440,13 +3438,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
let (window_a, _) = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a), cx)
});
let window_a = cx_a.add_window(|_| EmptyView);
let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
let mut editor_cx_a = EditorTestContext {
cx: cx_a,
window_id: window_a,
window_id: window_a.window_id(),
editor: editor_a,
};
@ -3455,13 +3451,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b), cx)
});
let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
let mut editor_cx_b = EditorTestContext {
cx: cx_b,
window_id: window_b,
window_id: window_b.window_id(),
editor: editor_b,
};
@ -4200,8 +4194,8 @@ async fn test_collaborating_with_completion(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| {
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
});
@ -5312,8 +5306,9 @@ async fn test_collaborating_with_code_actions(
// Join the project as client B.
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let (_window_b, workspace_b) =
let window_b =
cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@ -5537,8 +5532,9 @@ async fn test_collaborating_with_renames(
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let (_window_b, workspace_b) =
let window_b =
cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
let workspace_b = window_b.root(cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
@ -5569,6 +5565,7 @@ async fn test_collaborating_with_renames(
.unwrap();
prepare_rename.await.unwrap();
editor_b.update(cx_b, |editor, cx| {
use editor::ToOffset;
let rename = editor.pending_rename().unwrap();
let buffer = editor.buffer().read(cx).snapshot(cx);
assert_eq!(
@ -7599,8 +7596,8 @@ async fn test_on_input_format_from_host_to_guest(
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let (window_a, _) = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| {
let window_a = cx_a.add_window(|_| EmptyView);
let editor_a = window_a.add_view(cx_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
});
@ -7728,8 +7725,8 @@ async fn test_on_input_format_from_guest_to_host(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
});

View file

@ -31,7 +31,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.platform().screens() {
let screen_bounds = screen.bounds();
let (window_id, _) = cx.add_window(
let window = cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right()
@ -49,7 +49,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()),
);
notification_windows.push(window_id);
notification_windows.push(window.window_id());
}
}
}

View file

@ -26,7 +26,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.platform().screens() {
let screen_bounds = screen.bounds();
let (window_id, _) = cx.add_window(
let window = cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING),
@ -52,7 +52,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
notification_windows
.entry(*project_id)
.or_insert(Vec::new())
.push(window_id);
.push(window.window_id());
}
}
room::Event::RemoteProjectUnshared { project_id } => {

View file

@ -295,7 +295,9 @@ mod tests {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let editor = cx.add_view(window_id, |cx| {
let mut editor = Editor::single_line(None, cx);
editor.set_text("abc", cx);

View file

@ -4,7 +4,7 @@ use gpui::{
geometry::rect::RectF,
platform::{WindowBounds, WindowKind, WindowOptions},
AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
ViewHandle,
WindowHandle,
};
use theme::ui::modal;
@ -18,43 +18,43 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
pub fn init(cx: &mut AppContext) {
if let Some(copilot) = Copilot::global(cx) {
let mut code_verification: Option<ViewHandle<CopilotCodeVerification>> = None;
let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
cx.observe(&copilot, move |copilot, cx| {
let status = copilot.read(cx).status();
match &status {
crate::Status::SigningIn { prompt } => {
if let Some(code_verification_handle) = code_verification.as_mut() {
let window_id = code_verification_handle.window_id();
let updated = cx.update_window(window_id, |cx| {
code_verification_handle.update(cx, |code_verification, cx| {
code_verification.set_status(status.clone(), cx)
});
if let Some(window) = verification_window.as_mut() {
let updated = window
.root(cx)
.map(|root| {
root.update(cx, |verification, cx| {
verification.set_status(status.clone(), cx);
cx.activate_window();
});
if updated.is_none() {
code_verification = Some(create_copilot_auth_window(cx, &status));
})
})
.is_some();
if !updated {
verification_window = Some(create_copilot_auth_window(cx, &status));
}
} else if let Some(_prompt) = prompt {
code_verification = Some(create_copilot_auth_window(cx, &status));
verification_window = Some(create_copilot_auth_window(cx, &status));
}
}
Status::Authorized | Status::Unauthorized => {
if let Some(code_verification) = code_verification.as_ref() {
let window_id = code_verification.window_id();
cx.update_window(window_id, |cx| {
code_verification.update(cx, |code_verification, cx| {
code_verification.set_status(status, cx)
});
if let Some(window) = verification_window.as_ref() {
if let Some(verification) = window.root(cx) {
verification.update(cx, |verification, cx| {
verification.set_status(status, cx);
cx.platform().activate(true);
cx.activate_window();
});
}
}
}
_ => {
if let Some(code_verification) = code_verification.take() {
cx.update_window(code_verification.window_id(), |cx| cx.remove_window());
if let Some(code_verification) = verification_window.take() {
code_verification.update(cx, |cx| cx.remove_window());
}
}
}
@ -66,7 +66,7 @@ pub fn init(cx: &mut AppContext) {
fn create_copilot_auth_window(
cx: &mut AppContext,
status: &Status,
) -> ViewHandle<CopilotCodeVerification> {
) -> WindowHandle<CopilotCodeVerification> {
let window_size = theme::current(cx).copilot.modal.dimensions();
let window_options = WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
@ -78,10 +78,9 @@ fn create_copilot_auth_window(
is_movable: true,
screen: None,
};
let (_, view) = cx.add_window(window_options, |_cx| {
cx.add_window(window_options, |_cx| {
CopilotCodeVerification::new(status.clone())
});
view
})
}
pub struct CopilotCodeVerification {

View file

@ -855,7 +855,9 @@ mod tests {
let language_server_id = LanguageServerId(0);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
// Create some diagnostics
project.update(cx, |project, cx| {
@ -1248,7 +1250,9 @@ mod tests {
let server_id_1 = LanguageServerId(100);
let server_id_2 = LanguageServerId(101);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let view = cx.add_view(window_id, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)

View file

@ -47,6 +47,7 @@ workspace = { path = "../workspace" }
aho-corasick = "0.7"
anyhow.workspace = true
convert_case = "0.6.0"
futures.workspace = true
indoc = "1.0.4"
itertools = "0.10"
@ -56,12 +57,12 @@ ordered-float.workspace = true
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
rand.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
smol.workspace = true
rand.workspace = true
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-html = { workspace = true, optional = true }

View file

@ -28,6 +28,7 @@ use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings};
use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
@ -231,6 +232,13 @@ actions!(
SortLinesCaseInsensitive,
ReverseLines,
ShuffleLines,
ConvertToUpperCase,
ConvertToLowerCase,
ConvertToTitleCase,
ConvertToSnakeCase,
ConvertToKebabCase,
ConvertToUpperCamelCase,
ConvertToLowerCamelCase,
Transpose,
Cut,
Copy,
@ -353,6 +361,13 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::sort_lines_case_insensitive);
cx.add_action(Editor::reverse_lines);
cx.add_action(Editor::shuffle_lines);
cx.add_action(Editor::convert_to_upper_case);
cx.add_action(Editor::convert_to_lower_case);
cx.add_action(Editor::convert_to_title_case);
cx.add_action(Editor::convert_to_snake_case);
cx.add_action(Editor::convert_to_kebab_case);
cx.add_action(Editor::convert_to_upper_camel_case);
cx.add_action(Editor::convert_to_lower_camel_case);
cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end);
@ -4306,6 +4321,97 @@ impl Editor {
});
}
pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_uppercase())
}
pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_lowercase())
}
pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Title))
}
pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Snake))
}
pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
}
pub fn convert_to_upper_camel_case(
&mut self,
_: &ConvertToUpperCamelCase,
cx: &mut ViewContext<Self>,
) {
self.manipulate_text(cx, |text| text.to_case(Case::UpperCamel))
}
pub fn convert_to_lower_camel_case(
&mut self,
_: &ConvertToLowerCamelCase,
cx: &mut ViewContext<Self>,
) {
self.manipulate_text(cx, |text| text.to_case(Case::Camel))
}
fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
where
Fn: FnMut(&str) -> String,
{
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx).snapshot(cx);
let mut new_selections = Vec::new();
let mut edits = Vec::new();
let mut selection_adjustment = 0i32;
for selection in self.selections.all::<usize>(cx) {
let selection_is_empty = selection.is_empty();
let (start, end) = if selection_is_empty {
let word_range = movement::surrounding_word(
&display_map,
selection.start.to_display_point(&display_map),
);
let start = word_range.start.to_offset(&display_map, Bias::Left);
let end = word_range.end.to_offset(&display_map, Bias::Left);
(start, end)
} else {
(selection.start, selection.end)
};
let text = buffer.text_for_range(start..end).collect::<String>();
let old_length = text.len() as i32;
let text = callback(&text);
new_selections.push(Selection {
start: (start as i32 - selection_adjustment) as usize,
end: ((start + text.len()) as i32 - selection_adjustment) as usize,
goal: SelectionGoal::None,
..selection
});
selection_adjustment += old_length - text.len() as i32;
edits.push((start..end, text));
}
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(new_selections);
});
this.request_autoscroll(Autoscroll::fit(), cx);
});
}
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;

View file

@ -48,7 +48,8 @@ fn test_edit_events(cx: &mut TestAppContext) {
});
let events = Rc::new(RefCell::new(Vec::new()));
let (_, editor1) = cx.add_window({
let editor1 = cx
.add_window({
let events = events.clone();
|cx| {
cx.subscribe(&cx.handle(), move |_, _, event, _| {
@ -62,8 +63,10 @@ fn test_edit_events(cx: &mut TestAppContext) {
.detach();
Editor::for_buffer(buffer.clone(), None, cx)
}
});
let (_, editor2) = cx.add_window({
})
.root(cx);
let editor2 = cx
.add_window({
let events = events.clone();
|cx| {
cx.subscribe(&cx.handle(), move |_, _, event, _| {
@ -77,7 +80,8 @@ fn test_edit_events(cx: &mut TestAppContext) {
.detach();
Editor::for_buffer(buffer.clone(), None, cx)
}
});
})
.root(cx);
assert_eq!(mem::take(&mut *events.borrow_mut()), []);
// Mutating editor 1 will emit an `Edited` event only for that editor.
@ -173,7 +177,9 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
let editor = cx
.add_window(|cx| build_editor(buffer.clone(), cx))
.root(cx);
editor.update(cx, |editor, cx| {
editor.start_transaction_at(now, cx);
@ -343,10 +349,12 @@ fn test_ime_composition(cx: &mut TestAppContext) {
fn test_selection_with_mouse(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
build_editor(buffer, cx)
});
})
.root(cx);
editor.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
});
@ -410,10 +418,12 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
fn test_canceling_pending_selection(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
@ -456,10 +466,12 @@ fn test_clone(cx: &mut TestAppContext) {
true,
);
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&text, cx);
build_editor(buffer, cx)
});
})
.root(cx);
editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
@ -473,9 +485,11 @@ fn test_clone(cx: &mut TestAppContext) {
);
});
let (_, cloned_editor) = editor.update(cx, |editor, cx| {
let cloned_editor = editor
.update(cx, |editor, cx| {
cx.add_window(Default::default(), |cx| editor.clone(cx))
});
})
.root(cx);
let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
@ -509,7 +523,9 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
cx.add_view(window_id, |cx| {
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
@ -618,10 +634,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
fn test_cancel(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
@ -661,7 +679,8 @@ fn test_cancel(cx: &mut TestAppContext) {
fn test_fold_action(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(
&"
impl Foo {
@ -684,7 +703,8 @@ fn test_fold_action(cx: &mut TestAppContext) {
cx,
);
build_editor(buffer.clone(), cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@ -752,7 +772,9 @@ fn test_move_cursor(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
let view = cx
.add_window(|cx| build_editor(buffer.clone(), cx))
.root(cx);
buffer.update(cx, |buffer, cx| {
buffer.edit(
@ -827,10 +849,12 @@ fn test_move_cursor(cx: &mut TestAppContext) {
fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
build_editor(buffer.clone(), cx)
});
})
.root(cx);
assert_eq!('ⓐ'.len_utf8(), 3);
assert_eq!('α'.len_utf8(), 2);
@ -932,10 +956,12 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
build_editor(buffer.clone(), cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
@ -982,10 +1008,12 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
fn test_beginning_end_of_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("abc\n def", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@ -1145,10 +1173,12 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@ -1197,10 +1227,13 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
let view = cx
.add_window(|cx| {
let buffer =
MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.set_wrap_width(Some(140.), cx);
@ -1530,10 +1563,12 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("one two three four", cx);
build_editor(buffer.clone(), cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@ -1566,10 +1601,12 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
fn test_newline(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
build_editor(buffer.clone(), cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@ -1589,7 +1626,8 @@ fn test_newline(cx: &mut TestAppContext) {
fn test_newline_with_old_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(
"
a
@ -1612,7 +1650,8 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) {
])
});
editor
});
})
.root(cx);
editor.update(cx, |editor, cx| {
// Edit the buffer directly, deleting ranges surrounding the editor's selections
@ -1817,12 +1856,14 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
fn test_insert_with_old_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
let mut editor = build_editor(buffer.clone(), cx);
editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
editor
});
})
.root(cx);
editor.update(cx, |editor, cx| {
// Edit the buffer directly, deleting ranges surrounding the editor's selections
@ -2329,10 +2370,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
fn test_delete_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@ -2352,10 +2395,12 @@ fn test_delete_line(cx: &mut TestAppContext) {
);
});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
@ -2650,14 +2695,94 @@ async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
"});
}
#[gpui::test]
async fn test_manipulate_text(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
// Test convert_to_upper_case()
cx.set_state(indoc! {"
«hello worldˇ»
"});
cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
cx.assert_editor_state(indoc! {"
«HELLO WORLDˇ»
"});
// Test convert_to_lower_case()
cx.set_state(indoc! {"
«HELLO WORLDˇ»
"});
cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
cx.assert_editor_state(indoc! {"
«hello worldˇ»
"});
// From here on out, test more complex cases of manipulate_text()
// Test no selection case - should affect words cursors are in
// Cursor at beginning, middle, and end of word
cx.set_state(indoc! {"
ˇhello big beauˇtiful worldˇ
"});
cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
cx.assert_editor_state(indoc! {"
«HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
"});
// Test multiple selections on a single line and across multiple lines
cx.set_state(indoc! {"
«Theˇ» quick «brown
foxˇ» jumps «overˇ»
the «lazyˇ» dog
"});
cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
cx.assert_editor_state(indoc! {"
«THEˇ» quick «BROWN
FOXˇ» jumps «OVERˇ»
the «LAZYˇ» dog
"});
// Test case where text length grows
cx.set_state(indoc! {"
«tschüߡ»
"});
cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
cx.assert_editor_state(indoc! {"
«TSCHÜSSˇ»
"});
// Test to make sure we don't crash when text shrinks
cx.set_state(indoc! {"
aaa_bbbˇ
"});
cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
cx.assert_editor_state(indoc! {"
«aaaBbbˇ»
"});
// Test to make sure we all aware of the fact that each word can grow and shrink
// Final selections should be aware of this fact
cx.set_state(indoc! {"
aaa_bˇbb bbˇb_ccc ˇccc_ddd
"});
cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
cx.assert_editor_state(indoc! {"
«aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
"});
}
#[gpui::test]
fn test_duplicate_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@ -2680,10 +2805,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
);
});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@ -2707,10 +2834,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
fn test_move_line_up_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.fold_ranges(
vec![
@ -2806,10 +2935,12 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
build_editor(buffer, cx)
});
})
.root(cx);
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer.read(cx).snapshot(cx);
editor.insert_blocks(
@ -2834,8 +2965,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
fn test_transpose(cx: &mut TestAppContext) {
init_test(cx, |_| {});
_ = cx
.add_window(|cx| {
_ = cx.add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
@ -2852,11 +2982,9 @@ fn test_transpose(cx: &mut TestAppContext) {
assert_eq!(editor.selections.ranges(cx), [3..3]);
editor
})
.1;
});
_ = cx
.add_window(|cx| {
_ = cx.add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
@ -2878,11 +3006,9 @@ fn test_transpose(cx: &mut TestAppContext) {
assert_eq!(editor.selections.ranges(cx), [6..6]);
editor
})
.1;
});
_ = cx
.add_window(|cx| {
_ = cx.add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
@ -2907,11 +3033,9 @@ fn test_transpose(cx: &mut TestAppContext) {
assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
editor
})
.1;
});
_ = cx
.add_window(|cx| {
_ = cx.add_window(|cx| {
let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
@ -2928,8 +3052,7 @@ fn test_transpose(cx: &mut TestAppContext) {
assert_eq!(editor.selections.ranges(cx), [11..11]);
editor
})
.1;
});
}
#[gpui::test]
@ -3132,10 +3255,12 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
fn test_select_all(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.select_all(&SelectAll, cx);
assert_eq!(
@ -3149,10 +3274,12 @@ fn test_select_all(cx: &mut TestAppContext) {
fn test_select_line(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
s.select_display_ranges([
@ -3196,10 +3323,12 @@ fn test_select_line(cx: &mut TestAppContext) {
fn test_split_selection_into_lines(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.fold_ranges(
vec![
@ -3267,10 +3396,12 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
fn test_add_selection_above_below(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, view) = cx.add_window(|cx| {
let view = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
build_editor(buffer, cx)
});
})
.root(cx);
view.update(cx, |view, cx| {
view.change_selections(None, cx, |s| {
@ -3555,7 +3686,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@ -3718,7 +3849,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor
.condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
.await;
@ -4281,7 +4412,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@ -4429,7 +4560,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor
.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@ -4519,7 +4650,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) {
);
let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| {
let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
@ -4649,7 +4780,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
@ -4761,7 +4892,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
@ -4875,7 +5006,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
let format = editor.update(cx, |editor, cx| {
@ -5653,7 +5784,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
multibuffer
});
let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
view.update(cx, |view, cx| {
assert_eq!(view.text(cx), "aaaa\nbbbb");
view.change_selections(None, cx, |s| {
@ -5723,7 +5854,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
multibuffer
});
let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
view.update(cx, |view, cx| {
let (expected_text, selection_ranges) = marked_text_ranges(
indoc! {"
@ -5799,7 +5930,8 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
multibuffer
});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let mut editor = build_editor(multibuffer.clone(), cx);
let snapshot = editor.snapshot(cx);
editor.change_selections(None, cx, |s| {
@ -5814,7 +5946,8 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
]
);
editor
});
})
.root(cx);
// Refreshing selections is a no-op when excerpts haven't changed.
editor.update(cx, |editor, cx| {
@ -5884,7 +6017,8 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
multibuffer
});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let mut editor = build_editor(multibuffer.clone(), cx);
let snapshot = editor.snapshot(cx);
editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
@ -5893,7 +6027,8 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
[Point::new(1, 3)..Point::new(1, 3)]
);
editor
});
})
.root(cx);
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
@ -5956,7 +6091,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
.await;
@ -5992,10 +6127,12 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
fn test_highlighted_ranges(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
build_editor(buffer.clone(), cx)
});
})
.root(cx);
editor.update(cx, |editor, cx| {
struct Type1;
@ -6084,8 +6221,11 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
.unwrap();
cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
});
let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
let (_, follower) = cx.update(|cx| {
let leader = cx
.add_window(|cx| build_editor(buffer.clone(), cx))
.root(cx);
let follower = cx
.update(|cx| {
cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
@ -6093,7 +6233,8 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
},
|cx| build_editor(buffer.clone(), cx),
)
});
})
.root(cx);
let is_still_following = Rc::new(RefCell::new(true));
let follower_edit_event_count = Rc::new(RefCell::new(0));
@ -6224,7 +6365,9 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let leader = pane.update(cx, |_, cx| {
@ -6968,7 +7111,7 @@ async fn test_copilot_multibuffer(
);
multibuffer
});
let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
handle_copilot_completion_request(
&copilot_lsp,
@ -7098,7 +7241,7 @@ async fn test_copilot_disabled_globs(
);
multibuffer
});
let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
let mut copilot_requests = copilot_lsp
.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
@ -7177,7 +7320,9 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@ -7282,7 +7427,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
let (_, _workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let _buffer = project
.update(cx, |project, cx| {
project.open_local_buffer("/a/main.rs", cx)

View file

@ -3002,10 +3002,12 @@ mod tests {
fn test_layout_line_numbers(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
})
.root(cx);
let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let layouts = editor.update(cx, |editor, cx| {
@ -3021,10 +3023,12 @@ mod tests {
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
})
.root(cx);
editor.update(cx, |editor, cx| {
editor.set_placeholder_text("hello", cx);
@ -3231,10 +3235,12 @@ mod tests {
info!(
"Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
);
let (_, editor) = cx.add_window(|cx| {
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&input_text, cx);
Editor::new(editor_mode, buffer, None, None, cx)
});
})
.root(cx);
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, layout_state) = editor.update(cx, |editor, cx| {

View file

@ -1135,7 +1135,9 @@ mod tests {
)
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@ -1835,7 +1837,9 @@ mod tests {
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@ -1988,7 +1992,9 @@ mod tests {
project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language))
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@ -2074,8 +2080,9 @@ mod tests {
deterministic.run_until_parked();
cx.foreground().run_until_parked();
let (_, editor) =
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
let editor = cx
.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
.root(cx);
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited);
@ -2327,7 +2334,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language))
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@ -2372,8 +2381,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
deterministic.run_until_parked();
cx.foreground().run_until_parked();
let (_, editor) =
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
let editor = cx
.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
.root(cx);
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited);
@ -2561,7 +2571,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()

View file

@ -28,7 +28,10 @@ use std::{
path::{Path, PathBuf},
};
use text::Selection;
use util::{paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
use util::{
paths::{PathExt, FILE_ROW_COLUMN_DELIMITER},
ResultExt, TryFutureExt,
};
use workspace::item::{BreadcrumbText, FollowableItemHandle};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
@ -546,9 +549,7 @@ impl Item for Editor {
.and_then(|f| f.as_local())?
.abs_path(cx);
let file_path = util::paths::compact(&file_path)
.to_string_lossy()
.to_string();
let file_path = file_path.compact().to_string_lossy().to_string();
Some(file_path.into())
}

View file

@ -69,7 +69,8 @@ impl<'a> EditorLspTestContext<'a> {
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
.await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root", true, cx)
@ -98,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> {
Self {
cx: EditorTestContext {
cx,
window_id,
window_id: window.window_id(),
editor,
},
lsp,

View file

@ -32,16 +32,14 @@ impl<'a> EditorTestContext<'a> {
let buffer = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.unwrap();
let (window_id, editor) = cx.update(|cx| {
cx.add_window(Default::default(), |cx| {
let window = cx.add_window(|cx| {
cx.focus_self();
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
})
});
let editor = window.root(cx);
Self {
cx,
window_id,
window_id: window.window_id(),
editor,
}
}

View file

@ -617,8 +617,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
@ -631,8 +632,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
cx.dispatch_action(window.window_id(), SelectNext);
cx.dispatch_action(window.window_id(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@ -671,8 +672,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3];
@ -704,8 +706,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
cx.dispatch_action(window.window_id(), SelectNext);
cx.dispatch_action(window.window_id(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@ -754,8 +756,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3];
@ -787,8 +790,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
cx.dispatch_action(window.window_id(), SelectNext);
cx.dispatch_action(window.window_id(), Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@ -837,8 +840,11 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
@ -849,7 +855,8 @@ mod tests {
),
cx,
)
});
})
.root(cx);
let query = test_path_like("hi");
finder
@ -931,8 +938,11 @@ mod tests {
cx,
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
@ -943,7 +953,8 @@ mod tests {
),
cx,
)
});
})
.root(cx);
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("hi"), cx)
@ -967,8 +978,11 @@ mod tests {
cx,
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
@ -979,7 +993,8 @@ mod tests {
),
cx,
)
});
})
.root(cx);
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@ -1015,61 +1030,6 @@ mod tests {
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
}
#[gpui::test]
async fn test_multiple_matches_with_same_relative_path(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"dir1": { "a.txt": "" },
"dir2": { "a.txt": "" }
}),
)
.await;
let project = Project::test(
app_state.fs.clone(),
["/root/dir1".as_ref(), "/root/dir2".as_ref()],
cx,
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx,
)
});
// Run a search that matches two files with the same relative path.
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("a.t"), cx)
})
.await;
// Can switch between different matches with the same relative path.
finder.update(cx, |finder, cx| {
let delegate = finder.delegate_mut();
assert_eq!(delegate.matches.len(), 2);
assert_eq!(delegate.selected_index(), 0);
delegate.set_selected_index(1, cx);
assert_eq!(delegate.selected_index(), 1);
delegate.set_selected_index(0, cx);
assert_eq!(delegate.selected_index(), 0);
});
}
#[gpui::test]
async fn test_path_distance_ordering(cx: &mut TestAppContext) {
let app_state = init_test(cx);
@ -1089,7 +1049,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@ -1103,7 +1065,8 @@ mod tests {
worktree_id,
path: Arc::from(Path::new("/root/dir2/b.txt")),
}));
let (_, finder) = cx.add_window(|cx| {
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
@ -1114,7 +1077,8 @@ mod tests {
),
cx,
)
});
})
.root(cx);
finder
.update(cx, |f, cx| {
@ -1151,8 +1115,11 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
@ -1163,7 +1130,8 @@ mod tests {
),
cx,
)
});
})
.root(cx);
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("dir"), cx)
@ -1198,7 +1166,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@ -1404,7 +1374,9 @@ mod tests {
.detach();
deterministic.run_until_parked();
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1,);

View file

@ -130,8 +130,14 @@ pub trait BorrowAppContext {
}
pub trait BorrowWindowContext {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T;
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T;
type Result<T>;
fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
where
F: FnOnce(&WindowContext) -> T;
fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
where
F: FnOnce(&mut WindowContext) -> T;
}
#[derive(Clone)]
@ -402,7 +408,7 @@ impl AsyncAppContext {
&mut self,
window_options: WindowOptions,
build_root_view: F,
) -> (usize, ViewHandle<T>)
) -> WindowHandle<T>
where
T: View,
F: FnOnce(&mut ViewContext<T>) -> T,
@ -452,6 +458,26 @@ impl BorrowAppContext for AsyncAppContext {
}
}
impl BorrowWindowContext for AsyncAppContext {
type Result<T> = Option<T>;
fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
where
F: FnOnce(&WindowContext) -> T,
{
self.0.borrow().read_with(|cx| cx.read_window(window_id, f))
}
fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
where
F: FnOnce(&mut WindowContext) -> T,
{
self.0
.borrow_mut()
.update(|cx| cx.update_window(window_id, f))
}
}
type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
@ -494,8 +520,8 @@ pub struct AppContext {
// Action Types -> Action Handlers
global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
keystroke_matcher: KeymapMatcher,
next_entity_id: usize,
next_window_id: usize,
next_id: usize,
// next_window_id: usize,
next_subscription_id: usize,
frame_count: usize,
@ -554,8 +580,7 @@ impl AppContext {
actions: Default::default(),
global_actions: Default::default(),
keystroke_matcher: KeymapMatcher::default(),
next_entity_id: 0,
next_window_id: 0,
next_id: 0,
next_subscription_id: 0,
frame_count: 0,
subscriptions: Default::default(),
@ -783,7 +808,7 @@ impl AppContext {
result
}
pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
fn read_window<T, F: FnOnce(&WindowContext) -> T>(
&self,
window_id: usize,
callback: F,
@ -1226,7 +1251,7 @@ impl AppContext {
F: FnOnce(&mut ModelContext<T>) -> T,
{
self.update(|this| {
let model_id = post_inc(&mut this.next_entity_id);
let model_id = post_inc(&mut this.next_id);
let handle = ModelHandle::new(model_id, &this.ref_counts);
let mut cx = ModelContext::new(this, model_id);
let model = build_model(&mut cx);
@ -1300,20 +1325,19 @@ impl AppContext {
&mut self,
window_options: WindowOptions,
build_root_view: F,
) -> (usize, ViewHandle<V>)
) -> WindowHandle<V>
where
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
self.update(|this| {
let window_id = post_inc(&mut this.next_window_id);
let window_id = post_inc(&mut this.next_id);
let platform_window =
this.platform
.open_window(window_id, window_options, this.foreground.clone());
let window = this.build_window(window_id, platform_window, build_root_view);
let root_view = window.root_view().clone().downcast::<V>().unwrap();
this.windows.insert(window_id, window);
(window_id, root_view)
WindowHandle::new(window_id)
})
}
@ -1323,7 +1347,7 @@ impl AppContext {
F: FnOnce(&mut ViewContext<V>) -> V,
{
self.update(|this| {
let window_id = post_inc(&mut this.next_window_id);
let window_id = post_inc(&mut this.next_id);
let platform_window = this.platform.add_status_item(window_id);
let window = this.build_window(window_id, platform_window, build_root_view);
let root_view = window.root_view().clone().downcast::<V>().unwrap();
@ -1422,7 +1446,7 @@ impl AppContext {
&mut self,
window_id: usize,
build_root_view: F,
) -> Option<ViewHandle<V>>
) -> Option<WindowHandle<V>>
where
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
@ -2158,6 +2182,24 @@ impl BorrowAppContext for AppContext {
}
}
impl BorrowWindowContext for AppContext {
type Result<T> = Option<T>;
fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
where
F: FnOnce(&WindowContext) -> T,
{
AppContext::read_window(self, window_id, f)
}
fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
where
F: FnOnce(&mut WindowContext) -> T,
{
AppContext::update_window(self, window_id, f)
}
}
#[derive(Debug)]
pub enum ParentId {
View(usize),
@ -3356,12 +3398,18 @@ impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
}
impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
BorrowWindowContext::read_with(&*self.window_context, window_id, f)
type Result<T> = T;
fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
BorrowWindowContext::read_window_with(&*self.window_context, window_id, f)
}
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
BorrowWindowContext::update(&mut *self.window_context, window_id, f)
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window_id: usize,
f: F,
) -> T {
BorrowWindowContext::update_window(&mut *self.window_context, window_id, f)
}
}
@ -3461,12 +3509,18 @@ impl<V: View> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
}
impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
BorrowWindowContext::read_with(&*self.view_context, window_id, f)
type Result<T> = T;
fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
BorrowWindowContext::read_window_with(&*self.view_context, window_id, f)
}
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
BorrowWindowContext::update(&mut *self.view_context, window_id, f)
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window_id: usize,
f: F,
) -> T {
BorrowWindowContext::update_window(&mut *self.view_context, window_id, f)
}
}
@ -3513,12 +3567,18 @@ impl<V: View> BorrowAppContext for EventContext<'_, '_, '_, V> {
}
impl<V: View> BorrowWindowContext for EventContext<'_, '_, '_, V> {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
BorrowWindowContext::read_with(&*self.view_context, window_id, f)
type Result<T> = T;
fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
BorrowWindowContext::read_window_with(&*self.view_context, window_id, f)
}
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
BorrowWindowContext::update(&mut *self.view_context, window_id, f)
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window_id: usize,
f: F,
) -> T {
BorrowWindowContext::update_window(&mut *self.view_context, window_id, f)
}
}
@ -3802,6 +3862,89 @@ impl<T> Clone for WeakModelHandle<T> {
impl<T> Copy for WeakModelHandle<T> {}
pub struct WindowHandle<T> {
window_id: usize,
root_view_type: PhantomData<T>,
}
#[allow(dead_code)]
impl<V: View> WindowHandle<V> {
fn new(window_id: usize) -> Self {
WindowHandle {
window_id,
root_view_type: PhantomData,
}
}
pub fn window_id(&self) -> usize {
self.window_id
}
pub fn root<C: BorrowWindowContext>(&self, cx: &C) -> C::Result<ViewHandle<V>> {
self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap())
}
pub fn read_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
where
C: BorrowWindowContext,
F: FnOnce(&WindowContext) -> R,
{
cx.read_window_with(self.window_id(), |cx| read(cx))
}
pub fn update<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<R>
where
C: BorrowWindowContext,
F: FnOnce(&mut WindowContext) -> R,
{
cx.update_window(self.window_id(), update)
}
// pub fn update_root<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<Option<R>>
// where
// C: BorrowWindowContext,
// F: FnOnce(&mut V, &mut ViewContext<V>) -> R,
// {
// cx.update_window(self.window_id, |cx| {
// cx.root_view()
// .clone()
// .downcast::<V>()
// .map(|v| v.update(cx, update))
// })
// }
pub fn read_root<'a>(&self, cx: &'a AppContext) -> &'a V {
let root_view = cx
.read_window(self.window_id(), |cx| {
cx.root_view().clone().downcast().unwrap()
})
.unwrap();
root_view.read(cx)
}
pub fn read_root_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
where
C: BorrowWindowContext,
F: FnOnce(&V, &ViewContext<V>) -> R,
{
self.read_with(cx, |cx| {
cx.root_view()
.downcast_ref::<V>()
.unwrap()
.read_with(cx, read)
})
}
pub fn add_view<C, U, F>(&self, cx: &mut C, build_view: F) -> C::Result<ViewHandle<U>>
where
C: BorrowWindowContext,
U: View,
F: FnOnce(&mut ViewContext<U>) -> U,
{
self.update(cx, |cx| cx.add_view(build_view))
}
}
#[repr(transparent)]
pub struct ViewHandle<T> {
any_handle: AnyViewHandle,
@ -3849,25 +3992,25 @@ impl<T: View> ViewHandle<T> {
cx.read_view(self)
}
pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> S
pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> C::Result<S>
where
C: BorrowWindowContext,
F: FnOnce(&T, &ViewContext<T>) -> S,
{
cx.read_with(self.window_id, |cx| {
cx.read_window_with(self.window_id, |cx| {
let cx = ViewContext::immutable(cx, self.view_id);
read(cx.read_view(self), &cx)
})
}
pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> S
pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> C::Result<S>
where
C: BorrowWindowContext,
F: FnOnce(&mut T, &mut ViewContext<T>) -> S,
{
let mut update = Some(update);
cx.update(self.window_id, |cx| {
cx.update_window(self.window_id, |cx| {
cx.update_view(self, &mut |view, cx| {
let update = update.take().unwrap();
update(view, cx)
@ -4684,11 +4827,11 @@ mod tests {
}
}
let (_, view) = cx.add_window(|_| View { render_count: 0 });
let window = cx.add_window(|_| View { render_count: 0 });
let called_defer = Rc::new(AtomicBool::new(false));
let called_after_window_update = Rc::new(AtomicBool::new(false));
view.update(cx, |this, cx| {
window.root(cx).update(cx, |this, cx| {
assert_eq!(this.render_count, 1);
cx.defer({
let called_defer = called_defer.clone();
@ -4712,7 +4855,7 @@ mod tests {
assert!(called_defer.load(SeqCst));
assert!(called_after_window_update.load(SeqCst));
assert_eq!(view.read_with(cx, |view, _| view.render_count), 3);
assert_eq!(window.read_root_with(cx, |view, _| view.render_count), 3);
}
#[crate::test(self)]
@ -4751,9 +4894,9 @@ mod tests {
}
}
let (window_id, _root_view) = cx.add_window(|cx| View::new(None, cx));
let handle_1 = cx.add_view(window_id, |cx| View::new(None, cx));
let handle_2 = cx.add_view(window_id, |cx| View::new(Some(handle_1.clone()), cx));
let window = cx.add_window(|cx| View::new(None, cx));
let handle_1 = window.add_view(cx, |cx| View::new(None, cx));
let handle_2 = window.add_view(cx, |cx| View::new(Some(handle_1.clone()), cx));
assert_eq!(cx.read(|cx| cx.views.len()), 3);
handle_1.update(cx, |view, cx| {
@ -4813,11 +4956,11 @@ mod tests {
}
let mouse_down_count = Arc::new(AtomicUsize::new(0));
let (window_id, _) = cx.add_window(Default::default(), |_| View {
let window = cx.add_window(Default::default(), |_| View {
mouse_down_count: mouse_down_count.clone(),
});
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
// Ensure window's root element is in a valid lifecycle state.
cx.dispatch_event(
Event::MouseDown(MouseButtonEvent {
@ -4833,7 +4976,7 @@ mod tests {
}
#[crate::test(self)]
fn test_entity_release_hooks(cx: &mut AppContext) {
fn test_entity_release_hooks(cx: &mut TestAppContext) {
struct Model {
released: Rc<Cell<bool>>,
}
@ -4876,12 +5019,15 @@ mod tests {
let model = cx.add_model(|_| Model {
released: model_released.clone(),
});
let (window_id, view) = cx.add_window(Default::default(), |_| View {
let window = cx.add_window(|_| View {
released: view_released.clone(),
});
let view = window.root(cx);
assert!(!model_released.get());
assert!(!view_released.get());
cx.update(|cx| {
cx.observe_release(&model, {
let model_release_observed = model_release_observed.clone();
move |_, _| model_release_observed.set(true)
@ -4892,6 +5038,7 @@ mod tests {
move |_, _| view_release_observed.set(true)
})
.detach();
});
cx.update(move |_| {
drop(model);
@ -4900,7 +5047,7 @@ mod tests {
assert!(model_release_observed.get());
drop(view);
cx.update_window(window_id, |cx| cx.remove_window());
window.update(cx, |cx| cx.remove_window());
assert!(view_released.get());
assert!(view_release_observed.get());
}
@ -4913,8 +5060,9 @@ mod tests {
type Event = String;
}
let (window_id, handle_1) = cx.add_window(|_| TestView::default());
let handle_2 = cx.add_view(window_id, |_| TestView::default());
let window = cx.add_window(|_| TestView::default());
let handle_1 = window.root(cx);
let handle_2 = window.add_view(cx, |_| TestView::default());
let handle_3 = cx.add_model(|_| Model);
handle_1.update(cx, |_, cx| {
@ -5140,9 +5288,9 @@ mod tests {
type Event = ();
}
let (window_id, _root_view) = cx.add_window(|_| TestView::default());
let observing_view = cx.add_view(window_id, |_| TestView::default());
let emitting_view = cx.add_view(window_id, |_| TestView::default());
let window = cx.add_window(|_| TestView::default());
let observing_view = window.add_view(cx, |_| TestView::default());
let emitting_view = window.add_view(cx, |_| TestView::default());
let observing_model = cx.add_model(|_| Model);
let observed_model = cx.add_model(|_| Model);
@ -5165,7 +5313,7 @@ mod tests {
#[crate::test(self)]
fn test_view_emit_before_subscribe_in_same_update_cycle(cx: &mut AppContext) {
let (_, view) = cx.add_window::<TestView, _>(Default::default(), |cx| {
let window = cx.add_window::<TestView, _>(Default::default(), |cx| {
drop(cx.subscribe(&cx.handle(), {
move |this, _, _, _| this.events.push("dropped before flush".into())
}));
@ -5181,7 +5329,7 @@ mod tests {
TestView { events: Vec::new() }
});
assert_eq!(view.read(cx).events, ["before emit"]);
assert_eq!(window.read_root(cx).events, ["before emit"]);
}
#[crate::test(self)]
@ -5195,7 +5343,8 @@ mod tests {
type Event = ();
}
let (_, view) = cx.add_window(|_| TestView::default());
let window = cx.add_window(|_| TestView::default());
let view = window.root(cx);
let model = cx.add_model(|_| Model {
state: "old-state".into(),
});
@ -5216,7 +5365,7 @@ mod tests {
#[crate::test(self)]
fn test_view_notify_before_observe_in_same_update_cycle(cx: &mut AppContext) {
let (_, view) = cx.add_window::<TestView, _>(Default::default(), |cx| {
let window = cx.add_window::<TestView, _>(Default::default(), |cx| {
drop(cx.observe(&cx.handle(), {
move |this, _, _| this.events.push("dropped before flush".into())
}));
@ -5232,7 +5381,7 @@ mod tests {
TestView { events: Vec::new() }
});
assert_eq!(view.read(cx).events, ["before notify"]);
assert_eq!(window.read_root(cx).events, ["before notify"]);
}
#[crate::test(self)]
@ -5243,7 +5392,8 @@ mod tests {
}
let model = cx.add_model(|_| Model);
let (_, view) = cx.add_window(|_| TestView::default());
let window = cx.add_window(|_| TestView::default());
let view = window.root(cx);
view.update(cx, |_, cx| {
model.update(cx, |_, cx| cx.notify());
@ -5267,8 +5417,8 @@ mod tests {
type Event = ();
}
let (window_id, _root_view) = cx.add_window(|_| TestView::default());
let observing_view = cx.add_view(window_id, |_| TestView::default());
let window = cx.add_window(|_| TestView::default());
let observing_view = window.add_view(cx, |_| TestView::default());
let observing_model = cx.add_model(|_| Model);
let observed_model = cx.add_model(|_| Model);
@ -5390,9 +5540,9 @@ mod tests {
}
}
let (window_id, _root_view) = cx.add_window(|_| View);
let observing_view = cx.add_view(window_id, |_| View);
let observed_view = cx.add_view(window_id, |_| View);
let window = cx.add_window(|_| View);
let observing_view = window.add_view(cx, |_| View);
let observed_view = window.add_view(cx, |_| View);
let observation_count = Rc::new(RefCell::new(0));
observing_view.update(cx, |_, cx| {
@ -5474,13 +5624,13 @@ mod tests {
}
let view_events: Arc<Mutex<Vec<String>>> = Default::default();
let (window_id, view_1) = cx.add_window(|_| View {
let window = cx.add_window(|_| View {
events: view_events.clone(),
name: "view 1".to_string(),
child: None,
});
let view_2 = cx
.update_window(window_id, |cx| {
let view_1 = window.root(cx);
let view_2 = window.update(cx, |cx| {
let view_2 = cx.add_view(|_| View {
events: view_events.clone(),
name: "view 2".to_string(),
@ -5491,8 +5641,7 @@ mod tests {
cx.notify();
});
view_2
})
.unwrap();
});
let observed_events: Arc<Mutex<Vec<String>>> = Default::default();
view_1.update(cx, |_, cx| {
@ -5619,7 +5768,7 @@ mod tests {
}
#[crate::test(self)]
fn test_dispatch_action(cx: &mut AppContext) {
fn test_dispatch_action(cx: &mut TestAppContext) {
struct ViewA {
id: usize,
child: Option<AnyViewHandle>,
@ -5670,7 +5819,9 @@ mod tests {
impl_actions!(test, [Action]);
let actions = Rc::new(RefCell::new(Vec::new()));
let observed_actions = Rc::new(RefCell::new(Vec::new()));
cx.update(|cx| {
cx.add_global_action({
let actions = actions.clone();
move |_: &Action, _: &mut AppContext| {
@ -5724,47 +5875,41 @@ mod tests {
}
});
let observed_actions = Rc::new(RefCell::new(Vec::new()));
cx.observe_actions({
let observed_actions = observed_actions.clone();
move |action_id, _| observed_actions.borrow_mut().push(action_id)
})
.detach();
});
let (window_id, view_1) =
cx.add_window(Default::default(), |_| ViewA { id: 1, child: None });
let view_2 = cx
.update_window(window_id, |cx| {
let window = cx.add_window(|_| ViewA { id: 1, child: None });
let view_1 = window.root(cx);
let view_2 = window.update(cx, |cx| {
let child = cx.add_view(|_| ViewB { id: 2, child: None });
view_1.update(cx, |view, cx| {
view.child = Some(child.clone().into_any());
cx.notify();
});
child
})
.unwrap();
let view_3 = cx
.update_window(window_id, |cx| {
});
let view_3 = window.update(cx, |cx| {
let child = cx.add_view(|_| ViewA { id: 3, child: None });
view_2.update(cx, |view, cx| {
view.child = Some(child.clone().into_any());
cx.notify();
});
child
})
.unwrap();
let view_4 = cx
.update_window(window_id, |cx| {
});
let view_4 = window.update(cx, |cx| {
let child = cx.add_view(|_| ViewB { id: 4, child: None });
view_3.update(cx, |view, cx| {
view.child = Some(child.clone().into_any());
cx.notify();
});
child
})
.unwrap();
});
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
});
@ -5786,31 +5931,27 @@ mod tests {
// Remove view_1, which doesn't propagate the action
let (window_id, view_2) =
cx.add_window(Default::default(), |_| ViewB { id: 2, child: None });
let view_3 = cx
.update_window(window_id, |cx| {
let window = cx.add_window(|_| ViewB { id: 2, child: None });
let view_2 = window.root(cx);
let view_3 = window.update(cx, |cx| {
let child = cx.add_view(|_| ViewA { id: 3, child: None });
view_2.update(cx, |view, cx| {
view.child = Some(child.clone().into_any());
cx.notify();
});
child
})
.unwrap();
let view_4 = cx
.update_window(window_id, |cx| {
});
let view_4 = window.update(cx, |cx| {
let child = cx.add_view(|_| ViewB { id: 4, child: None });
view_3.update(cx, |view, cx| {
view.child = Some(child.clone().into_any());
cx.notify();
});
child
})
.unwrap();
});
actions.borrow_mut().clear();
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
});
@ -5887,7 +6028,7 @@ mod tests {
view_3.keymap_context.add_identifier("b");
view_3.keymap_context.add_identifier("c");
let (window_id, _view_1) = cx.add_window(Default::default(), |cx| {
let window = cx.add_window(Default::default(), |cx| {
let view_2 = cx.add_view(|cx| {
let view_3 = cx.add_view(|cx| {
cx.focus_self();
@ -5947,26 +6088,26 @@ mod tests {
}
});
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
cx.dispatch_keystroke(&Keystroke::parse("a").unwrap())
});
assert_eq!(&*actions.borrow(), &["2 a"]);
actions.borrow_mut().clear();
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
cx.dispatch_keystroke(&Keystroke::parse("b").unwrap());
});
assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]);
actions.borrow_mut().clear();
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
cx.dispatch_keystroke(&Keystroke::parse("c").unwrap());
});
assert_eq!(&*actions.borrow(), &["3 c"]);
actions.borrow_mut().clear();
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
cx.dispatch_keystroke(&Keystroke::parse("d").unwrap());
});
assert_eq!(&*actions.borrow(), &["2 d"]);
@ -6006,13 +6147,14 @@ mod tests {
}
}
let (window_id, view_1) = cx.add_window(|cx| {
let window = cx.add_window(|cx| {
let view_2 = cx.add_view(|cx| {
cx.focus_self();
View2 {}
});
View1 { child: view_2 }
});
let view_1 = window.root(cx);
let view_2 = view_1.read_with(cx, |view, _| view.child.clone());
cx.update(|cx| {
@ -6076,7 +6218,7 @@ mod tests {
// Check that global actions do not have a binding, even if a binding does exist in another view
assert_eq!(
&available_actions(window_id, view_1.id(), cx),
&available_actions(window.window_id(), view_1.id(), cx),
&[
("test::Action1", vec![Keystroke::parse("a").unwrap()]),
("test::GlobalAction", vec![])
@ -6085,7 +6227,7 @@ mod tests {
// Check that view 1 actions and bindings are available even when called from view 2
assert_eq!(
&available_actions(window_id, view_2.id(), cx),
&available_actions(window.window_id(), view_2.id(), cx),
&[
("test::Action1", vec![Keystroke::parse("a").unwrap()]),
("test::Action2", vec![Keystroke::parse("b").unwrap()]),
@ -6138,7 +6280,8 @@ mod tests {
impl_actions!(test, [ActionWithArg]);
let (window_id, view) = cx.add_window(|_| View);
let window = cx.add_window(|_| View);
let view = window.root(cx);
cx.update(|cx| {
cx.add_global_action(|_: &ActionWithArg, _| {});
cx.add_bindings(vec![
@ -6147,7 +6290,7 @@ mod tests {
]);
});
let actions = cx.available_actions(window_id, view.id());
let actions = cx.available_actions(window.window_id(), view.id());
assert_eq!(
actions[0].1.as_any().downcast_ref::<ActionWithArg>(),
Some(&ActionWithArg { arg: false })
@ -6250,7 +6393,8 @@ mod tests {
}
}
let (_, view) = cx.add_window(|_| Counter(0));
let window = cx.add_window(|_| Counter(0));
let view = window.root(cx);
let condition1 = view.condition(cx, |view, _| view.0 == 2);
let condition2 = view.condition(cx, |view, _| view.0 == 3);
@ -6272,15 +6416,15 @@ mod tests {
#[crate::test(self)]
#[should_panic]
async fn test_view_condition_timeout(cx: &mut TestAppContext) {
let (_, view) = cx.add_window(|_| TestView::default());
view.condition(cx, |_, _| false).await;
let window = cx.add_window(|_| TestView::default());
window.root(cx).condition(cx, |_, _| false).await;
}
#[crate::test(self)]
#[should_panic(expected = "view dropped with pending condition")]
async fn test_view_condition_panic_on_drop(cx: &mut TestAppContext) {
let (window_id, _root_view) = cx.add_window(|_| TestView::default());
let view = cx.add_view(window_id, |_| TestView::default());
let window = cx.add_window(|_| TestView::default());
let view = window.add_view(cx, |_| TestView::default());
let condition = view.condition(cx, |_, _| false);
cx.update(|_| drop(view));
@ -6288,7 +6432,7 @@ mod tests {
}
#[crate::test(self)]
fn test_refresh_windows(cx: &mut AppContext) {
fn test_refresh_windows(cx: &mut TestAppContext) {
struct View(usize);
impl super::Entity for View {
@ -6305,22 +6449,21 @@ mod tests {
}
}
let (window_id, root_view) = cx.add_window(Default::default(), |_| View(0));
cx.update_window(window_id, |cx| {
let window = cx.add_window(|_| View(0));
let root_view = window.root(cx);
window.update(cx, |cx| {
assert_eq!(
cx.window.rendered_views[&root_view.id()].name(),
Some("render count: 0")
);
});
let view = cx
.update_window(window_id, |cx| {
let view = window.update(cx, |cx| {
cx.refresh_windows();
cx.add_view(|_| View(0))
})
.unwrap();
});
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
assert_eq!(
cx.window.rendered_views[&root_view.id()].name(),
Some("render count: 1")
@ -6333,7 +6476,7 @@ mod tests {
cx.update(|cx| cx.refresh_windows());
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
assert_eq!(
cx.window.rendered_views[&root_view.id()].name(),
Some("render count: 2")
@ -6349,7 +6492,7 @@ mod tests {
drop(view);
});
cx.update_window(window_id, |cx| {
window.update(cx, |cx| {
assert_eq!(
cx.window.rendered_views[&root_view.id()].name(),
Some("render count: 3")
@ -6397,7 +6540,7 @@ mod tests {
}
let events = Rc::new(RefCell::new(Vec::new()));
let (window_1, _) = cx.add_window(|cx: &mut ViewContext<View>| {
let window_1 = cx.add_window(|cx: &mut ViewContext<View>| {
cx.observe_window_activation({
let events = events.clone();
move |this, active, _| events.borrow_mut().push((this.0, active))
@ -6407,7 +6550,7 @@ mod tests {
});
assert_eq!(mem::take(&mut *events.borrow_mut()), [("window 1", true)]);
let (window_2, _) = cx.add_window(|cx: &mut ViewContext<View>| {
let window_2 = cx.add_window(|cx: &mut ViewContext<View>| {
cx.observe_window_activation({
let events = events.clone();
move |this, active, _| events.borrow_mut().push((this.0, active))
@ -6420,7 +6563,7 @@ mod tests {
[("window 1", false), ("window 2", true)]
);
let (window_3, _) = cx.add_window(|cx: &mut ViewContext<View>| {
let window_3 = cx.add_window(|cx: &mut ViewContext<View>| {
cx.observe_window_activation({
let events = events.clone();
move |this, active, _| events.borrow_mut().push((this.0, active))
@ -6433,25 +6576,25 @@ mod tests {
[("window 2", false), ("window 3", true)]
);
cx.simulate_window_activation(Some(window_2));
cx.simulate_window_activation(Some(window_2.window_id()));
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[("window 3", false), ("window 2", true)]
);
cx.simulate_window_activation(Some(window_1));
cx.simulate_window_activation(Some(window_1.window_id()));
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[("window 2", false), ("window 1", true)]
);
cx.simulate_window_activation(Some(window_3));
cx.simulate_window_activation(Some(window_3.window_id()));
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[("window 1", false), ("window 3", true)]
);
cx.simulate_window_activation(Some(window_3));
cx.simulate_window_activation(Some(window_3.window_id()));
assert_eq!(mem::take(&mut *events.borrow_mut()), []);
}
@ -6507,12 +6650,13 @@ mod tests {
let child_rendered = Rc::new(Cell::new(false));
let child_dropped = Rc::new(Cell::new(false));
let (_, root_view) = cx.add_window(|cx| Parent {
let window = cx.add_window(|cx| Parent {
child: Some(cx.add_view(|_| Child {
rendered: child_rendered.clone(),
dropped: child_dropped.clone(),
})),
});
let root_view = window.root(cx);
assert!(child_rendered.take());
assert!(!child_dropped.take());

View file

@ -6,7 +6,7 @@ use crate::{
platform::{Event, InputHandler, KeyDownEvent, Platform},
Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
WindowContext,
WindowContext, WindowHandle,
};
use collections::BTreeMap;
use futures::Future;
@ -60,7 +60,7 @@ impl TestAppContext {
RefCounts::new(leak_detector),
(),
);
cx.next_entity_id = first_entity_id;
cx.next_id = first_entity_id;
let cx = TestAppContext {
cx: Rc::new(RefCell::new(cx)),
foreground_platform,
@ -148,17 +148,18 @@ impl TestAppContext {
self.cx.borrow_mut().add_model(build_model)
}
pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
pub fn add_window<T, F>(&mut self, build_root_view: F) -> WindowHandle<T>
where
T: View,
F: FnOnce(&mut ViewContext<T>) -> T,
{
let (window_id, view) = self
let window = self
.cx
.borrow_mut()
.add_window(Default::default(), build_root_view);
self.simulate_window_activation(Some(window_id));
(window_id, view)
self.simulate_window_activation(Some(window.window_id()));
WindowHandle::new(window.window_id())
}
pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
@ -405,14 +406,20 @@ impl BorrowAppContext for TestAppContext {
}
impl BorrowWindowContext for TestAppContext {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
type Result<T> = T;
fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
self.cx
.borrow()
.read_window(window_id, f)
.expect("window was closed")
}
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window_id: usize,
f: F,
) -> T {
self.cx
.borrow_mut()
.update_window(window_id, f)

View file

@ -15,7 +15,7 @@ use crate::{
util::post_inc,
Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
View, ViewContext, ViewHandle, WindowInvalidation,
View, ViewContext, ViewHandle, WindowHandle, WindowInvalidation,
};
use anyhow::{anyhow, bail, Result};
use collections::{HashMap, HashSet};
@ -142,7 +142,9 @@ impl BorrowAppContext for WindowContext<'_> {
}
impl BorrowWindowContext for WindowContext<'_> {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
type Result<T> = T;
fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
if self.window_id == window_id {
f(self)
} else {
@ -150,7 +152,11 @@ impl BorrowWindowContext for WindowContext<'_> {
}
}
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window_id: usize,
f: F,
) -> T {
if self.window_id == window_id {
f(self)
} else {
@ -1151,15 +1157,15 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
where
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
let root_view = self.add_view(|cx| build_root_view(cx));
self.window.root_view = Some(root_view.clone().into_any());
self.window.focused_view_id = Some(root_view.id());
root_view
self.window.root_view = Some(root_view.into_any());
WindowHandle::new(self.window_id)
}
pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
@ -1176,7 +1182,7 @@ impl<'a> WindowContext<'a> {
F: FnOnce(&mut ViewContext<T>) -> Option<T>,
{
let window_id = self.window_id;
let view_id = post_inc(&mut self.next_entity_id);
let view_id = post_inc(&mut self.next_id);
let mut cx = ViewContext::mutable(self, view_id);
let handle = if let Some(view) = build_view(&mut cx) {
let mut keymap_context = KeymapContext::default();

View file

@ -61,7 +61,9 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await;
let (_, log_view) = cx.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx));
let log_view = cx
.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx))
.root(cx);
language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
message: "hello from the server".into(),

View file

@ -4,7 +4,7 @@ use collections::HashMap;
use gpui::{AppContext, AssetSource};
use serde_derive::Deserialize;
use util::iife;
use util::{iife, paths::PathExt};
#[derive(Deserialize, Debug)]
struct TypeConfig {
@ -48,14 +48,7 @@ impl FileAssociations {
// FIXME: Associate a type with the languages and have the file's langauge
// override these associations
iife!({
let suffix = path
.file_name()
.and_then(|os_str| os_str.to_str())
.and_then(|file_name| {
file_name
.find('.')
.and_then(|dot_index| file_name.get(dot_index + 1..))
})?;
let suffix = path.icon_suffix()?;
this.suffixes
.get(suffix)

View file

@ -1756,7 +1756,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx),
@ -1844,7 +1846,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@ -2195,7 +2199,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@ -2295,7 +2301,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {
@ -2368,7 +2376,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
toggle_expand_dir(&panel, "src/test", cx);
@ -2457,7 +2467,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "src/", cx);
@ -2603,7 +2615,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
let new_search_events_count = Arc::new(AtomicUsize::new(0));
@ -2690,7 +2704,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {

View file

@ -326,7 +326,9 @@ mod tests {
},
);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
// Create the project symbols view.
let symbols = cx.add_view(window_id, |cx| {

View file

@ -5,6 +5,7 @@ use gpui::{
elements::{Label, LabelStyle},
AnyElement, Element, View,
};
use util::paths::PathExt;
use workspace::WorkspaceLocation;
pub struct HighlightedText {
@ -61,7 +62,7 @@ impl HighlightedWorkspaceLocation {
.paths()
.iter()
.map(|path| {
let path = util::paths::compact(&path);
let path = path.compact();
let highlighted_text = Self::highlights_for_path(
path.as_ref(),
&string_match.positions,

View file

@ -11,6 +11,7 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate, PickerEvent};
use std::sync::Arc;
use util::paths::PathExt;
use workspace::{
notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
WORKSPACE_DB,
@ -134,7 +135,7 @@ impl PickerDelegate for RecentProjectsDelegate {
let combined_string = location
.paths()
.iter()
.map(|path| util::paths::compact(&path).to_string_lossy().into_owned())
.map(|path| path.compact().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("");
StringMatchCandidate::new(id, combined_string)

View file

@ -849,11 +849,13 @@ mod tests {
cx,
)
});
let (window_id, _root_view) = cx.add_window(|_| EmptyView);
let window = cx.add_window(|_| EmptyView);
let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let editor = cx.add_view(window.window_id(), |cx| {
Editor::for_buffer(buffer.clone(), None, cx)
});
let search_bar = cx.add_view(window_id, |cx| {
let search_bar = cx.add_view(window.window_id(), |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
@ -1229,7 +1231,8 @@ mod tests {
"Should pick a query with multiple results"
);
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
let (window_id, _root_view) = cx.add_window(|_| EmptyView);
let window = cx.add_window(|_| EmptyView);
let window_id = window.window_id();
let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
@ -1416,11 +1419,13 @@ mod tests {
"#
.unindent();
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
let (window_id, _root_view) = cx.add_window(|_| EmptyView);
let window = cx.add_window(|_| EmptyView);
let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let editor = cx.add_view(window.window_id(), |cx| {
Editor::for_buffer(buffer.clone(), None, cx)
});
let search_bar = cx.add_view(window_id, |cx| {
let search_bar = cx.add_view(window.window_id(), |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);

View file

@ -1447,7 +1447,9 @@ pub mod tests {
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
let (_, search_view) = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx));
let search_view = cx
.add_window(|cx| ProjectSearchView::new(search.clone(), cx))
.root(cx);
search_view.update(cx, |search_view, cx| {
search_view
@ -1564,7 +1566,9 @@ pub mod tests {
)
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let active_item = cx.read(|cx| {
workspace
@ -1748,7 +1752,9 @@ pub mod tests {
let worktree_id = project.read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let active_item = cx.read(|cx| {
workspace
@ -1866,7 +1872,9 @@ pub mod tests {
)
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
workspace.update(cx, |workspace, cx| {
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
});

View file

@ -1070,7 +1070,9 @@ mod tests {
});
let project = Project::test(params.fs.clone(), [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
(project, workspace)
}

View file

@ -30,49 +30,47 @@ pub mod legacy {
}
}
pub trait PathExt {
fn compact(&self) -> PathBuf;
fn icon_suffix(&self) -> Option<&str>;
}
impl<T: AsRef<Path>> PathExt for T {
/// Compacts a given file path by replacing the user's home directory
/// prefix with a tilde (`~`).
///
/// # Arguments
///
/// * `path` - A reference to a `Path` representing the file path to compact.
///
/// # Examples
///
/// ```
/// use std::path::{Path, PathBuf};
/// use util::paths::compact;
/// let path: PathBuf = [
/// util::paths::HOME.to_string_lossy().to_string(),
/// "some_file.txt".to_string(),
/// ]
/// .iter()
/// .collect();
/// if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
/// assert_eq!(compact(&path).to_str(), Some("~/some_file.txt"));
/// } else {
/// assert_eq!(compact(&path).to_str(), path.to_str());
/// }
/// ```
///
/// # Returns
///
/// * A `PathBuf` containing the compacted file path. If the input path
/// does not have the user's home directory prefix, or if we are not on
/// Linux or macOS, the original path is returned unchanged.
pub fn compact(path: &Path) -> PathBuf {
fn compact(&self) -> PathBuf {
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
match path.strip_prefix(HOME.as_path()) {
match self.as_ref().strip_prefix(HOME.as_path()) {
Ok(relative_path) => {
let mut shortened_path = PathBuf::new();
shortened_path.push("~");
shortened_path.push(relative_path);
shortened_path
}
Err(_) => path.to_path_buf(),
Err(_) => self.as_ref().to_path_buf(),
}
} else {
path.to_path_buf()
self.as_ref().to_path_buf()
}
}
fn icon_suffix(&self) -> Option<&str> {
let file_name = self.as_ref().file_name()?.to_str()?;
if file_name.starts_with(".") {
return file_name.strip_prefix(".");
}
self.as_ref()
.extension()
.map(|extension| extension.to_str())
.flatten()
}
}
@ -279,4 +277,42 @@ mod tests {
);
}
}
#[test]
fn test_path_compact() {
let path: PathBuf = [
HOME.to_string_lossy().to_string(),
"some_file.txt".to_string(),
]
.iter()
.collect();
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
assert_eq!(path.compact().to_str(), Some("~/some_file.txt"));
} else {
assert_eq!(path.compact().to_str(), path.to_str());
}
}
#[test]
fn test_path_suffix() {
// No dots in name
let path = Path::new("/a/b/c/file_name.rs");
assert_eq!(path.icon_suffix(), Some("rs"));
// Single dot in name
let path = Path::new("/a/b/c/file.name.rs");
assert_eq!(path.icon_suffix(), Some("rs"));
// Multiple dots in name
let path = Path::new("/a/b/c/long.file.name.rs");
assert_eq!(path.icon_suffix(), Some("rs"));
// Hidden file, no extension
let path = Path::new("/a/b/c/.gitignore");
assert_eq!(path.icon_suffix(), Some("gitignore"));
// Hidden file, with extension
let path = Path::new("/a/b/c/.eslintrc.js");
assert_eq!(path.icon_suffix(), Some("eslintrc.js"));
}
}

View file

@ -29,12 +29,8 @@ pub trait Panel: View {
fn is_zoomed(&self, _cx: &WindowContext) -> bool {
false
}
fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {
}
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {
}
fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
fn should_activate_on_event(_: &Self::Event) -> bool {
false
}

View file

@ -1972,7 +1972,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
pane.update(cx, |pane, cx| {
@ -1987,7 +1988,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@ -2065,7 +2067,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// 1. Add with a destination index
@ -2141,7 +2144,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
// singleton view
@ -2209,7 +2213,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
add_labeled_item(&pane, "A", false, cx);
@ -2256,7 +2261,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@ -2276,7 +2282,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
add_labeled_item(&pane, "A", true, cx);
@ -2299,7 +2306,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@ -2319,7 +2327,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@ -2339,7 +2348,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
add_labeled_item(&pane, "A", false, cx);

View file

@ -797,20 +797,14 @@ impl Workspace {
DB.next_id().await.unwrap_or(0)
};
let workspace = requesting_window_id
.and_then(|window_id| {
let window = requesting_window_id.and_then(|window_id| {
cx.update(|cx| {
cx.replace_root_view(window_id, |cx| {
Workspace::new(
workspace_id,
project_handle.clone(),
app_state.clone(),
cx,
)
Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
})
})
})
.unwrap_or_else(|| {
});
let window = window.unwrap_or_else(|| {
let window_bounds_override = window_bounds_env_override(&cx);
let (bounds, display) = if let Some(bounds) = window_bounds_override {
(Some(bounds), None)
@ -848,17 +842,15 @@ impl Workspace {
cx.add_window(
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
|cx| {
Workspace::new(
workspace_id,
project_handle.clone(),
app_state.clone(),
cx,
)
Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
},
)
.1
});
// We haven't yielded the main thread since obtaining the window handle,
// so the window exists.
let workspace = window.root(&cx).unwrap();
(app_state.initialize_workspace)(
workspace.downgrade(),
serialized_workspace.is_some(),
@ -868,7 +860,7 @@ impl Workspace {
.await
.log_err();
cx.update_window(workspace.window_id(), |cx| cx.activate_window());
window.update(&mut cx, |cx| cx.activate_window());
let workspace = workspace.downgrade();
notify_if_database_failed(&workspace, &mut cx);
@ -3987,7 +3979,7 @@ pub fn join_remote_project(
.await?;
let window_bounds_override = window_bounds_env_override(&cx);
let (_, workspace) = cx.add_window(
let window = cx.add_window(
(app_state.build_window_options)(
window_bounds_override,
None,
@ -3995,6 +3987,7 @@ pub fn join_remote_project(
),
|cx| Workspace::new(0, project, app_state.clone(), cx),
);
let workspace = window.root(&cx).unwrap();
(app_state.initialize_workspace)(
workspace.downgrade(),
false,
@ -4123,10 +4116,11 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
// Adding an item with no ambiguity renders the tab without detail.
let item1 = cx.add_view(window_id, |_| {
let item1 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
item
@ -4138,7 +4132,7 @@ mod tests {
// Adding an item that creates ambiguity increases the level of detail on
// both tabs.
let item2 = cx.add_view(window_id, |_| {
let item2 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
@ -4152,7 +4146,7 @@ mod tests {
// Adding an item that creates ambiguity increases the level of detail only
// on the ambiguous tabs. In this case, the ambiguity can't be resolved so
// we stop at the highest detail available.
let item3 = cx.add_view(window_id, |_| {
let item3 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
@ -4187,16 +4181,17 @@ mod tests {
.await;
let project = Project::test(fs, ["root1".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let worktree_id = project.read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
let item1 = cx.add_view(window_id, |cx| {
let item1 = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
});
let item2 = cx.add_view(window_id, |cx| {
let item2 = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
});
@ -4211,14 +4206,14 @@ mod tests {
);
});
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root1")
);
// Add a second item to a non-empty pane
workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("two.txt — root1")
);
project.read_with(cx, |project, cx| {
@ -4237,7 +4232,7 @@ mod tests {
.await
.unwrap();
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root1")
);
project.read_with(cx, |project, cx| {
@ -4257,14 +4252,14 @@ mod tests {
.await
.unwrap();
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root1, root2")
);
// Remove a project folder
project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root2")
);
}
@ -4277,18 +4272,19 @@ mod tests {
fs.insert_tree("/root", json!({ "one": "" })).await;
let project = Project::test(fs, ["root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
// When there are no dirty items, there's nothing to do.
let item1 = cx.add_view(window_id, |_| TestItem::new());
let item1 = window.add_view(cx, |_| TestItem::new());
workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
assert!(task.await.unwrap());
// When there are dirty untitled items, prompt to save each one. If the user
// cancels any prompt, then abort.
let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
let item3 = cx.add_view(window_id, |cx| {
let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
let item3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@ -4299,9 +4295,9 @@ mod tests {
});
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
cx.foreground().run_until_parked();
cx.simulate_prompt_answer(window_id, 2 /* cancel */);
cx.simulate_prompt_answer(window.window_id(), 2 /* cancel */);
cx.foreground().run_until_parked();
assert!(!cx.has_pending_prompt(window_id));
assert!(!cx.has_pending_prompt(window.window_id()));
assert!(!task.await.unwrap());
}
@ -4312,26 +4308,27 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let item1 = cx.add_view(window_id, |cx| {
let item1 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let item2 = cx.add_view(window_id, |cx| {
let item2 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_conflict(true)
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
});
let item3 = cx.add_view(window_id, |cx| {
let item3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_conflict(true)
.with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
});
let item4 = cx.add_view(window_id, |cx| {
let item4 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new_untitled(cx)])
@ -4359,10 +4356,10 @@ mod tests {
assert_eq!(pane.items_len(), 4);
assert_eq!(pane.active_item().unwrap().id(), item1.id());
});
assert!(cx.has_pending_prompt(window_id));
assert!(cx.has_pending_prompt(window.window_id()));
// Confirm saving item 1.
cx.simulate_prompt_answer(window_id, 0);
cx.simulate_prompt_answer(window.window_id(), 0);
cx.foreground().run_until_parked();
// Item 1 is saved. There's a prompt to save item 3.
@ -4373,10 +4370,10 @@ mod tests {
assert_eq!(pane.items_len(), 3);
assert_eq!(pane.active_item().unwrap().id(), item3.id());
});
assert!(cx.has_pending_prompt(window_id));
assert!(cx.has_pending_prompt(window.window_id()));
// Cancel saving item 3.
cx.simulate_prompt_answer(window_id, 1);
cx.simulate_prompt_answer(window.window_id(), 1);
cx.foreground().run_until_parked();
// Item 3 is reloaded. There's a prompt to save item 4.
@ -4387,10 +4384,10 @@ mod tests {
assert_eq!(pane.items_len(), 2);
assert_eq!(pane.active_item().unwrap().id(), item4.id());
});
assert!(cx.has_pending_prompt(window_id));
assert!(cx.has_pending_prompt(window.window_id()));
// Confirm saving item 4.
cx.simulate_prompt_answer(window_id, 0);
cx.simulate_prompt_answer(window.window_id(), 0);
cx.foreground().run_until_parked();
// There's a prompt for a path for item 4.
@ -4414,13 +4411,14 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
// Create several workspace items with single project entries, and two
// workspace items with multiple project entries.
let single_entry_items = (0..=4)
.map(|project_entry_id| {
cx.add_view(window_id, |cx| {
window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(
@ -4431,7 +4429,7 @@ mod tests {
})
})
.collect::<Vec<_>>();
let item_2_3 = cx.add_view(window_id, |cx| {
let item_2_3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_singleton(false)
@ -4440,7 +4438,7 @@ mod tests {
single_entry_items[3].read(cx).project_items[0].clone(),
])
});
let item_3_4 = cx.add_view(window_id, |cx| {
let item_3_4 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_singleton(false)
@ -4492,7 +4490,7 @@ mod tests {
&[ProjectEntryId::from_proto(0)]
);
});
cx.simulate_prompt_answer(window_id, 0);
cx.simulate_prompt_answer(window.window_id(), 0);
cx.foreground().run_until_parked();
left_pane.read_with(cx, |pane, cx| {
@ -4501,7 +4499,7 @@ mod tests {
&[ProjectEntryId::from_proto(2)]
);
});
cx.simulate_prompt_answer(window_id, 0);
cx.simulate_prompt_answer(window.window_id(), 0);
cx.foreground().run_until_parked();
close.await.unwrap();
@ -4517,10 +4515,11 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let item = cx.add_view(window_id, |cx| {
let item = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let item_id = item.id();
@ -4560,7 +4559,7 @@ mod tests {
item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
// Deactivating the window still saves the file.
cx.simulate_window_activation(Some(window_id));
cx.simulate_window_activation(Some(window.window_id()));
item.update(cx, |item, cx| {
cx.focus_self();
item.is_dirty = true;
@ -4602,7 +4601,7 @@ mod tests {
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
.await
.unwrap();
assert!(!cx.has_pending_prompt(window_id));
assert!(!cx.has_pending_prompt(window.window_id()));
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
// Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
@ -4623,7 +4622,7 @@ mod tests {
let _close_items =
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
deterministic.run_until_parked();
assert!(cx.has_pending_prompt(window_id));
assert!(cx.has_pending_prompt(window.window_id()));
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
}
@ -4634,9 +4633,10 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let item = cx.add_view(window_id, |cx| {
let item = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
@ -4687,7 +4687,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| {
let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
@ -4834,7 +4835,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
// Add panel_1 on the left, panel_2 on the right.
@ -4989,7 +4991,7 @@ mod tests {
// If focus is transferred to another view that's not a panel or another pane, we still show
// the panel as zoomed.
let focus_receiver = cx.add_view(window_id, |_| EmptyView);
let focus_receiver = window.add_view(cx, |_| EmptyView);
focus_receiver.update(cx, |_, cx| cx.focus_self());
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.98.0"
version = "0.99.0"
publish = false
[lib]

View file

@ -1,5 +1,6 @@
name = "Shell Script"
path_suffixes = [".sh", ".bash", ".bashrc", ".bash_profile", ".bash_aliases", ".bash_logout", ".profile", ".zsh", ".zshrc", ".zshenv", ".zsh_profile", ".zsh_aliases", ".zsh_histfile", ".zlogin"]
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin"]
line_comment = "# "
first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
brackets = [
{ start = "[", end = "]", close = true, newline = false },

View file

@ -54,5 +54,5 @@
(
(command (_) @constant)
(.match? @constant "^-")
(#match? @constant "^-")
)

View file

@ -86,7 +86,7 @@
(identifier) @variable
((identifier) @constant
(.match? @constant "^_*[A-Z][A-Z\\d_]*$"))
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
(call_expression
function: (identifier) @function)
@ -106,3 +106,4 @@
(primitive_type)
(sized_type_specifier)
] @type

View file

@ -1,7 +1,7 @@
(preproc_def
value: (preproc_arg) @content
(.set! "language" "c"))
(#set! "language" "c"))
(preproc_function_def
value: (preproc_arg) @content
(.set! "language" "c"))
(#set! "language" "c"))

View file

@ -31,13 +31,13 @@
declarator: (field_identifier) @function)
((namespace_identifier) @type
(.match? @type "^[A-Z]"))
(#match? @type "^[A-Z]"))
(auto) @type
(type_identifier) @type
((identifier) @constant
(.match? @constant "^_*[A-Z][A-Z\\d_]*$"))
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
(field_identifier) @property
(statement_identifier) @label

View file

@ -1,7 +1,7 @@
(preproc_def
value: (preproc_arg) @content
(.set! "language" "c++"))
(#set! "language" "c++"))
(preproc_function_def
value: (preproc_arg) @content
(.set! "language" "c++"))
(#set! "language" "c++"))

View file

@ -46,7 +46,7 @@
(property_name)
(plain_value)
] @variable.special
(.match? @variable.special "^--")
(#match? @variable.special "^--")
)
[

View file

@ -3,7 +3,7 @@
operator: "@"
operand: (call
target: (identifier) @unary
(.match? @unary "^(doc)$"))
(#match? @unary "^(doc)$"))
) @context
.
(call
@ -18,10 +18,10 @@
target: (identifier) @name)
operator: "when")
])
(.match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
(#match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
)
(call
target: (identifier) @name
(arguments (alias) @name)
(.match? @name "^(defmodule|defprotocol)$")) @item
(#match? @name "^(defmodule|defprotocol)$")) @item

View file

@ -54,13 +54,13 @@
(sigil_name) @__name__
quoted_start: _ @string
quoted_end: _ @string
(.match? @__name__ "^[sS]$")) @string
(#match? @__name__ "^[sS]$")) @string
(sigil
(sigil_name) @__name__
quoted_start: _ @string.regex
quoted_end: _ @string.regex
(.match? @__name__ "^[rR]$")) @string.regex
(#match? @__name__ "^[rR]$")) @string.regex
(sigil
(sigil_name) @__name__
@ -69,7 +69,7 @@
(
(identifier) @comment.unused
(.match? @comment.unused "^_")
(#match? @comment.unused "^_")
)
(call
@ -91,7 +91,7 @@
operator: "|>"
right: (identifier))
])
(.match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
(#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
(binary_operator
operator: "|>"
@ -99,15 +99,15 @@
(call
target: (identifier) @keyword
(.match? @keyword "^(def|defdelegate|defexception|defguard|defguardp|defimpl|defmacro|defmacrop|defmodule|defn|defnp|defoverridable|defp|defprotocol|defstruct)$"))
(#match? @keyword "^(def|defdelegate|defexception|defguard|defguardp|defimpl|defmacro|defmacrop|defmodule|defn|defnp|defoverridable|defp|defprotocol|defstruct)$"))
(call
target: (identifier) @keyword
(.match? @keyword "^(alias|case|cond|else|for|if|import|quote|raise|receive|require|reraise|super|throw|try|unless|unquote|unquote_splicing|use|with)$"))
(#match? @keyword "^(alias|case|cond|else|for|if|import|quote|raise|receive|require|reraise|super|throw|try|unless|unquote|unquote_splicing|use|with)$"))
(
(identifier) @constant.builtin
(.match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
(#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
)
(unary_operator
@ -121,7 +121,7 @@
(sigil)
(boolean)
] @comment.doc))
(.match? @__attribute__ "^(moduledoc|typedoc|doc)$"))
(#match? @__attribute__ "^(moduledoc|typedoc|doc)$"))
(comment) @comment
@ -150,4 +150,4 @@
((sigil
(sigil_name) @_sigil_name
(quoted_content) @embedded)
(.eq? @_sigil_name "H"))
(#eq? @_sigil_name "H"))

View file

@ -3,5 +3,5 @@
((sigil
(sigil_name) @_sigil_name
(quoted_content) @content)
(.eq? @_sigil_name "H")
(.set! language "heex"))
(#eq? @_sigil_name "H")
(#set! language "heex"))

View file

@ -1,7 +1,7 @@
(call
target: (identifier) @context
(arguments (alias) @name)
(.match? @context "^(defmodule|defprotocol)$")) @item
(#match? @context "^(defmodule|defprotocol)$")) @item
(call
target: (identifier) @context
@ -23,4 +23,4 @@
")" @context.extra))
operator: "when")
])
(.match? @context "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
(#match? @context "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item

View file

@ -1,2 +1,2 @@
((glsl_content) @content
(.set! "language" "glsl"))
(#set! "language" "glsl"))

View file

@ -1,7 +1,7 @@
((code) @content
(.set! "language" "ruby")
(.set! "combined"))
(#set! "language" "ruby")
(#set! "combined"))
((content) @content
(.set! "language" "html")
(.set! "combined"))
(#set! "language" "html")
(#set! "combined"))

View file

@ -74,7 +74,7 @@
(sized_type_specifier) @type
((identifier) @constant
(.match? @constant "^[A-Z][A-Z\\d_]*$"))
(#match? @constant "^[A-Z][A-Z\\d_]*$"))
(identifier) @variable
@ -114,5 +114,5 @@
(
(identifier) @variable.builtin
(.match? @variable.builtin "^gl_")
(#match? @variable.builtin "^gl_")
)

View file

@ -5,9 +5,9 @@
(expression_value)
(ending_expression_value)
] @content)
(.set! language "elixir")
(.set! combined)
(#set! language "elixir")
(#set! combined)
)
((expression (expression_value) @content)
(.set! language "elixir"))
(#set! language "elixir"))

View file

@ -1,7 +1,7 @@
(script_element
(raw_text) @content
(.set! "language" "javascript"))
(#set! "language" "javascript"))
(style_element
(raw_text) @content
(.set! "language" "css"))
(#set! "language" "css"))

View file

@ -44,7 +44,7 @@
; Special identifiers
((identifier) @type
(.match? @type "^[A-Z]"))
(#match? @type "^[A-Z]"))
(type_identifier) @type
(predefined_type) @type.builtin
@ -53,7 +53,7 @@
(shorthand_property_identifier)
(shorthand_property_identifier_pattern)
] @constant
(.match? @constant "^_*[A-Z_][A-Z\\d_]*$"))
(#match? @constant "^_*[A-Z_][A-Z\\d_]*$"))
; Literals

View file

@ -127,7 +127,7 @@
(identifier) @variable
((identifier) @variable.special
(.eq? @variable.special "self"))
(#eq? @variable.special "self"))
(variable_list
attribute: (attribute
@ -137,7 +137,7 @@
;; Constants
((identifier) @constant
(.match? @constant "^[A-Z][A-Z_0-9]*$"))
(#match? @constant "^[A-Z][A-Z_0-9]*$"))
(vararg_expression) @constant
@ -158,7 +158,7 @@
[
"{"
"}"
] @method.constructor)
] @constructor)
;; Functions
@ -180,7 +180,7 @@
(function_call
(identifier) @function.builtin
(.any-of? @function.builtin
(#any-of? @function.builtin
;; built-in functions in Lua 5.1
"assert" "collectgarbage" "dofile" "error" "getfenv" "getmetatable" "ipairs"
"load" "loadfile" "loadstring" "module" "next" "pairs" "pcall" "print"

View file

@ -43,15 +43,15 @@
(relative_scope) @variable.builtin
((name) @constant
(.match? @constant "^_?[A-Z][A-Z\\d_]+$"))
(#match? @constant "^_?[A-Z][A-Z\\d_]+$"))
((name) @constant.builtin
(.match? @constant.builtin "^__[A-Z][A-Z\d_]+__$"))
(#match? @constant.builtin "^__[A-Z][A-Z\d_]+__$"))
((name) @method.constructor
(.match? @method.constructor "^[A-Z]"))
((name) @constructor
(#match? @constructor "^[A-Z]"))
((name) @variable.builtin
(.eq? @variable.builtin "this"))
(#eq? @variable.builtin "this"))
(variable_name) @variable

View file

@ -1,3 +1,3 @@
((text) @content
(.set! "language" "html")
(.set! "combined"))
(#set! "language" "html")
(#set! "combined"))

View file

@ -18,16 +18,16 @@
; Identifier naming conventions
((identifier) @type
(.match? @type "^[A-Z]"))
(#match? @type "^[A-Z]"))
((identifier) @constant
(.match? @constant "^_*[A-Z][A-Z\\d_]*$"))
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
; Builtin functions
((call
function: (identifier) @function.builtin)
(.match?
(#match?
@function.builtin
"^(abs|all|any|ascii|bin|bool|breakpoint|bytearray|bytes|callable|chr|classmethod|compile|complex|delattr|dict|dir|divmod|enumerate|eval|exec|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|isinstance|issubclass|iter|len|list|locals|map|max|memoryview|min|next|object|oct|open|ord|pow|print|property|range|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|vars|zip|__import__)$"))

File diff suppressed because one or more lines are too long

View file

@ -6,5 +6,5 @@
(symbol) @name
(list . (symbol) @name)
]
(.match? @start-symbol "^define")
(#match? @start-symbol "^define")
) @item

View file

@ -33,12 +33,12 @@
(identifier) @variable
((identifier) @keyword
(.match? @keyword "^(private|protected|public)$"))
(#match? @keyword "^(private|protected|public)$"))
; Function calls
((identifier) @function.method.builtin
(.eq? @function.method.builtin "require"))
(#eq? @function.method.builtin "require"))
"defined?" @function.method.builtin
@ -60,7 +60,7 @@
] @property
((identifier) @constant.builtin
(.match? @constant.builtin "^__(FILE|LINE|ENCODING)__$"))
(#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$"))
(file) @constant.builtin
(line) @constant.builtin
@ -71,7 +71,7 @@
) @constant.builtin
((constant) @constant
(.match? @constant "^[A-Z\\d_]+$"))
(#match? @constant "^[A-Z\\d_]+$"))
(constant) @type

View file

@ -38,11 +38,11 @@
; Assume uppercase names are types/enum-constructors
((identifier) @type
(.match? @type "^[A-Z]"))
(#match? @type "^[A-Z]"))
; Assume all-caps names are constants
((identifier) @constant
(.match? @constant "^_*[A-Z][A-Z\\d_]*$"))
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
[
"("

View file

@ -1,7 +1,7 @@
(macro_invocation
(token_tree) @content
(.set! "language" "rust"))
(#set! "language" "rust"))
(macro_rule
(token_tree) @content
(.set! "language" "rust"))
(#set! "language" "rust"))

View file

@ -14,7 +14,7 @@
(directive)] @comment
((symbol) @operator
(.match? @operator "^(\\+|-|\\*|/|=|>|<|>=|<=)$"))
(#match? @operator "^(\\+|-|\\*|/|=|>|<|>=|<=)$"))
(list
.
@ -23,6 +23,6 @@
(list
.
(symbol) @keyword
(.match? @keyword
(#match? @keyword
"^(define-syntax|let\\*|lambda|λ|case|=>|quote-splicing|unquote-splicing|set!|let|letrec|letrec-syntax|let-values|let\\*-values|do|else|define|cond|syntax-rules|unquote|begin|quote|let-syntax|and|if|quasiquote|letrec|delay|or|when|unless|identifier-syntax|assert|library|export|import|rename|only|except|prefix)$"
))

View file

@ -6,5 +6,5 @@
(symbol) @name
(list . (symbol) @name)
]
(.match? @start-symbol "^define")
(#match? @start-symbol "^define")
) @item

View file

@ -2,27 +2,27 @@
; --------------
(script_element
(raw_text) @content
(.set! "language" "javascript"))
(#set! "language" "javascript"))
((script_element
(start_tag
(attribute
(quoted_attribute_value (attribute_value) @_language)))
(raw_text) @content)
(.eq? @_language "ts")
(.set! "language" "typescript"))
(#eq? @_language "ts")
(#set! "language" "typescript"))
((script_element
(start_tag
(attribute
(quoted_attribute_value (attribute_value) @_language)))
(raw_text) @content)
(.eq? @_language "typescript")
(.set! "language" "typescript"))
(#eq? @_language "typescript")
(#set! "language" "typescript"))
(style_element
(raw_text) @content
(.set! "language" "css"))
(#set! "language" "css"))
((raw_text_expr) @content
(.set! "language" "javascript"))
(#set! "language" "javascript"))

View file

@ -43,11 +43,11 @@
; Special identifiers
((identifier) @method.constructor
(.match? @method.constructor "^[A-Z]"))
((identifier) @constructor
(#match? @constructor "^[A-Z]"))
((identifier) @type
(.match? @type "^[A-Z]"))
(#match? @type "^[A-Z]"))
(type_identifier) @type
(predefined_type) @type.builtin
@ -56,7 +56,7 @@
(shorthand_property_identifier)
(shorthand_property_identifier_pattern)
] @constant
(.match? @constant "^_*[A-Z_][A-Z\\d_]*$"))
(#match? @constant "^_*[A-Z_][A-Z\\d_]*$"))
; Literals

View file

@ -981,7 +981,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@ -1293,7 +1295,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
// Open a file within an existing worktree.
workspace
@ -1334,7 +1338,9 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
// Create a new untitled buffer
@ -1427,7 +1433,9 @@ mod tests {
let project = Project::test(app_state.fs.clone(), [], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
// Create a new untitled buffer
cx.dispatch_action(window_id, NewFile);
@ -1478,7 +1486,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@ -1552,7 +1562,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let entries = cx.read(|cx| workspace.file_project_paths(cx));
@ -1829,7 +1841,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let entries = cx.read(|cx| workspace.file_project_paths(cx));
@ -2071,7 +2085,8 @@ mod tests {
cx.foreground().run_until_parked();
let (window_id, _view) = cx.add_window(|_| TestView);
let window = cx.add_window(|_| TestView);
let window_id = window.window_id();
// Test loading the keymap base at all
assert_key_bindings_for(
@ -2241,7 +2256,8 @@ mod tests {
cx.foreground().run_until_parked();
let (window_id, _view) = cx.add_window(|_| TestView);
let window = cx.add_window(|_| TestView);
let window_id = window.window_id();
// Test loading the keymap base at all
assert_key_bindings_for(

View file

@ -8,7 +8,6 @@
"build-licenses": "ts-node ./src/build_licenses.ts",
"build-tokens": "ts-node ./src/build_tokens.ts",
"build-types": "ts-node ./src/build_types.ts",
"generate-syntax": "ts-node ./src/types/extract_syntax_types.ts",
"test": "vitest"
},
"author": "Zed Industries (https://github.com/zed-industries/)",

View file

@ -21,7 +21,9 @@ function clear_themes(theme_directory: string) {
}
}
const all_themes: Theme[] = themes.map((theme) => create_theme(theme))
const all_themes: Theme[] = themes.map((theme) =>
create_theme(theme)
)
function write_themes(themes: Theme[], output_directory: string) {
clear_themes(output_directory)
@ -32,7 +34,10 @@ function write_themes(themes: Theme[], output_directory: string) {
const style_tree = app()
const style_tree_json = JSON.stringify(style_tree, null, 2)
const temp_path = path.join(temp_directory, `${theme.name}.json`)
const out_path = path.join(output_directory, `${theme.name}.json`)
const out_path = path.join(
output_directory,
`${theme.name}.json`
)
fs.writeFileSync(temp_path, style_tree_json)
fs.renameSync(temp_path, out_path)
console.log(`- ${out_path} created`)

View file

@ -83,6 +83,8 @@ function write_tokens(themes: Theme[], tokens_directory: string) {
console.log(`- ${METADATA_FILE} created`)
}
const all_themes: Theme[] = themes.map((theme) => create_theme(theme))
const all_themes: Theme[] = themes.map((theme) =>
create_theme(theme)
)
write_tokens(all_themes, TOKENS_DIRECTORY)

View file

@ -10,7 +10,10 @@ export type Margin = {
}
interface IconButtonOptions {
layer?: Theme["lowest"] | Theme["middle"] | Theme["highest"]
layer?:
| Theme["lowest"]
| Theme["middle"]
| Theme["highest"]
color?: keyof Theme["lowest"]
margin?: Partial<Margin>
}

View file

@ -12,13 +12,11 @@ type TabBarButtonProps = TabBarButtonOptions & {
state?: Partial<Record<InteractiveState, Partial<TabBarButtonOptions>>>
}
export function tab_bar_button(
theme: Theme,
{ icon, color = "base" }: TabBarButtonProps
) {
export function tab_bar_button(theme: Theme, { icon, color = "base" }: TabBarButtonProps) {
const button_spacing = 8
return interactive({
return (
interactive({
base: {
icon: {
color: foreground(theme.middle, color),
@ -31,10 +29,7 @@ export function tab_bar_button(
container: {
corner_radius: 4,
padding: {
top: 4,
bottom: 4,
left: 4,
right: 4,
top: 4, bottom: 4, left: 4, right: 4
},
margin: {
left: button_spacing / 2,
@ -46,13 +41,15 @@ export function tab_bar_button(
hovered: {
container: {
background: background(theme.middle, color, "hovered"),
},
}
},
clicked: {
container: {
background: background(theme.middle, color, "pressed"),
},
}
},
},
})
)
}

View file

@ -9,7 +9,10 @@ import { useTheme, Theme } from "../theme"
import { Margin } from "./icon_button"
interface TextButtonOptions {
layer?: Theme["lowest"] | Theme["middle"] | Theme["highest"]
layer?:
| Theme["lowest"]
| Theme["middle"]
| Theme["highest"]
color?: keyof Theme["lowest"]
margin?: Partial<Margin>
text_properties?: TextProperties

View file

@ -55,6 +55,6 @@ export default function app(): any {
tooltip: tooltip(),
terminal: terminal(),
assistant: assistant(),
feedback: feedback(),
feedback: feedback()
}
}

View file

@ -8,24 +8,23 @@ type RoleCycleButton = TextStyle & {
}
// TODO: Replace these with zed types
type RemainingTokens = TextStyle & {
background: string
margin: { top: number; right: number }
background: string,
margin: { top: number, right: number },
padding: {
right: number
left: number
top: number
bottom: number
}
corner_radius: number
right: number,
left: number,
top: number,
bottom: number,
},
corner_radius: number,
}
export default function assistant(): any {
const theme = useTheme()
const interactive_role = (
color: StyleSets
): Interactive<RoleCycleButton> => {
return interactive({
const interactive_role = (color: StyleSets): Interactive<RoleCycleButton> => {
return (
interactive({
base: {
...text(theme.highest, "sans", color, { size: "sm" }),
},
@ -37,19 +36,22 @@ export default function assistant(): any {
clicked: {
...text(theme.highest, "sans", color, { size: "sm" }),
background: background(theme.highest, color, "pressed"),
},
}
},
})
)
}
const tokens_remaining = (color: StyleSets): RemainingTokens => {
return {
return (
{
...text(theme.highest, "mono", color, { size: "xs" }),
background: background(theme.highest, "on", "default"),
margin: { top: 12, right: 20 },
padding: { right: 4, left: 4, top: 1, bottom: 1 },
corner_radius: 6,
}
)
}
return {
@ -91,10 +93,7 @@ export default function assistant(): any {
base: {
background: background(theme.middle),
padding: { top: 4, bottom: 4 },
border: border(theme.middle, "default", {
top: true,
overlay: true,
}),
border: border(theme.middle, "default", { top: true, overlay: true }),
},
state: {
hovered: {
@ -102,7 +101,7 @@ export default function assistant(): any {
},
clicked: {
background: background(theme.middle, "pressed"),
},
}
},
}),
saved_at: {

View file

@ -9,9 +9,9 @@ import {
} from "./components"
import hover_popover from "./hover_popover"
import { build_syntax } from "../theme/syntax"
import { interactive, toggleable } from "../element"
import { useTheme } from "../theme"
import chroma from "chroma-js"
export default function editor(): any {
const theme = useTheme()
@ -48,28 +48,16 @@ export default function editor(): any {
}
}
const syntax = build_syntax()
return {
text_color: theme.syntax.primary.color,
text_color: syntax.primary.color,
background: background(layer),
active_line_background: with_opacity(background(layer, "on"), 0.75),
highlighted_line_background: background(layer, "on"),
// Inline autocomplete suggestions, Co-pilot suggestions, etc.
hint: chroma
.mix(
theme.ramps.neutral(0.6).hex(),
theme.ramps.blue(0.4).hex(),
0.45,
"lch"
)
.hex(),
suggestion: chroma
.mix(
theme.ramps.neutral(0.4).hex(),
theme.ramps.blue(0.4).hex(),
0.45,
"lch"
)
.hex(),
hint: syntax.hint,
suggestion: syntax.predictive,
code_actions: {
indicator: toggleable({
base: interactive({
@ -267,8 +255,8 @@ export default function editor(): any {
invalid_warning_diagnostic: diagnostic(theme.middle, "base"),
hover_popover: hover_popover(),
link_definition: {
color: theme.syntax.link_uri.color,
underline: theme.syntax.link_uri.underline,
color: syntax.link_uri.color,
underline: syntax.link_uri.underline,
},
jump_icon: interactive({
base: {
@ -318,7 +306,7 @@ export default function editor(): any {
? with_opacity(theme.ramps.green(0.5).hex(), 0.8)
: with_opacity(theme.ramps.green(0.4).hex(), 0.8),
},
selections: foreground(layer, "accent"),
selections: foreground(layer, "accent")
},
composition_mark: {
underline: {
@ -326,6 +314,6 @@ export default function editor(): any {
color: border_color(layer),
},
},
syntax: theme.syntax,
syntax,
}
}

View file

@ -37,7 +37,7 @@ export default function feedback(): any {
...text(theme.highest, "mono", "on", "disabled"),
background: background(theme.highest, "on", "disabled"),
border: border(theme.highest, "on", "disabled"),
},
}
},
}),
button_margin: 8,

View file

@ -152,7 +152,7 @@ export default function picker(): any {
0.5
),
},
},
}
}),
}
}

View file

@ -64,17 +64,17 @@ export default function project_panel(): any {
const unselected_default_style = merge(
base_properties,
unselected?.default ?? {},
{}
{},
)
const unselected_hovered_style = merge(
base_properties,
{ background: background(theme.middle, "hovered") },
unselected?.hovered ?? {}
unselected?.hovered ?? {},
)
const unselected_clicked_style = merge(
base_properties,
{ background: background(theme.middle, "pressed") },
unselected?.clicked ?? {}
unselected?.clicked ?? {},
)
const selected_default_style = merge(
base_properties,
@ -82,7 +82,7 @@ export default function project_panel(): any {
background: background(theme.lowest),
text: text(theme.lowest, "sans", { size: "sm" }),
},
selected_style?.default ?? {}
selected_style?.default ?? {},
)
const selected_hovered_style = merge(
base_properties,
@ -90,7 +90,7 @@ export default function project_panel(): any {
background: background(theme.lowest, "hovered"),
text: text(theme.lowest, "sans", { size: "sm" }),
},
selected_style?.hovered ?? {}
selected_style?.hovered ?? {},
)
const selected_clicked_style = merge(
base_properties,
@ -98,7 +98,7 @@ export default function project_panel(): any {
background: background(theme.lowest, "pressed"),
text: text(theme.lowest, "sans", { size: "sm" }),
},
selected_style?.clicked ?? {}
selected_style?.clicked ?? {},
)
return toggleable({
@ -175,7 +175,7 @@ export default function project_panel(): any {
default: {
icon_color: foreground(theme.middle, "variant"),
},
}
},
),
cut_entry: entry(
{
@ -190,7 +190,7 @@ export default function project_panel(): any {
size: "sm",
}),
},
}
},
),
filename_editor: {
background: background(theme.middle, "on"),

View file

@ -34,14 +34,10 @@ export default function status_bar(): any {
...text(layer, "mono", "variant", { size: "xs" }),
},
active_language: text_button({
color: "variant",
}),
auto_update_progress_message: text(layer, "sans", "variant", {
size: "xs",
}),
auto_update_done_message: text(layer, "sans", "variant", {
size: "xs",
color: "variant"
}),
auto_update_progress_message: text(layer, "sans", "variant", { size: "xs" }),
auto_update_done_message: text(layer, "sans", "variant", { size: "xs" }),
lsp_status: interactive({
base: {
...diagnostic_status_container,
@ -53,7 +49,7 @@ export default function status_bar(): any {
},
state: {
hovered: {
message: text(layer, "sans"),
message: text(layer, "sans", { size: "xs" }),
icon_color: foreground(layer),
background: background(layer, "hovered"),
},

View file

@ -187,10 +187,10 @@ export function titlebar(): any {
project_name_divider: text(theme.lowest, "sans", "variant"),
project_menu_button: toggleable_text_button(theme, {
color: "base",
color: 'base',
}),
git_menu_button: toggleable_text_button(theme, {
color: "variant",
color: 'variant',
}),
// Collaborators

View file

@ -1,12 +1,12 @@
import { Scale, Color } from "chroma-js"
import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax"
export { Syntax, ThemeSyntax, SyntaxHighlightStyle }
import {
ThemeConfig,
ThemeAppearance,
ThemeConfigInputColors,
} from "./theme_config"
import { get_ramps } from "./ramps"
import { syntaxStyle } from "./syntax"
import { Syntax } from "../types/syntax"
export interface Theme {
name: string
@ -31,7 +31,7 @@ export interface Theme {
modal_shadow: Shadow
players: Players
syntax: Syntax
syntax?: Partial<ThemeSyntax>
}
export interface Meta {
@ -115,7 +115,12 @@ export interface Style {
}
export function create_theme(theme: ThemeConfig): Theme {
const { name, appearance, input_color } = theme
const {
name,
appearance,
input_color,
override: { syntax },
} = theme
const is_light = appearance === ThemeAppearance.Light
const color_ramps: ThemeConfigInputColors = input_color
@ -157,11 +162,6 @@ export function create_theme(theme: ThemeConfig): Theme {
"7": player(ramps.yellow),
}
const syntax = syntaxStyle(
ramps,
theme.override.syntax ? theme.override.syntax : {}
)
return {
name,
is_light,

View file

@ -1,45 +1,325 @@
import deepmerge from "deepmerge"
import { font_weights, ThemeConfigInputSyntax, RampSet } from "../common"
import { Syntax, SyntaxHighlightStyle, allSyntaxKeys } from "../types/syntax"
import { FontWeight, font_weights, useTheme } from "../common"
import chroma from "chroma-js"
// Apply defaults to any missing syntax properties that are not defined manually
function apply_defaults(
ramps: RampSet,
syntax_highlights: Partial<Syntax>
): Syntax {
const restKeys: (keyof Syntax)[] = allSyntaxKeys.filter(
(key) => !syntax_highlights[key]
export interface SyntaxHighlightStyle {
color?: string
weight?: FontWeight
underline?: boolean
italic?: boolean
}
export interface Syntax {
// == Text Styles ====== /
comment: SyntaxHighlightStyle
// elixir: doc comment
"comment.doc": SyntaxHighlightStyle
primary: SyntaxHighlightStyle
predictive: SyntaxHighlightStyle
hint: SyntaxHighlightStyle
// === Formatted Text ====== /
emphasis: SyntaxHighlightStyle
"emphasis.strong": SyntaxHighlightStyle
title: SyntaxHighlightStyle
link_uri: SyntaxHighlightStyle
link_text: SyntaxHighlightStyle
/** md: indented_code_block, fenced_code_block, code_span */
"text.literal": SyntaxHighlightStyle
// == Punctuation ====== /
punctuation: SyntaxHighlightStyle
/** Example: `(`, `[`, `{`...*/
"punctuation.bracket": SyntaxHighlightStyle
/**., ;*/
"punctuation.delimiter": SyntaxHighlightStyle
// js, ts: ${, } in a template literal
// yaml: *, &, ---, ...
"punctuation.special": SyntaxHighlightStyle
// md: list_marker_plus, list_marker_dot, etc
"punctuation.list_marker": SyntaxHighlightStyle
// == Strings ====== /
string: SyntaxHighlightStyle
// css: color_value
// js: this, super
// toml: offset_date_time, local_date_time...
"string.special": SyntaxHighlightStyle
// elixir: atom, quoted_atom, keyword, quoted_keyword
// ruby: simple_symbol, delimited_symbol...
"string.special.symbol"?: SyntaxHighlightStyle
// elixir, python, yaml...: escape_sequence
"string.escape"?: SyntaxHighlightStyle
// Regular expressions
"string.regex"?: SyntaxHighlightStyle
// == Types ====== /
// We allow Function here because all JS objects literals have this property
constructor: SyntaxHighlightStyle | Function // eslint-disable-line @typescript-eslint/ban-types
variant: SyntaxHighlightStyle
type: SyntaxHighlightStyle
// js: predefined_type
"type.builtin"?: SyntaxHighlightStyle
// == Values
variable: SyntaxHighlightStyle
// this, ...
// css: -- (var(--foo))
// lua: self
"variable.special"?: SyntaxHighlightStyle
// c: statement_identifier,
label: SyntaxHighlightStyle
// css: tag_name, nesting_selector, universal_selector...
tag: SyntaxHighlightStyle
// css: attribute, pseudo_element_selector (tag_name),
attribute: SyntaxHighlightStyle
// css: class_name, property_name, namespace_name...
property: SyntaxHighlightStyle
// true, false, null, nullptr
constant: SyntaxHighlightStyle
// css: @media, @import, @supports...
// js: declare, implements, interface, keyof, public...
keyword: SyntaxHighlightStyle
// note: js enum is currently defined as a keyword
enum: SyntaxHighlightStyle
// -, --, ->, !=, &&, ||, <=...
operator: SyntaxHighlightStyle
number: SyntaxHighlightStyle
boolean: SyntaxHighlightStyle
// elixir: __MODULE__, __DIR__, __ENV__, etc
// go: nil, iota
"constant.builtin"?: SyntaxHighlightStyle
// == Functions ====== /
function: SyntaxHighlightStyle
// lua: assert, error, loadfile, tostring, unpack...
"function.builtin"?: SyntaxHighlightStyle
// go: call_expression, method_declaration
// js: call_expression, method_definition, pair (key, arrow function)
// rust: function_item name: (identifier)
"function.definition"?: SyntaxHighlightStyle
// rust: macro_definition name: (identifier)
"function.special.definition"?: SyntaxHighlightStyle
"function.method"?: SyntaxHighlightStyle
// ruby: identifier/"defined?" // Nate note: I don't fully understand this one.
"function.method.builtin"?: SyntaxHighlightStyle
// == Unsorted ====== /
// lua: hash_bang_line
preproc: SyntaxHighlightStyle
// elixir, python: interpolation (ex: foo in ${foo})
// js: template_substitution
embedded: SyntaxHighlightStyle
}
export type ThemeSyntax = Partial<Syntax>
const default_syntax_highlight_style: Omit<SyntaxHighlightStyle, "color"> = {
weight: "normal",
underline: false,
italic: false,
}
function build_default_syntax(): Syntax {
const theme = useTheme()
// Make a temporary object that is allowed to be missing
// the "color" property for each style
const syntax: {
[key: string]: Omit<SyntaxHighlightStyle, "color">
} = {}
// then spread the default to each style
for (const key of Object.keys({} as Syntax)) {
syntax[key as keyof Syntax] = {
...default_syntax_highlight_style,
}
}
// Mix the neutral and blue colors to get a
// predictive color distinct from any other color in the theme
const predictive = chroma
.mix(
theme.ramps.neutral(0.4).hex(),
theme.ramps.blue(0.4).hex(),
0.45,
"lch"
)
.hex()
// Mix the neutral and green colors to get a
// hint color distinct from any other color in the theme
const hint = chroma
.mix(
theme.ramps.neutral(0.6).hex(),
theme.ramps.blue(0.4).hex(),
0.45,
"lch"
)
.hex()
const completeSyntax: Syntax = {} as Syntax
const defaults: SyntaxHighlightStyle = {
color: ramps.neutral(1).hex(),
const color = {
primary: theme.ramps.neutral(1).hex(),
comment: theme.ramps.neutral(0.71).hex(),
punctuation: theme.ramps.neutral(0.86).hex(),
predictive: predictive,
hint: hint,
emphasis: theme.ramps.blue(0.5).hex(),
string: theme.ramps.orange(0.5).hex(),
function: theme.ramps.yellow(0.5).hex(),
type: theme.ramps.cyan(0.5).hex(),
constructor: theme.ramps.blue(0.5).hex(),
variant: theme.ramps.blue(0.5).hex(),
property: theme.ramps.blue(0.5).hex(),
enum: theme.ramps.orange(0.5).hex(),
operator: theme.ramps.orange(0.5).hex(),
number: theme.ramps.green(0.5).hex(),
boolean: theme.ramps.green(0.5).hex(),
constant: theme.ramps.green(0.5).hex(),
keyword: theme.ramps.blue(0.5).hex(),
}
for (const key of restKeys) {
{
completeSyntax[key] = {
...defaults,
}
}
// Then assign colors and use Syntax to enforce each style getting it's own color
const default_syntax: Syntax = {
...syntax,
comment: {
color: color.comment,
},
"comment.doc": {
color: color.comment,
},
primary: {
color: color.primary,
},
predictive: {
color: color.predictive,
italic: true,
},
hint: {
color: color.hint,
weight: font_weights.bold,
},
emphasis: {
color: color.emphasis,
},
"emphasis.strong": {
color: color.emphasis,
weight: font_weights.bold,
},
title: {
color: color.primary,
weight: font_weights.bold,
},
link_uri: {
color: theme.ramps.green(0.5).hex(),
underline: true,
},
link_text: {
color: theme.ramps.orange(0.5).hex(),
italic: true,
},
"text.literal": {
color: color.string,
},
punctuation: {
color: color.punctuation,
},
"punctuation.bracket": {
color: color.punctuation,
},
"punctuation.delimiter": {
color: color.punctuation,
},
"punctuation.special": {
color: theme.ramps.neutral(0.86).hex(),
},
"punctuation.list_marker": {
color: color.punctuation,
},
string: {
color: color.string,
},
"string.special": {
color: color.string,
},
"string.special.symbol": {
color: color.string,
},
"string.escape": {
color: color.comment,
},
"string.regex": {
color: color.string,
},
constructor: {
color: theme.ramps.blue(0.5).hex(),
},
variant: {
color: theme.ramps.blue(0.5).hex(),
},
type: {
color: color.type,
},
variable: {
color: color.primary,
},
label: {
color: theme.ramps.blue(0.5).hex(),
},
tag: {
color: theme.ramps.blue(0.5).hex(),
},
attribute: {
color: theme.ramps.blue(0.5).hex(),
},
property: {
color: theme.ramps.blue(0.5).hex(),
},
constant: {
color: color.constant,
},
keyword: {
color: color.keyword,
},
enum: {
color: color.enum,
},
operator: {
color: color.operator,
},
number: {
color: color.number,
},
boolean: {
color: color.boolean,
},
function: {
color: color.function,
},
preproc: {
color: color.primary,
},
embedded: {
color: color.primary,
},
}
const mergedBaseSyntax = Object.assign(completeSyntax, syntax_highlights)
return mergedBaseSyntax
return default_syntax
}
// Merge the base syntax with the theme syntax overrides
// This is a deep merge, so any nested properties will be merged as well
// This allows for a theme to only override a single property of a syntax highlight style
const merge_syntax = (
baseSyntax: Syntax,
theme_syntax_overrides: ThemeConfigInputSyntax
): Syntax => {
return deepmerge<Syntax, ThemeConfigInputSyntax>(
baseSyntax,
theme_syntax_overrides,
export function build_syntax(): Syntax {
const theme = useTheme()
const default_syntax: Syntax = build_default_syntax()
if (!theme.syntax) {
return default_syntax
}
const syntax = deepmerge<Syntax, Partial<ThemeSyntax>>(
default_syntax,
theme.syntax,
{
arrayMerge: (destinationArray, sourceArray) => [
...destinationArray,
@ -47,49 +327,6 @@ const merge_syntax = (
],
}
)
}
/** Returns a complete Syntax object of the combined styles of a theme's syntax overrides and the default syntax styles */
export const syntaxStyle = (
ramps: RampSet,
theme_syntax_overrides: ThemeConfigInputSyntax
): Syntax => {
const syntax_highlights: Partial<Syntax> = {
comment: { color: ramps.neutral(0.71).hex() },
"comment.doc": { color: ramps.neutral(0.71).hex() },
primary: { color: ramps.neutral(1).hex() },
emphasis: { color: ramps.blue(0.5).hex() },
"emphasis.strong": {
color: ramps.blue(0.5).hex(),
weight: font_weights.bold,
},
link_uri: { color: ramps.green(0.5).hex(), underline: true },
link_text: { color: ramps.orange(0.5).hex(), italic: true },
"text.literal": { color: ramps.orange(0.5).hex() },
punctuation: { color: ramps.neutral(0.86).hex() },
"punctuation.bracket": { color: ramps.neutral(0.86).hex() },
"punctuation.special": { color: ramps.neutral(0.86).hex() },
"punctuation.delimiter": { color: ramps.neutral(0.86).hex() },
"punctuation.list_marker": { color: ramps.neutral(0.86).hex() },
string: { color: ramps.orange(0.5).hex() },
"string.special": { color: ramps.orange(0.5).hex() },
"string.special.symbol": { color: ramps.orange(0.5).hex() },
"string.escape": { color: ramps.neutral(0.71).hex() },
"string.regex": { color: ramps.orange(0.5).hex() },
"method.constructor": { color: ramps.blue(0.5).hex() },
type: { color: ramps.cyan(0.5).hex() },
label: { color: ramps.blue(0.5).hex() },
attribute: { color: ramps.blue(0.5).hex() },
property: { color: ramps.blue(0.5).hex() },
constant: { color: ramps.green(0.5).hex() },
keyword: { color: ramps.blue(0.5).hex() },
operator: { color: ramps.orange(0.5).hex() },
number: { color: ramps.green(0.5).hex() },
boolean: { color: ramps.green(0.5).hex() },
function: { color: ramps.yellow(0.5).hex() },
}
const baseSyntax = apply_defaults(ramps, syntax_highlights)
const mergedSyntax = merge_syntax(baseSyntax, theme_syntax_overrides)
return mergedSyntax
return syntax
}

View file

@ -1,5 +1,5 @@
import { Scale, Color } from "chroma-js"
import { SyntaxHighlightStyle, SyntaxProperty } from "../types/syntax"
import { Syntax } from "./syntax"
interface ThemeMeta {
/** The name of the theme */
@ -55,9 +55,7 @@ export type ThemeConfigInputColorsKeys = keyof ThemeConfigInputColors
* }
* ```
*/
export type ThemeConfigInputSyntax = Partial<
Record<SyntaxProperty, Partial<SyntaxHighlightStyle>>
>
export type ThemeConfigInputSyntax = Partial<Syntax>
interface ThemeConfigOverrides {
syntax: ThemeConfigInputSyntax

View file

@ -4,13 +4,17 @@ import {
SingleOtherToken,
TokenTypes,
} from "@tokens-studio/types"
import { Shadow } from "../create_theme"
import {
Shadow,
SyntaxHighlightStyle,
ThemeSyntax,
} from "../create_theme"
import { LayerToken, layer_token } from "./layer"
import { PlayersToken, players_token } from "./players"
import { color_token } from "./token"
import { Syntax } from "../syntax"
import editor from "../../style_tree/editor"
import { useTheme } from "../../../src/common"
import { Syntax, SyntaxHighlightStyle } from "../../types/syntax"
interface ThemeTokens {
name: SingleOtherToken
@ -47,7 +51,7 @@ const modal_shadow_token = (): SingleBoxShadowToken => {
return create_shadow_token(shadow, "modal_shadow")
}
type ThemeSyntaxColorTokens = Record<keyof Syntax, SingleColorToken>
type ThemeSyntaxColorTokens = Record<keyof ThemeSyntax, SingleColorToken>
function syntax_highlight_style_color_tokens(
syntax: Syntax

View file

@ -1,8 +1,4 @@
import {
ThemeLicenseType,
ThemeFamilyMeta,
ThemeConfigInputSyntax,
} from "../../common"
import { ThemeLicenseType, ThemeSyntax, ThemeFamilyMeta } from "../../common"
export interface Variant {
colors: {
@ -33,7 +29,7 @@ export const meta: ThemeFamilyMeta = {
"https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/",
}
export const build_syntax = (variant: Variant): ThemeConfigInputSyntax => {
export const build_syntax = (variant: Variant): ThemeSyntax => {
const { colors } = variant
return {
primary: { color: colors.base06 },
@ -54,6 +50,7 @@ export const build_syntax = (variant: Variant): ThemeConfigInputSyntax => {
property: { color: colors.base08 },
variable: { color: colors.base06 },
"variable.special": { color: colors.base0E },
variant: { color: colors.base0A },
keyword: { color: colors.base0E },
}
}

View file

@ -3,8 +3,8 @@ import {
chroma,
color_ramp,
ThemeLicenseType,
ThemeSyntax,
ThemeFamilyMeta,
ThemeConfigInputSyntax,
} from "../../common"
export const ayu = {
@ -27,7 +27,7 @@ export const build_theme = (t: typeof dark, light: boolean) => {
purple: t.syntax.constant.hex(),
}
const syntax: ThemeConfigInputSyntax = {
const syntax: ThemeSyntax = {
constant: { color: t.syntax.constant.hex() },
"string.regex": { color: t.syntax.regexp.hex() },
string: { color: t.syntax.string.hex() },
@ -61,7 +61,7 @@ export const build_theme = (t: typeof dark, light: boolean) => {
}
}
export const build_syntax = (t: typeof dark): ThemeConfigInputSyntax => {
export const build_syntax = (t: typeof dark): ThemeSyntax => {
return {
constant: { color: t.syntax.constant.hex() },
"string.regex": { color: t.syntax.regexp.hex() },

View file

@ -4,8 +4,8 @@ import {
ThemeAppearance,
ThemeLicenseType,
ThemeConfig,
ThemeSyntax,
ThemeFamilyMeta,
ThemeConfigInputSyntax,
} from "../../common"
const meta: ThemeFamilyMeta = {
@ -214,7 +214,7 @@ const build_variant = (variant: Variant): ThemeConfig => {
magenta: color_ramp(chroma(variant.colors.gray)),
}
const syntax: ThemeConfigInputSyntax = {
const syntax: ThemeSyntax = {
primary: { color: neutral[is_light ? 0 : 8] },
"text.literal": { color: colors.blue },
comment: { color: colors.gray },
@ -229,7 +229,7 @@ const build_variant = (variant: Variant): ThemeConfig => {
"string.special.symbol": { color: colors.aqua },
"string.regex": { color: colors.orange },
type: { color: colors.yellow },
// enum: { color: colors.orange },
enum: { color: colors.orange },
tag: { color: colors.aqua },
constant: { color: colors.yellow },
keyword: { color: colors.red },

View file

@ -54,6 +54,7 @@ export const theme: ThemeConfig = {
syntax: {
boolean: { color: color.orange },
comment: { color: color.grey },
enum: { color: color.red },
"emphasis.strong": { color: color.orange },
function: { color: color.blue },
keyword: { color: color.purple },
@ -72,7 +73,8 @@ export const theme: ThemeConfig = {
"text.literal": { color: color.green },
type: { color: color.teal },
"variable.special": { color: color.orange },
"method.constructor": { color: color.blue },
variant: { color: color.blue },
constructor: { color: color.blue },
},
},
}

View file

@ -55,6 +55,7 @@ export const theme: ThemeConfig = {
syntax: {
boolean: { color: color.orange },
comment: { color: color.grey },
enum: { color: color.red },
"emphasis.strong": { color: color.orange },
function: { color: color.blue },
keyword: { color: color.purple },
@ -72,6 +73,7 @@ export const theme: ThemeConfig = {
"text.literal": { color: color.green },
type: { color: color.teal },
"variable.special": { color: color.orange },
variant: { color: color.blue },
},
},
}

View file

@ -1,4 +1,4 @@
import { ThemeConfigInputSyntax } from "../../common"
import { ThemeSyntax } from "../../common"
export const color = {
default: {
@ -54,7 +54,7 @@ export const color = {
},
}
export const syntax = (c: typeof color.default): ThemeConfigInputSyntax => {
export const syntax = (c: typeof color.default): Partial<ThemeSyntax> => {
return {
comment: { color: c.muted },
operator: { color: c.pine },

View file

@ -1,111 +0,0 @@
import fs from "fs"
import path from "path"
import readline from "readline"
function escapeTypeName(name: string): string {
return `'${name.replace("@", "").toLowerCase()}'`
}
const generatedNote = `// This file is generated by extract_syntax_types.ts
// Do not edit this file directly
// It is generated from the highlight.scm files in the zed crate
// To regenerate this file manually:
// 'npm run extract-syntax-types' from ./styles`
const defaultTextProperty = ` /** Default text color */
| 'primary'`
const main = async () => {
const pathFromRoot = "crates/zed/src/languages"
const directoryPath = path.join(__dirname, "../../../", pathFromRoot)
const stylesMap: Record<string, Set<string>> = {}
const propertyLanguageMap: Record<string, Set<string>> = {}
const processFile = async (filePath: string, language: string) => {
const fileStream = fs.createReadStream(filePath)
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
})
for await (const line of rl) {
const cleanedLine = line.replace(/"@[a-zA-Z0-9_.]*"/g, "")
const match = cleanedLine.match(/@(\w+\.*)*/g)
if (match) {
match.forEach((property) => {
const formattedProperty = escapeTypeName(property)
// Only add non-empty properties
if (formattedProperty !== "''") {
if (!propertyLanguageMap[formattedProperty]) {
propertyLanguageMap[formattedProperty] = new Set()
}
propertyLanguageMap[formattedProperty].add(language)
}
})
}
}
}
const directories = fs
.readdirSync(directoryPath, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)
for (const dir of directories) {
const highlightsFilePath = path.join(
directoryPath,
dir,
"highlights.scm"
)
if (fs.existsSync(highlightsFilePath)) {
await processFile(highlightsFilePath, dir)
}
}
for (const [language, properties] of Object.entries(stylesMap)) {
console.log(`${language}: ${Array.from(properties).join(", ")}`)
}
const sortedProperties = Object.entries(propertyLanguageMap).sort(
([propA], [propB]) => propA.localeCompare(propB)
)
const outStream = fs.createWriteStream(path.join(__dirname, "syntax.ts"))
let allProperties = ""
const syntaxKeys = []
for (const [property, languages] of sortedProperties) {
let languagesArray = Array.from(languages)
const moreThanSeven = languagesArray.length > 7
// Limit to the first 7 languages, append "..." if more than 7
languagesArray = languagesArray.slice(0, 7)
if (moreThanSeven) {
languagesArray.push("...")
}
const languagesString = languagesArray.join(", ")
const comment = `/** ${languagesString} */`
allProperties += ` ${comment}\n | ${property} \n`
syntaxKeys.push(property)
}
outStream.write(`${generatedNote}
export type SyntaxHighlightStyle = {
color: string,
fade_out?: number,
italic?: boolean,
underline?: boolean,
weight?: string,
}
export type Syntax = Record<SyntaxProperty, SyntaxHighlightStyle>
export type SyntaxOverride = Partial<Syntax>
export type SyntaxProperty = \n${defaultTextProperty}\n\n${allProperties}
export const allSyntaxKeys: SyntaxProperty[] = [\n ${syntaxKeys.join(
",\n "
)}\n]`)
outStream.end()
}
main().catch(console.error)

View file

@ -1,202 +0,0 @@
// This file is generated by extract_syntax_types.ts
// Do not edit this file directly
// It is generated from the highlight.scm files in the zed crate
// To regenerate this file manually:
// 'npm run extract-syntax-types' from ./styles
export type SyntaxHighlightStyle = {
color: string
fade_out?: number
italic?: boolean
underline?: boolean
weight?: string
}
export type Syntax = Record<SyntaxProperty, SyntaxHighlightStyle>
export type SyntaxOverride = Partial<Syntax>
export type SyntaxProperty =
/** Default text color */
| "primary"
/** elixir */
| "__attribute__"
/** elixir */
| "__name__"
/** elixir */
| "_sigil_name"
/** css, heex, lua */
| "attribute"
/** javascript, lua, tsx, typescript, yaml */
| "boolean"
/** elixir */
| "comment.doc"
/** elixir */
| "comment.unused"
/** bash, c, cpp, css, elixir, elm, erb, ... */
| "comment"
/** elixir, go, javascript, lua, php, python, racket, ... */
| "constant.builtin"
/** bash, c, cpp, elixir, elm, glsl, heex, ... */
| "constant"
/** glsl */
| "delimiter"
/** bash, elixir, javascript, python, ruby, tsx, typescript */
| "embedded"
/** markdown */
| "emphasis.strong"
/** markdown */
| "emphasis"
/** go, python, racket, ruby, scheme */
| "escape"
/** lua */
| "field"
/** lua, php, python */
| "function.builtin"
/** elm, lua, rust */
| "function.definition"
/** ruby */
| "function.method.builtin"
/** go, javascript, php, python, ruby, rust, tsx, ... */
| "function.method"
/** rust */
| "function.special.definition"
/** c, cpp, glsl, rust */
| "function.special"
/** bash, c, cpp, css, elixir, elm, glsl, ... */
| "function"
/** elm */
| "identifier"
/** glsl */
| "keyword.function"
/** bash, c, cpp, css, elixir, elm, erb, ... */
| "keyword"
/** c, cpp, glsl */
| "label"
/** markdown */
| "link_text"
/** markdown */
| "link_uri"
/** lua, php, tsx, typescript */
| "method.constructor"
/** lua */
| "method"
/** heex */
| "module"
/** svelte */
| "none"
/** bash, c, cpp, css, elixir, glsl, go, ... */
| "number"
/** bash, c, cpp, css, elixir, elm, glsl, ... */
| "operator"
/** lua */
| "parameter"
/** lua */
| "preproc"
/** bash, c, cpp, css, glsl, go, html, ... */
| "property"
/** c, cpp, elixir, elm, heex, html, javascript, ... */
| "punctuation.bracket"
/** c, cpp, css, elixir, elm, heex, javascript, ... */
| "punctuation.delimiter"
/** markdown */
| "punctuation.list_marker"
/** elixir, javascript, python, ruby, tsx, typescript, yaml */
| "punctuation.special"
/** elixir */
| "punctuation"
/** glsl */
| "storageclass"
/** elixir, elm, yaml */
| "string.escape"
/** elixir, javascript, racket, ruby, tsx, typescript */
| "string.regex"
/** elixir, ruby */
| "string.special.symbol"
/** css, elixir, toml */
| "string.special"
/** bash, c, cpp, css, elixir, elm, glsl, ... */
| "string"
/** svelte */
| "tag.delimiter"
/** css, heex, php, svelte */
| "tag"
/** markdown */
| "text.literal"
/** markdown */
| "title"
/** javascript, php, rust, tsx, typescript */
| "type.builtin"
/** glsl */
| "type.qualifier"
/** c, cpp, css, elixir, elm, glsl, go, ... */
| "type"
/** glsl, php */
| "variable.builtin"
/** cpp, css, javascript, lua, racket, ruby, rust, ... */
| "variable.special"
/** c, cpp, elm, glsl, go, javascript, lua, ... */
| "variable"
export const allSyntaxKeys: SyntaxProperty[] = [
"__attribute__",
"__name__",
"_sigil_name",
"attribute",
"boolean",
"comment.doc",
"comment.unused",
"comment",
"constant.builtin",
"constant",
"delimiter",
"embedded",
"emphasis.strong",
"emphasis",
"escape",
"field",
"function.builtin",
"function.definition",
"function.method.builtin",
"function.method",
"function.special.definition",
"function.special",
"function",
"identifier",
"keyword.function",
"keyword",
"label",
"link_text",
"link_uri",
"method.constructor",
"method",
"module",
"none",
"number",
"operator",
"parameter",
"preproc",
"property",
"punctuation.bracket",
"punctuation.delimiter",
"punctuation.list_marker",
"punctuation.special",
"punctuation",
"storageclass",
"string.escape",
"string.regex",
"string.special.symbol",
"string.special",
"string",
"tag.delimiter",
"tag",
"text.literal",
"title",
"type.builtin",
"type.qualifier",
"type",
"variable.builtin",
"variable.special",
"variable",
]