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", "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]] [[package]]
name = "copilot" name = "copilot"
version = "0.1.0" version = "0.1.0"
@ -2316,6 +2325,7 @@ dependencies = [
"clock", "clock",
"collections", "collections",
"context_menu", "context_menu",
"convert_case",
"copilot", "copilot",
"ctor", "ctor",
"db", "db",
@ -9817,7 +9827,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.98.0" version = "0.99.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"ai", "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 // 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. // drop the workspace and we can use a ViewHandle instead.
let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None }); let window = cx.add_window(|_| WorkspaceContainer { workspace: None });
let workspace = cx.add_view(window_id, |cx| { let container = window.root(cx);
let workspace = window.add_view(cx, |cx| {
Workspace::new(0, project.clone(), self.app_state.clone(), cx) Workspace::new(0, project.clone(), self.app_state.clone(), cx)
}); });
container.update(cx, |container, cx| { container.update(cx, |container, cx| {

View file

@ -7,8 +7,7 @@ use client::{User, RECEIVE_TIMEOUT};
use collections::HashSet; use collections::HashSet;
use editor::{ use editor::{
test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion, test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions, ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToggleCodeActions, Undo,
Undo,
}; };
use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions}; use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions};
use futures::StreamExt as _; use futures::StreamExt as _;
@ -1208,7 +1207,7 @@ async fn test_share_project(
cx_c: &mut TestAppContext, cx_c: &mut TestAppContext,
) { ) {
deterministic.forbid_parking(); 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 mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await; let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await; let client_b = server.create_client(cx_b, "user_b").await;
@ -1316,7 +1315,7 @@ async fn test_share_project(
.await .await
.unwrap(); .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 // Client A sees client B's selection
deterministic.run_until_parked(); deterministic.run_until_parked();
@ -1499,8 +1498,9 @@ async fn test_host_disconnect(
deterministic.run_until_parked(); deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); 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)); 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 let editor_b = workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx) workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@ -1509,9 +1509,7 @@ async fn test_host_disconnect(
.unwrap() .unwrap()
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();
assert!(cx_b assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
.read_window(window_id_b, |cx| editor_b.is_focused(cx))
.unwrap());
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.is_window_edited(workspace_b.window_id())); 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())); 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. // 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_eq!(cx.focused_view_id(), None);
}); });
assert!(!cx_b.is_window_edited(workspace_b.window_id())); 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)) .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await .await
.unwrap(); .unwrap();
let (window_a, _) = cx_a.add_window(|_| EmptyView); let window_a = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| { let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
Editor::for_buffer(buffer_a, Some(project_a), cx)
});
let mut editor_cx_a = EditorTestContext { let mut editor_cx_a = EditorTestContext {
cx: cx_a, cx: cx_a,
window_id: window_a, window_id: window_a.window_id(),
editor: editor_a, 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)) .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await .await
.unwrap(); .unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView); let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| { let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
Editor::for_buffer(buffer_b, Some(project_b), cx)
});
let mut editor_cx_b = EditorTestContext { let mut editor_cx_b = EditorTestContext {
cx: cx_b, cx: cx_b,
window_id: window_b, window_id: window_b.window_id(),
editor: editor_b, 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)) .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await .await
.unwrap(); .unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView); let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| { let editor_b = window_b.add_view(cx_b, |cx| {
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), 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. // Join the project as client B.
let project_b = client_b.build_remote_project(project_id, cx_b).await; 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)); 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 let editor_b = workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx) workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@ -5537,8 +5532,9 @@ async fn test_collaborating_with_renames(
.unwrap(); .unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await; 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)); 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 let editor_b = workspace_b
.update(cx_b, |workspace, cx| { .update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx) workspace.open_path((worktree_id, "one.rs"), None, true, cx)
@ -5569,6 +5565,7 @@ async fn test_collaborating_with_renames(
.unwrap(); .unwrap();
prepare_rename.await.unwrap(); prepare_rename.await.unwrap();
editor_b.update(cx_b, |editor, cx| { editor_b.update(cx_b, |editor, cx| {
use editor::ToOffset;
let rename = editor.pending_rename().unwrap(); let rename = editor.pending_rename().unwrap();
let buffer = editor.buffer().read(cx).snapshot(cx); let buffer = editor.buffer().read(cx).snapshot(cx);
assert_eq!( 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)) .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await .await
.unwrap(); .unwrap();
let (window_a, _) = cx_a.add_window(|_| EmptyView); let window_a = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| { let editor_a = window_a.add_view(cx_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), 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)) .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await .await
.unwrap(); .unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView); let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| { let editor_b = window_b.add_view(cx_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), 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() { for screen in cx.platform().screens() {
let screen_bounds = screen.bounds(); let screen_bounds = screen.bounds();
let (window_id, _) = cx.add_window( let window = cx.add_window(
WindowOptions { WindowOptions {
bounds: WindowBounds::Fixed(RectF::new( bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right() 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()), |_| 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() { for screen in cx.platform().screens() {
let screen_bounds = screen.bounds(); let screen_bounds = screen.bounds();
let (window_id, _) = cx.add_window( let window = cx.add_window(
WindowOptions { WindowOptions {
bounds: WindowBounds::Fixed(RectF::new( bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING), 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 notification_windows
.entry(*project_id) .entry(*project_id)
.or_insert(Vec::new()) .or_insert(Vec::new())
.push(window_id); .push(window.window_id());
} }
} }
room::Event::RemoteProjectUnshared { project_id } => { room::Event::RemoteProjectUnshared { project_id } => {

View file

@ -295,7 +295,9 @@ mod tests {
let app_state = init_test(cx); let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await; 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 editor = cx.add_view(window_id, |cx| {
let mut editor = Editor::single_line(None, cx); let mut editor = Editor::single_line(None, cx);
editor.set_text("abc", cx); editor.set_text("abc", cx);

View file

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

View file

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

View file

@ -47,6 +47,7 @@ workspace = { path = "../workspace" }
aho-corasick = "0.7" aho-corasick = "0.7"
anyhow.workspace = true anyhow.workspace = true
convert_case = "0.6.0"
futures.workspace = true futures.workspace = true
indoc = "1.0.4" indoc = "1.0.4"
itertools = "0.10" itertools = "0.10"
@ -56,12 +57,12 @@ ordered-float.workspace = true
parking_lot.workspace = true parking_lot.workspace = true
postage.workspace = true postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false } pulldown-cmark = { version = "0.9.2", default-features = false }
rand.workspace = true
schemars.workspace = true schemars.workspace = true
serde.workspace = true serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true
smallvec.workspace = true smallvec.workspace = true
smol.workspace = true smol.workspace = true
rand.workspace = true
tree-sitter-rust = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-html = { 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 client::{ClickhouseEvent, TelemetrySettings};
use clock::{Global, ReplicaId}; use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use copilot::Copilot; use copilot::Copilot;
pub use display_map::DisplayPoint; pub use display_map::DisplayPoint;
use display_map::*; use display_map::*;
@ -231,6 +232,13 @@ actions!(
SortLinesCaseInsensitive, SortLinesCaseInsensitive,
ReverseLines, ReverseLines,
ShuffleLines, ShuffleLines,
ConvertToUpperCase,
ConvertToLowerCase,
ConvertToTitleCase,
ConvertToSnakeCase,
ConvertToKebabCase,
ConvertToUpperCamelCase,
ConvertToLowerCamelCase,
Transpose, Transpose,
Cut, Cut,
Copy, Copy,
@ -353,6 +361,13 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::sort_lines_case_insensitive); cx.add_action(Editor::sort_lines_case_insensitive);
cx.add_action(Editor::reverse_lines); cx.add_action(Editor::reverse_lines);
cx.add_action(Editor::shuffle_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_word_start);
cx.add_action(Editor::delete_to_previous_subword_start); cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end); 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>) { 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 display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot; let buffer = &display_map.buffer_snapshot;

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1135,7 +1135,9 @@ mod tests {
) )
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).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| { let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| { workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id() project.worktrees(cx).next().unwrap().read(cx).id()
@ -1835,7 +1837,9 @@ mod tests {
.await; .await;
let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language))); 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| { let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| { workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id() project.worktrees(cx).next().unwrap().read(cx).id()
@ -1988,7 +1992,9 @@ mod tests {
project.update(cx, |project, _| { project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language)) 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| { let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| { workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id() project.worktrees(cx).next().unwrap().read(cx).id()
@ -2074,8 +2080,9 @@ mod tests {
deterministic.run_until_parked(); deterministic.run_until_parked();
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
let (_, editor) = let editor = cx
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
.root(cx);
let editor_edited = Arc::new(AtomicBool::new(false)); let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap(); let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited); 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.update(cx, |project, _| {
project.languages().add(Arc::clone(&language)) 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| { let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| { workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id() 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(); deterministic.run_until_parked();
cx.foreground().run_until_parked(); cx.foreground().run_until_parked();
let (_, editor) = let editor = cx
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
.root(cx);
let editor_edited = Arc::new(AtomicBool::new(false)); let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap(); let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited); 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; let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language))); 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| { let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| { workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id() project.worktrees(cx).next().unwrap().read(cx).id()

View file

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

View file

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

View file

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

View file

@ -617,8 +617,9 @@ mod tests {
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).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));
cx.dispatch_action(window_id, Toggle); let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap()); let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder finder
@ -631,8 +632,8 @@ mod tests {
}); });
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext); cx.dispatch_action(window.window_id(), SelectNext);
cx.dispatch_action(window_id, Confirm); cx.dispatch_action(window.window_id(), Confirm);
active_pane active_pane
.condition(cx, |pane, _| pane.active_item().is_some()) .condition(cx, |pane, _| pane.active_item().is_some())
.await; .await;
@ -671,8 +672,9 @@ mod tests {
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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));
cx.dispatch_action(window_id, Toggle); let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap()); let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3]; 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()); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext); cx.dispatch_action(window.window_id(), SelectNext);
cx.dispatch_action(window_id, Confirm); cx.dispatch_action(window.window_id(), Confirm);
active_pane active_pane
.condition(cx, |pane, _| pane.active_item().is_some()) .condition(cx, |pane, _| pane.active_item().is_some())
.await; .await;
@ -754,8 +756,9 @@ mod tests {
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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));
cx.dispatch_action(window_id, Toggle); let workspace = window.root(cx);
cx.dispatch_action(window.window_id(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap()); let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3]; 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()); let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window_id, SelectNext); cx.dispatch_action(window.window_id(), SelectNext);
cx.dispatch_action(window_id, Confirm); cx.dispatch_action(window.window_id(), Confirm);
active_pane active_pane
.condition(cx, |pane, _| pane.active_item().is_some()) .condition(cx, |pane, _| pane.active_item().is_some())
.await; .await;
@ -837,19 +840,23 @@ mod tests {
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).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 workspace = cx
let (_, finder) = cx.add_window(|cx| { .add_window(|cx| Workspace::test_new(project, cx))
Picker::new( .root(cx);
FileFinderDelegate::new( let finder = cx
workspace.downgrade(), .add_window(|cx| {
workspace.read(cx).project().clone(), Picker::new(
None, FileFinderDelegate::new(
Vec::new(), workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx, cx,
), )
cx, })
) .root(cx);
});
let query = test_path_like("hi"); let query = test_path_like("hi");
finder finder
@ -931,19 +938,23 @@ mod tests {
cx, cx,
) )
.await; .await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = cx
let (_, finder) = cx.add_window(|cx| { .add_window(|cx| Workspace::test_new(project, cx))
Picker::new( .root(cx);
FileFinderDelegate::new( let finder = cx
workspace.downgrade(), .add_window(|cx| {
workspace.read(cx).project().clone(), Picker::new(
None, FileFinderDelegate::new(
Vec::new(), workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx, cx,
), )
cx, })
) .root(cx);
});
finder finder
.update(cx, |f, cx| { .update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("hi"), cx) f.delegate_mut().spawn_search(test_path_like("hi"), cx)
@ -967,19 +978,23 @@ mod tests {
cx, cx,
) )
.await; .await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); let workspace = cx
let (_, finder) = cx.add_window(|cx| { .add_window(|cx| Workspace::test_new(project, cx))
Picker::new( .root(cx);
FileFinderDelegate::new( let finder = cx
workspace.downgrade(), .add_window(|cx| {
workspace.read(cx).project().clone(), Picker::new(
None, FileFinderDelegate::new(
Vec::new(), workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx, cx,
), )
cx, })
) .root(cx);
});
// Even though there is only one worktree, that worktree's filename // Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file. // 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)); 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] #[gpui::test]
async fn test_path_distance_ordering(cx: &mut TestAppContext) { async fn test_path_distance_ordering(cx: &mut TestAppContext) {
let app_state = init_test(cx); let app_state = init_test(cx);
@ -1089,7 +1049,9 @@ mod tests {
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).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 worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1); assert_eq!(worktrees.len(), 1);
@ -1103,18 +1065,20 @@ mod tests {
worktree_id, worktree_id,
path: Arc::from(Path::new("/root/dir2/b.txt")), path: Arc::from(Path::new("/root/dir2/b.txt")),
})); }));
let (_, finder) = cx.add_window(|cx| { let finder = cx
Picker::new( .add_window(|cx| {
FileFinderDelegate::new( Picker::new(
workspace.downgrade(), FileFinderDelegate::new(
workspace.read(cx).project().clone(), workspace.downgrade(),
b_path, workspace.read(cx).project().clone(),
Vec::new(), b_path,
Vec::new(),
cx,
),
cx, cx,
), )
cx, })
) .root(cx);
});
finder finder
.update(cx, |f, cx| { .update(cx, |f, cx| {
@ -1151,19 +1115,23 @@ mod tests {
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).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
let (_, finder) = cx.add_window(|cx| { .add_window(|cx| Workspace::test_new(project, cx))
Picker::new( .root(cx);
FileFinderDelegate::new( let finder = cx
workspace.downgrade(), .add_window(|cx| {
workspace.read(cx).project().clone(), Picker::new(
None, FileFinderDelegate::new(
Vec::new(), workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx, cx,
), )
cx, })
) .root(cx);
});
finder finder
.update(cx, |f, cx| { .update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("dir"), cx) f.delegate_mut().spawn_search(test_path_like("dir"), cx)
@ -1198,7 +1166,9 @@ mod tests {
.await; .await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).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 worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1); assert_eq!(worktrees.len(), 1);
@ -1404,7 +1374,9 @@ mod tests {
.detach(); .detach();
deterministic.run_until_parked(); 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 worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>(); let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1,); assert_eq!(worktrees.len(), 1,);

File diff suppressed because it is too large Load diff

View file

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

View file

@ -15,7 +15,7 @@ use crate::{
util::post_inc, util::post_inc,
Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect, Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription, Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
View, ViewContext, ViewHandle, WindowInvalidation, View, ViewContext, ViewHandle, WindowHandle, WindowInvalidation,
}; };
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
@ -142,7 +142,9 @@ impl BorrowAppContext for WindowContext<'_> {
} }
impl BorrowWindowContext 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 { if self.window_id == window_id {
f(self) f(self)
} else { } 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 { if self.window_id == window_id {
f(self) f(self)
} else { } else {
@ -1151,15 +1157,15 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers) 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 where
V: View, V: View,
F: FnOnce(&mut ViewContext<V>) -> V, F: FnOnce(&mut ViewContext<V>) -> V,
{ {
let root_view = self.add_view(|cx| build_root_view(cx)); 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()); 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> 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>, F: FnOnce(&mut ViewContext<T>) -> Option<T>,
{ {
let window_id = self.window_id; 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 mut cx = ViewContext::mutable(self, view_id);
let handle = if let Some(view) = build_view(&mut cx) { let handle = if let Some(view) = build_view(&mut cx) {
let mut keymap_context = KeymapContext::default(); 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>() .receive_notification::<lsp::notification::DidOpenTextDocument>()
.await; .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 { language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
message: "hello from the server".into(), message: "hello from the server".into(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -30,49 +30,47 @@ pub mod legacy {
} }
} }
/// Compacts a given file path by replacing the user's home directory pub trait PathExt {
/// prefix with a tilde (`~`). fn compact(&self) -> PathBuf;
/// fn icon_suffix(&self) -> Option<&str>;
/// # Arguments }
///
/// * `path` - A reference to a `Path` representing the file path to compact. impl<T: AsRef<Path>> PathExt for T {
/// /// Compacts a given file path by replacing the user's home directory
/// # Examples /// prefix with a tilde (`~`).
/// ///
/// ``` /// # Returns
/// use std::path::{Path, PathBuf}; ///
/// use util::paths::compact; /// * A `PathBuf` containing the compacted file path. If the input path
/// let path: PathBuf = [ /// does not have the user's home directory prefix, or if we are not on
/// util::paths::HOME.to_string_lossy().to_string(), /// Linux or macOS, the original path is returned unchanged.
/// "some_file.txt".to_string(), fn compact(&self) -> PathBuf {
/// ] if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
/// .iter() match self.as_ref().strip_prefix(HOME.as_path()) {
/// .collect(); Ok(relative_path) => {
/// if cfg!(target_os = "linux") || cfg!(target_os = "macos") { let mut shortened_path = PathBuf::new();
/// assert_eq!(compact(&path).to_str(), Some("~/some_file.txt")); shortened_path.push("~");
/// } else { shortened_path.push(relative_path);
/// assert_eq!(compact(&path).to_str(), path.to_str()); shortened_path
/// } }
/// ``` Err(_) => self.as_ref().to_path_buf(),
///
/// # 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 {
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
match path.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(), } else {
self.as_ref().to_path_buf()
} }
} else { }
path.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 { fn is_zoomed(&self, _cx: &WindowContext) -> bool {
false false
} }
fn set_zoomed(&mut self, _zoomed: 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 set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {
}
fn should_activate_on_event(_: &Self::Event) -> bool { fn should_activate_on_event(_: &Self::Event) -> bool {
false false
} }

View file

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

View file

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

View file

@ -1,5 +1,6 @@
name = "Shell Script" 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" first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
brackets = [ brackets = [
{ start = "[", end = "]", close = true, newline = false }, { start = "[", end = "]", close = true, newline = false },

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@
operator: "@" operator: "@"
operand: (call operand: (call
target: (identifier) @unary target: (identifier) @unary
(.match? @unary "^(doc)$")) (#match? @unary "^(doc)$"))
) @context ) @context
. .
(call (call
@ -18,10 +18,10 @@
target: (identifier) @name) target: (identifier) @name)
operator: "when") 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 (call
target: (identifier) @name target: (identifier) @name
(arguments (alias) @name) (arguments (alias) @name)
(.match? @name "^(defmodule|defprotocol)$")) @item (#match? @name "^(defmodule|defprotocol)$")) @item

View file

@ -54,13 +54,13 @@
(sigil_name) @__name__ (sigil_name) @__name__
quoted_start: _ @string quoted_start: _ @string
quoted_end: _ @string quoted_end: _ @string
(.match? @__name__ "^[sS]$")) @string (#match? @__name__ "^[sS]$")) @string
(sigil (sigil
(sigil_name) @__name__ (sigil_name) @__name__
quoted_start: _ @string.regex quoted_start: _ @string.regex
quoted_end: _ @string.regex quoted_end: _ @string.regex
(.match? @__name__ "^[rR]$")) @string.regex (#match? @__name__ "^[rR]$")) @string.regex
(sigil (sigil
(sigil_name) @__name__ (sigil_name) @__name__
@ -69,7 +69,7 @@
( (
(identifier) @comment.unused (identifier) @comment.unused
(.match? @comment.unused "^_") (#match? @comment.unused "^_")
) )
(call (call
@ -91,7 +91,7 @@
operator: "|>" operator: "|>"
right: (identifier)) 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 (binary_operator
operator: "|>" operator: "|>"
@ -99,15 +99,15 @@
(call (call
target: (identifier) @keyword 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 (call
target: (identifier) @keyword 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 (identifier) @constant.builtin
(.match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$") (#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
) )
(unary_operator (unary_operator
@ -121,7 +121,7 @@
(sigil) (sigil)
(boolean) (boolean)
] @comment.doc)) ] @comment.doc))
(.match? @__attribute__ "^(moduledoc|typedoc|doc)$")) (#match? @__attribute__ "^(moduledoc|typedoc|doc)$"))
(comment) @comment (comment) @comment
@ -150,4 +150,4 @@
((sigil ((sigil
(sigil_name) @_sigil_name (sigil_name) @_sigil_name
(quoted_content) @embedded) (quoted_content) @embedded)
(.eq? @_sigil_name "H")) (#eq? @_sigil_name "H"))

View file

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

View file

@ -1,7 +1,7 @@
(call (call
target: (identifier) @context target: (identifier) @context
(arguments (alias) @name) (arguments (alias) @name)
(.match? @context "^(defmodule|defprotocol)$")) @item (#match? @context "^(defmodule|defprotocol)$")) @item
(call (call
target: (identifier) @context target: (identifier) @context
@ -23,4 +23,4 @@
")" @context.extra)) ")" @context.extra))
operator: "when") 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 ((glsl_content) @content
(.set! "language" "glsl")) (#set! "language" "glsl"))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,16 +18,16 @@
; Identifier naming conventions ; Identifier naming conventions
((identifier) @type ((identifier) @type
(.match? @type "^[A-Z]")) (#match? @type "^[A-Z]"))
((identifier) @constant ((identifier) @constant
(.match? @constant "^_*[A-Z][A-Z\\d_]*$")) (#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
; Builtin functions ; Builtin functions
((call ((call
function: (identifier) @function.builtin) function: (identifier) @function.builtin)
(.match? (#match?
@function.builtin @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__)$")) "^(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 (symbol) @name
(list . (symbol) @name) (list . (symbol) @name)
] ]
(.match? @start-symbol "^define") (#match? @start-symbol "^define")
) @item ) @item

View file

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

View file

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

View file

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

View file

@ -14,7 +14,7 @@
(directive)] @comment (directive)] @comment
((symbol) @operator ((symbol) @operator
(.match? @operator "^(\\+|-|\\*|/|=|>|<|>=|<=)$")) (#match? @operator "^(\\+|-|\\*|/|=|>|<|>=|<=)$"))
(list (list
. .
@ -23,6 +23,6 @@
(list (list
. .
(symbol) @keyword (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)$" "^(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 (symbol) @name
(list . (symbol) @name) (list . (symbol) @name)
] ]
(.match? @start-symbol "^define") (#match? @start-symbol "^define")
) @item ) @item

View file

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

View file

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

View file

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

View file

@ -8,7 +8,6 @@
"build-licenses": "ts-node ./src/build_licenses.ts", "build-licenses": "ts-node ./src/build_licenses.ts",
"build-tokens": "ts-node ./src/build_tokens.ts", "build-tokens": "ts-node ./src/build_tokens.ts",
"build-types": "ts-node ./src/build_types.ts", "build-types": "ts-node ./src/build_types.ts",
"generate-syntax": "ts-node ./src/types/extract_syntax_types.ts",
"test": "vitest" "test": "vitest"
}, },
"author": "Zed Industries (https://github.com/zed-industries/)", "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) { function write_themes(themes: Theme[], output_directory: string) {
clear_themes(output_directory) clear_themes(output_directory)
@ -32,7 +34,10 @@ function write_themes(themes: Theme[], output_directory: string) {
const style_tree = app() const style_tree = app()
const style_tree_json = JSON.stringify(style_tree, null, 2) const style_tree_json = JSON.stringify(style_tree, null, 2)
const temp_path = path.join(temp_directory, `${theme.name}.json`) 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.writeFileSync(temp_path, style_tree_json)
fs.renameSync(temp_path, out_path) fs.renameSync(temp_path, out_path)
console.log(`- ${out_path} created`) console.log(`- ${out_path} created`)

View file

@ -83,6 +83,8 @@ function write_tokens(themes: Theme[], tokens_directory: string) {
console.log(`- ${METADATA_FILE} created`) 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) write_tokens(all_themes, TOKENS_DIRECTORY)

View file

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

View file

@ -12,47 +12,44 @@ type TabBarButtonProps = TabBarButtonOptions & {
state?: Partial<Record<InteractiveState, Partial<TabBarButtonOptions>>> state?: Partial<Record<InteractiveState, Partial<TabBarButtonOptions>>>
} }
export function tab_bar_button( export function tab_bar_button(theme: Theme, { icon, color = "base" }: TabBarButtonProps) {
theme: Theme,
{ icon, color = "base" }: TabBarButtonProps
) {
const button_spacing = 8 const button_spacing = 8
return interactive({ return (
base: { interactive({
icon: { base: {
color: foreground(theme.middle, color), icon: {
asset: icon, color: foreground(theme.middle, color),
dimensions: { asset: icon,
width: 15, dimensions: {
height: 15, width: 15,
height: 15,
},
}, },
},
container: {
corner_radius: 4,
padding: {
top: 4,
bottom: 4,
left: 4,
right: 4,
},
margin: {
left: button_spacing / 2,
right: button_spacing / 2,
},
},
},
state: {
hovered: {
container: { container: {
background: background(theme.middle, color, "hovered"), corner_radius: 4,
padding: {
top: 4, bottom: 4, left: 4, right: 4
},
margin: {
left: button_spacing / 2,
right: button_spacing / 2,
},
}, },
}, },
clicked: { state: {
container: { hovered: {
background: background(theme.middle, color, "pressed"), 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" import { Margin } from "./icon_button"
interface TextButtonOptions { interface TextButtonOptions {
layer?: Theme["lowest"] | Theme["middle"] | Theme["highest"] layer?:
| Theme["lowest"]
| Theme["middle"]
| Theme["highest"]
color?: keyof Theme["lowest"] color?: keyof Theme["lowest"]
margin?: Partial<Margin> margin?: Partial<Margin>
text_properties?: TextProperties text_properties?: TextProperties

View file

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

View file

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

View file

@ -9,9 +9,9 @@ import {
} from "./components" } from "./components"
import hover_popover from "./hover_popover" import hover_popover from "./hover_popover"
import { build_syntax } from "../theme/syntax"
import { interactive, toggleable } from "../element" import { interactive, toggleable } from "../element"
import { useTheme } from "../theme" import { useTheme } from "../theme"
import chroma from "chroma-js"
export default function editor(): any { export default function editor(): any {
const theme = useTheme() const theme = useTheme()
@ -48,28 +48,16 @@ export default function editor(): any {
} }
} }
const syntax = build_syntax()
return { return {
text_color: theme.syntax.primary.color, text_color: syntax.primary.color,
background: background(layer), background: background(layer),
active_line_background: with_opacity(background(layer, "on"), 0.75), active_line_background: with_opacity(background(layer, "on"), 0.75),
highlighted_line_background: background(layer, "on"), highlighted_line_background: background(layer, "on"),
// Inline autocomplete suggestions, Co-pilot suggestions, etc. // Inline autocomplete suggestions, Co-pilot suggestions, etc.
hint: chroma hint: syntax.hint,
.mix( suggestion: syntax.predictive,
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(),
code_actions: { code_actions: {
indicator: toggleable({ indicator: toggleable({
base: interactive({ base: interactive({
@ -267,8 +255,8 @@ export default function editor(): any {
invalid_warning_diagnostic: diagnostic(theme.middle, "base"), invalid_warning_diagnostic: diagnostic(theme.middle, "base"),
hover_popover: hover_popover(), hover_popover: hover_popover(),
link_definition: { link_definition: {
color: theme.syntax.link_uri.color, color: syntax.link_uri.color,
underline: theme.syntax.link_uri.underline, underline: syntax.link_uri.underline,
}, },
jump_icon: interactive({ jump_icon: interactive({
base: { 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.5).hex(), 0.8)
: with_opacity(theme.ramps.green(0.4).hex(), 0.8), : with_opacity(theme.ramps.green(0.4).hex(), 0.8),
}, },
selections: foreground(layer, "accent"), selections: foreground(layer, "accent")
}, },
composition_mark: { composition_mark: {
underline: { underline: {
@ -326,6 +314,6 @@ export default function editor(): any {
color: border_color(layer), 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"), ...text(theme.highest, "mono", "on", "disabled"),
background: background(theme.highest, "on", "disabled"), background: background(theme.highest, "on", "disabled"),
border: border(theme.highest, "on", "disabled"), border: border(theme.highest, "on", "disabled"),
}, }
}, },
}), }),
button_margin: 8, button_margin: 8,

View file

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

View file

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

View file

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

View file

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

View file

@ -1,28 +1,28 @@
import { Scale, Color } from "chroma-js" import { Scale, Color } from "chroma-js"
import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax"
export { Syntax, ThemeSyntax, SyntaxHighlightStyle }
import { import {
ThemeConfig, ThemeConfig,
ThemeAppearance, ThemeAppearance,
ThemeConfigInputColors, ThemeConfigInputColors,
} from "./theme_config" } from "./theme_config"
import { get_ramps } from "./ramps" import { get_ramps } from "./ramps"
import { syntaxStyle } from "./syntax"
import { Syntax } from "../types/syntax"
export interface Theme { export interface Theme {
name: string name: string
is_light: boolean is_light: boolean
/** /**
* App background, other elements that should sit directly on top of the background. * App background, other elements that should sit directly on top of the background.
*/ */
lowest: Layer lowest: Layer
/** /**
* Panels, tabs, other UI surfaces that sit on top of the background. * Panels, tabs, other UI surfaces that sit on top of the background.
*/ */
middle: Layer middle: Layer
/** /**
* Editors like code buffers, conversation editors, etc. * Editors like code buffers, conversation editors, etc.
*/ */
highest: Layer highest: Layer
ramps: RampSet ramps: RampSet
@ -31,7 +31,7 @@ export interface Theme {
modal_shadow: Shadow modal_shadow: Shadow
players: Players players: Players
syntax: Syntax syntax?: Partial<ThemeSyntax>
} }
export interface Meta { export interface Meta {
@ -115,7 +115,12 @@ export interface Style {
} }
export function create_theme(theme: ThemeConfig): Theme { 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 is_light = appearance === ThemeAppearance.Light
const color_ramps: ThemeConfigInputColors = input_color const color_ramps: ThemeConfigInputColors = input_color
@ -157,11 +162,6 @@ export function create_theme(theme: ThemeConfig): Theme {
"7": player(ramps.yellow), "7": player(ramps.yellow),
} }
const syntax = syntaxStyle(
ramps,
theme.override.syntax ? theme.override.syntax : {}
)
return { return {
name, name,
is_light, is_light,

View file

@ -1,45 +1,325 @@
import deepmerge from "deepmerge" import deepmerge from "deepmerge"
import { font_weights, ThemeConfigInputSyntax, RampSet } from "../common" import { FontWeight, font_weights, useTheme } from "../common"
import { Syntax, SyntaxHighlightStyle, allSyntaxKeys } from "../types/syntax" import chroma from "chroma-js"
// Apply defaults to any missing syntax properties that are not defined manually export interface SyntaxHighlightStyle {
function apply_defaults( color?: string
ramps: RampSet, weight?: FontWeight
syntax_highlights: Partial<Syntax> underline?: boolean
): Syntax { italic?: boolean
const restKeys: (keyof Syntax)[] = allSyntaxKeys.filter( }
(key) => !syntax_highlights[key]
)
const completeSyntax: Syntax = {} as Syntax export interface Syntax {
// == Text Styles ====== /
comment: SyntaxHighlightStyle
// elixir: doc comment
"comment.doc": SyntaxHighlightStyle
primary: SyntaxHighlightStyle
predictive: SyntaxHighlightStyle
hint: SyntaxHighlightStyle
const defaults: SyntaxHighlightStyle = { // === Formatted Text ====== /
color: ramps.neutral(1).hex(), 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
for (const key of restKeys) { // == Punctuation ====== /
{ punctuation: SyntaxHighlightStyle
completeSyntax[key] = { /** Example: `(`, `[`, `{`...*/
...defaults, "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,
} }
} }
const mergedBaseSyntax = Object.assign(completeSyntax, syntax_highlights) // 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()
return mergedBaseSyntax 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(),
}
// 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,
},
}
return default_syntax
} }
// Merge the base syntax with the theme syntax overrides export function build_syntax(): Syntax {
// This is a deep merge, so any nested properties will be merged as well const theme = useTheme()
// This allows for a theme to only override a single property of a syntax highlight style
const merge_syntax = ( const default_syntax: Syntax = build_default_syntax()
baseSyntax: Syntax,
theme_syntax_overrides: ThemeConfigInputSyntax if (!theme.syntax) {
): Syntax => { return default_syntax
return deepmerge<Syntax, ThemeConfigInputSyntax>( }
baseSyntax,
theme_syntax_overrides, const syntax = deepmerge<Syntax, Partial<ThemeSyntax>>(
default_syntax,
theme.syntax,
{ {
arrayMerge: (destinationArray, sourceArray) => [ arrayMerge: (destinationArray, sourceArray) => [
...destinationArray, ...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 */ return syntax
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
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -54,6 +54,7 @@ export const theme: ThemeConfig = {
syntax: { syntax: {
boolean: { color: color.orange }, boolean: { color: color.orange },
comment: { color: color.grey }, comment: { color: color.grey },
enum: { color: color.red },
"emphasis.strong": { color: color.orange }, "emphasis.strong": { color: color.orange },
function: { color: color.blue }, function: { color: color.blue },
keyword: { color: color.purple }, keyword: { color: color.purple },
@ -72,7 +73,8 @@ export const theme: ThemeConfig = {
"text.literal": { color: color.green }, "text.literal": { color: color.green },
type: { color: color.teal }, type: { color: color.teal },
"variable.special": { color: color.orange }, "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: { syntax: {
boolean: { color: color.orange }, boolean: { color: color.orange },
comment: { color: color.grey }, comment: { color: color.grey },
enum: { color: color.red },
"emphasis.strong": { color: color.orange }, "emphasis.strong": { color: color.orange },
function: { color: color.blue }, function: { color: color.blue },
keyword: { color: color.purple }, keyword: { color: color.purple },
@ -72,6 +73,7 @@ export const theme: ThemeConfig = {
"text.literal": { color: color.green }, "text.literal": { color: color.green },
type: { color: color.teal }, type: { color: color.teal },
"variable.special": { color: color.orange }, "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 = { export const color = {
default: { 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 { return {
comment: { color: c.muted }, comment: { color: c.muted },
operator: { color: c.pine }, 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",
]