Compare commits

...

26 Commits

Author SHA1 Message Date
Piotr Osiewicz
61ba49bd12 Add a skip-ahead to the end-of-line. 2023-08-08 02:04:14 +02:00
Piotr Osiewicz
fa16895976 Do not query start of range if it's end is the same as the previous hunk's 2023-08-08 00:27:38 +02:00
Piotr Osiewicz
ca21626064 Baseline: Improve selection rendering for large quantities from 270ms to 90ms 2023-08-07 23:32:27 +02:00
Joseph T. Lyons
e3a4d174de Fix bash path_suffixes and add cmd-/ line comment support (#2827)
<img width="1608" alt="SCR-20230806-cyrg"
src="https://github.com/zed-industries/zed/assets/19867440/2491c4bc-5797-4417-9633-08c136b4e8fe">

I noticed we weren't highlghting bash files if the shebang line didn't
exist. After checking, it looks like the `.` were accidentally added to
the `path_suffixes` list. This PR fixes that and adds in support for
`cmd-/` to trigger line comments.

<img width="1608" alt="SCR-20230806-czxh"
src="https://github.com/zed-industries/zed/assets/19867440/37dd0c8e-c4e7-49e2-9997-9dd8145f460e">


Release Notes:

- Fixed a bug where shell files weren't syntax highlighted if a shebang
didn't exist.
- Added support for `cmd-/` to add line comments to shell files.
2023-08-06 02:35:00 -04:00
Joseph T. Lyons
ef5b982ea5 Fix bash path_suffixes and add line_comment 2023-08-06 02:20:31 -04:00
Joseph T. Lyons
7777d973cd Expand empty selections to cover full word when doing case conversions and fix bugs (#2826)
When doing case conversions, specifically in the case of an empty
selection, in both VS Code and Sublime, the cursor winds up being in a
different place relative to where it started.

In VS Code, the cursor maintains it position in the text, no matter if
the text expands or shrinks


https://github.com/zed-industries/zed/assets/19867440/b24f5d86-c315-4a72-9ed4-3732b490ea9a

In Sublime, I have no idea what is going on:


https://github.com/zed-industries/zed/assets/19867440/05f21303-6e42-47b2-b844-7accd0bf05d7

I thought it would be a better experience if, when doing an empty
selection transformation, we simply expand the selection and park the
cursor at the end of the newly-transformed text.


https://github.com/zed-industries/zed/assets/19867440/833619ef-04e2-47b6-ad4e-e2b43d54fb2b

This feels similar to us expanding the selection when doing line
manipulations:


https://github.com/zed-industries/zed/assets/19867440/c30c5332-787d-4cf0-a9ee-e66c3c159956

Selections are adjusted to match however each word expands and shrinks,
even when there are multiple:


https://github.com/zed-industries/zed/assets/19867440/d7073aac-8a59-4f2c-b0e5-1df37be1694c

Release Notes:

- Improved behavior of empty-selection case transformations by selecting
resulting word.
- Fixed some bugs with overflow
2023-08-05 12:16:46 -04:00
Joseph T. Lyons
1abb6a0176 Expand empty selections to cover full word and fix bugs 2023-08-05 11:31:21 -04:00
Joseph T. Lyons
d1048d03b0 Add more convert to case commands (#2825)
I'm using [convert_case](https://crates.io/crates/convert_case)
underneath the hood, which has over 35 million downloads and feels
solid.

Release Notes:

- Added commands to convert between variable name styles
([#1821](https://github.com/zed-industries/community/issues/1821)).
    - `convert to kebab case`
    - `convert to snake case`
    - `convert to upper camel case`
    - `convert to lower camel case`
    - `convert to title case`
2023-08-04 22:51:32 -04:00
Joseph T. Lyons
12e8f417e4 Add more convert to case commands
ConvertToTitleCase
ConvertToSnakeCase
ConvertToKebabCase
ConvertToUpperCamelCase
ConvertToLowerCamelCase
2023-08-04 22:45:26 -04:00
Joseph T. Lyons
5c2f38a0bc Add convert to {upper,lower} case commands (#2824)
Release Notes:

- Added `convert to upper case` and `convert to lower case` commands
([#1011](https://github.com/zed-industries/community/issues/1011)).
2023-08-04 18:12:55 -04:00
Joseph T. Lyons
8c98b02e45 Add convert to {upper,lower} case commands
Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com>
2023-08-04 15:10:33 -04:00
Nathan Sobo
d3c1966d96 WIP: Return WindowHandle<V: View> from AppContext::add_window (#2820)
Instead of returning a usize for the window id, I'm instead returning a
`WindowHandle<V: View>` where `V` is the type of the window's root view.
@as-cii helped me with a cool technique using generic associated types
where methods on `WindowHandle` can return either T or Option<T>
depending on the `BorrowWindowContext::Result` associated type.

Some example usage...

```rs
let window = cx.add_window(|cx| MyView::new(cx));
let my_view = window.root(cx); // If cx is TestAppContext, returns MyView. Otherwise returns Option<MyView>, because the window could be closed.
```


This isn't insanely beneficial on its own, but I think it will help
clean up our testing story. I'm planning on making `window` more useful
in tests for laying out elements, etc.

- [x] Rework tests that call `add_window` 😅 to expect only a window in
return.
- [x] Get tests passing
- [x] 🚬  test
2023-08-03 18:45:51 -06:00
Nathan Sobo
2d96388be3 Use WindowHandles in a couple places 2023-08-03 17:46:34 -06:00
Nathan Sobo
485c0a482e Don't refcount window handles 2023-08-03 17:11:47 -06:00
Nathan Sobo
afcc0d621b WIP 2023-08-03 17:03:39 -06:00
Joseph T. Lyons
ee1b4a52cc Add PathExt trait (#2823)
This PR adds a `PathExt` trait. It pulls in our existing `compact()`
function, as a method, and then adds a method, and testing, for
`icon_suffix()`. A test was added to fix:

- https://github.com/zed-industries/community/issues/1877

Release Notes:

- Fixed a bug where file icons would not be registered for files with
with `.` characters in their name
([#1877](https://github.com/zed-industries/community/issues/1877)).
2023-08-03 18:57:43 -04:00
Nathan Sobo
3c938a7377 WIP 2023-08-03 08:10:16 -06:00
Julia
ad4fd7619b Use the same font size for hovered state of LSP status (#2821)
This element is used for the update state as well for some reason so
while we don't normally ever see this state, it is used when the status
is acting as the restart to update button

Release Notes:

- Fixed an inconsistency in the status bar update button font size.
2023-08-02 18:16:39 -04:00
Julia
df4480ba52 Use the same font size for hovered state of LSP status
This element is used for the update state as well for some reason so
while we don't normally ever see this state, it is used when the status
is acting as the restart to update button
2023-08-02 17:33:56 -04:00
Nathan Sobo
8e36da1382 Get tests passing 2023-08-02 15:02:55 -06:00
Nathan Sobo
884cee6dfd Get tests compiling returning WindowHandle<V: View> from add_window 2023-08-02 14:05:03 -06:00
Max Brunsfeld
9e755bb855 Revert "Extract syntax highlighting properties from tree-sitter highlight queries (#2797)"
This reverts commit 45c635872b, reversing
changes made to f2b82369f2.
2023-08-02 12:15:39 -07:00
Nathan Sobo
60e190e500 WIP 2023-08-02 12:08:56 -06:00
Joseph T. Lyons
b0ec05a732 v0.99.x dev 2023-08-02 13:50:30 -04:00
Nathan Sobo
300ce61bd0 WIP 2023-08-02 08:25:40 -06:00
Nathan Sobo
b695c42e11 WIP: Return WindowHandle<V: View> from AppContext::add_window 2023-08-01 22:28:04 -06:00
92 changed files with 2122 additions and 1632 deletions

12
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,6 +28,7 @@ use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings};
use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
@@ -89,7 +90,7 @@ use std::{
cmp::{self, Ordering, Reverse},
mem,
num::NonZeroU32,
ops::{ControlFlow, Deref, DerefMut, Range},
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
path::Path,
sync::Arc,
time::{Duration, Instant},
@@ -231,6 +232,13 @@ actions!(
SortLinesCaseInsensitive,
ReverseLines,
ShuffleLines,
ConvertToUpperCase,
ConvertToLowerCase,
ConvertToTitleCase,
ConvertToSnakeCase,
ConvertToKebabCase,
ConvertToUpperCamelCase,
ConvertToLowerCamelCase,
Transpose,
Cut,
Copy,
@@ -353,6 +361,13 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::sort_lines_case_insensitive);
cx.add_action(Editor::reverse_lines);
cx.add_action(Editor::shuffle_lines);
cx.add_action(Editor::convert_to_upper_case);
cx.add_action(Editor::convert_to_lower_case);
cx.add_action(Editor::convert_to_title_case);
cx.add_action(Editor::convert_to_snake_case);
cx.add_action(Editor::convert_to_kebab_case);
cx.add_action(Editor::convert_to_upper_camel_case);
cx.add_action(Editor::convert_to_lower_camel_case);
cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end);
@@ -4306,6 +4321,97 @@ impl Editor {
});
}
pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_uppercase())
}
pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_lowercase())
}
pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Title))
}
pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Snake))
}
pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
}
pub fn convert_to_upper_camel_case(
&mut self,
_: &ConvertToUpperCamelCase,
cx: &mut ViewContext<Self>,
) {
self.manipulate_text(cx, |text| text.to_case(Case::UpperCamel))
}
pub fn convert_to_lower_camel_case(
&mut self,
_: &ConvertToLowerCamelCase,
cx: &mut ViewContext<Self>,
) {
self.manipulate_text(cx, |text| text.to_case(Case::Camel))
}
fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
where
Fn: FnMut(&str) -> String,
{
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx).snapshot(cx);
let mut new_selections = Vec::new();
let mut edits = Vec::new();
let mut selection_adjustment = 0i32;
for selection in self.selections.all::<usize>(cx) {
let selection_is_empty = selection.is_empty();
let (start, end) = if selection_is_empty {
let word_range = movement::surrounding_word(
&display_map,
selection.start.to_display_point(&display_map),
);
let start = word_range.start.to_offset(&display_map, Bias::Left);
let end = word_range.end.to_offset(&display_map, Bias::Left);
(start, end)
} else {
(selection.start, selection.end)
};
let text = buffer.text_for_range(start..end).collect::<String>();
let old_length = text.len() as i32;
let text = callback(&text);
new_selections.push(Selection {
start: (start as i32 - selection_adjustment) as usize,
end: ((start + text.len()) as i32 - selection_adjustment) as usize,
goal: SelectionGoal::None,
..selection
});
selection_adjustment += old_length - text.len() as i32;
edits.push((start..end, text));
}
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(new_selections);
});
this.request_autoscroll(Autoscroll::fit(), cx);
});
}
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
@@ -7443,6 +7549,88 @@ impl Editor {
results
}
pub fn selected_rows<T: 'static>(
&self,
search_range: Range<Anchor>,
display_snapshot: &DisplaySnapshot,
theme: &Theme,
) -> Vec<RangeInclusive<u32>> {
let mut results = Vec::new();
let buffer = &display_snapshot.buffer_snapshot;
let Some((_, ranges)) = self.background_highlights
.get(&TypeId::of::<T>()) else {
return vec![];
};
let start_ix = match ranges.binary_search_by(|probe| {
let cmp = probe.end.cmp(&search_range.start, buffer);
if cmp.is_gt() {
Ordering::Greater
} else {
Ordering::Less
}
}) {
Ok(i) | Err(i) => i,
};
let mut push_region = |start, end| {
if let (Some(start_display), Some(end_display)) = (start, end) {
results.push(start_display..=end_display);
}
};
let mut start_row = None;
let mut end_row = None;
let ranges = &ranges[start_ix..];
let mut it: usize = 0;
let find_next_row_candidate = |point: &Point, input_it| {
let it = input_it + 1;
let line_length = buffer.line_len(point.row);
if point.column == line_length {
return it;
}
let slice_len = ((line_length - point.column) as usize).min(ranges.len() - it);
let partition_point = ranges[it..it + slice_len]
.partition_point(|p| p.end.to_point(buffer).row <= point.row);
it + partition_point
};
while it < ranges.len() {
let range = &ranges[it];
if range.start.cmp(&search_range.end, buffer).is_ge() {
break;
}
let end = range.end.to_point(buffer);
let start = range.start.to_point(buffer).row;
if start_row.is_none() {
assert_eq!(end_row, None);
it = find_next_row_candidate(&end, it);
start_row = Some(start);
end_row = Some(end.row);
continue;
}
if let Some(current_end) = end_row.as_mut() {
it = find_next_row_candidate(&end, it);
if start > *current_end + 1 {
push_region(start_row, end_row);
start_row = Some(start);
end_row = Some(end.row);
} else {
// Merge two hunks.
*current_end = end.row;
}
} else {
unreachable!();
}
}
// We might still have a hunk that was not rendered (if there was a search hit on the last line)
push_region(start_row, end_row);
results
}
pub fn highlight_text<T: 'static>(
&mut self,
ranges: Vec<Range<Anchor>>,

File diff suppressed because it is too large Load Diff

View File

@@ -1107,8 +1107,6 @@ impl EditorElement {
if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min();
let end_anchor = Anchor::max();
let mut start_row = None;
let mut end_row = None;
let color = scrollbar_theme.selections;
let border = Border {
width: 1.,
@@ -1119,54 +1117,35 @@ impl EditorElement {
bottom: false,
left: true,
};
let mut push_region = |start, end| {
if let (Some(start_display), Some(end_display)) = (start, end) {
let start_y = y_for_row(start_display as f32);
let mut end_y = y_for_row(end_display as f32);
if end_y - start_y < 1. {
end_y = start_y + 1.;
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
let mut push_region = |start: u32, end: u32| {
let start_y = y_for_row(start as f32);
let mut end_y = y_for_row(end as f32);
if end_y - start_y < 1. {
end_y = start_y + 1.;
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
};
for (row, _) in &editor
.background_highlights_in_range_for::<crate::items::BufferSearchHighlights>(
let start = std::time::Instant::now();
let background_ranges = editor
.selected_rows::<crate::items::BufferSearchHighlights>(
start_anchor..end_anchor,
&layout.position_map.snapshot,
&theme,
)
{
let start_display = row.start;
let end_display = row.end;
if start_row.is_none() {
assert_eq!(end_row, None);
start_row = Some(start_display.row());
end_row = Some(end_display.row());
continue;
}
if let Some(current_end) = end_row.as_mut() {
if start_display.row() > *current_end + 1 {
push_region(start_row, end_row);
start_row = Some(start_display.row());
end_row = Some(end_display.row());
} else {
// Merge two hunks.
*current_end = end_display.row();
}
} else {
unreachable!();
}
);
dbg!(start.elapsed().as_millis());
for row in background_ranges {
let start = row.start();
let end = row.end();
push_region(*start, *end);
}
// We might still have a hunk that was not rendered (if there was a search hit on the last line)
push_region(start_row, end_row);
}
if layout.is_singleton && scrollbar_settings.git_diff {
@@ -3002,10 +2981,12 @@ mod tests {
fn test_layout_line_numbers(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let layouts = editor.update(cx, |editor, cx| {
@@ -3021,10 +3002,12 @@ mod tests {
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
editor.update(cx, |editor, cx| {
editor.set_placeholder_text("hello", cx);
@@ -3231,10 +3214,12 @@ mod tests {
info!(
"Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
);
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&input_text, cx);
Editor::new(editor_mode, buffer, None, None, cx)
});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&input_text, cx);
Editor::new(editor_mode, buffer, None, None, cx)
})
.root(cx);
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, layout_state) = editor.update(cx, |editor, cx| {

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -1780,7 +1780,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx),
@@ -1868,7 +1870,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@@ -2219,7 +2223,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@@ -2319,7 +2325,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {
@@ -2392,7 +2400,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
toggle_expand_dir(&panel, "src/test", cx);
@@ -2481,7 +2491,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "src/", cx);
@@ -2627,7 +2639,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
let new_search_events_count = Arc::new(AtomicUsize::new(0));
@@ -2714,7 +2728,9 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -793,67 +793,59 @@ impl Workspace {
DB.next_id().await.unwrap_or(0)
};
let workspace = requesting_window_id
.and_then(|window_id| {
cx.update(|cx| {
cx.replace_root_view(window_id, |cx| {
Workspace::new(
workspace_id,
project_handle.clone(),
app_state.clone(),
cx,
)
})
let window = requesting_window_id.and_then(|window_id| {
cx.update(|cx| {
cx.replace_root_view(window_id, |cx| {
Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
})
})
.unwrap_or_else(|| {
let window_bounds_override = window_bounds_env_override(&cx);
let (bounds, display) = if let Some(bounds) = window_bounds_override {
(Some(bounds), None)
} else {
serialized_workspace
.as_ref()
.and_then(|serialized_workspace| {
let display = serialized_workspace.display?;
let mut bounds = serialized_workspace.bounds?;
});
let window = window.unwrap_or_else(|| {
let window_bounds_override = window_bounds_env_override(&cx);
let (bounds, display) = if let Some(bounds) = window_bounds_override {
(Some(bounds), None)
} else {
serialized_workspace
.as_ref()
.and_then(|serialized_workspace| {
let display = serialized_workspace.display?;
let mut bounds = serialized_workspace.bounds?;
// Stored bounds are relative to the containing display.
// So convert back to global coordinates if that screen still exists
if let WindowBounds::Fixed(mut window_bounds) = bounds {
if let Some(screen) = cx.platform().screen_by_id(display) {
let screen_bounds = screen.bounds();
window_bounds.set_origin_x(
window_bounds.origin_x() + screen_bounds.origin_x(),
);
window_bounds.set_origin_y(
window_bounds.origin_y() + screen_bounds.origin_y(),
);
bounds = WindowBounds::Fixed(window_bounds);
} else {
// Screen no longer exists. Return none here.
return None;
}
// Stored bounds are relative to the containing display.
// So convert back to global coordinates if that screen still exists
if let WindowBounds::Fixed(mut window_bounds) = bounds {
if let Some(screen) = cx.platform().screen_by_id(display) {
let screen_bounds = screen.bounds();
window_bounds.set_origin_x(
window_bounds.origin_x() + screen_bounds.origin_x(),
);
window_bounds.set_origin_y(
window_bounds.origin_y() + screen_bounds.origin_y(),
);
bounds = WindowBounds::Fixed(window_bounds);
} else {
// Screen no longer exists. Return none here.
return None;
}
}
Some((bounds, display))
})
.unzip()
};
Some((bounds, display))
})
.unzip()
};
// Use the serialized workspace to construct the new window
cx.add_window(
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
|cx| {
Workspace::new(
workspace_id,
project_handle.clone(),
app_state.clone(),
cx,
)
},
)
.1
});
// Use the serialized workspace to construct the new window
cx.add_window(
(app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
|cx| {
Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
},
)
});
// We haven't yielded the main thread since obtaining the window handle,
// so the window exists.
let workspace = window.root(&cx).unwrap();
(app_state.initialize_workspace)(
workspace.downgrade(),
@@ -864,7 +856,7 @@ impl Workspace {
.await
.log_err();
cx.update_window(workspace.window_id(), |cx| cx.activate_window());
window.update(&mut cx, |cx| cx.activate_window());
let workspace = workspace.downgrade();
notify_if_database_failed(&workspace, &mut cx);
@@ -3977,7 +3969,7 @@ pub fn join_remote_project(
.await?;
let window_bounds_override = window_bounds_env_override(&cx);
let (_, workspace) = cx.add_window(
let window = cx.add_window(
(app_state.build_window_options)(
window_bounds_override,
None,
@@ -3985,6 +3977,7 @@ pub fn join_remote_project(
),
|cx| Workspace::new(0, project, app_state.clone(), cx),
);
let workspace = window.root(&cx).unwrap();
(app_state.initialize_workspace)(
workspace.downgrade(),
false,
@@ -4113,10 +4106,11 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
// Adding an item with no ambiguity renders the tab without detail.
let item1 = cx.add_view(window_id, |_| {
let item1 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
item
@@ -4128,7 +4122,7 @@ mod tests {
// Adding an item that creates ambiguity increases the level of detail on
// both tabs.
let item2 = cx.add_view(window_id, |_| {
let item2 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
@@ -4142,7 +4136,7 @@ mod tests {
// Adding an item that creates ambiguity increases the level of detail only
// on the ambiguous tabs. In this case, the ambiguity can't be resolved so
// we stop at the highest detail available.
let item3 = cx.add_view(window_id, |_| {
let item3 = window.add_view(cx, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
@@ -4177,16 +4171,17 @@ mod tests {
.await;
let project = Project::test(fs, ["root1".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let worktree_id = project.read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
let item1 = cx.add_view(window_id, |cx| {
let item1 = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
});
let item2 = cx.add_view(window_id, |cx| {
let item2 = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
});
@@ -4201,14 +4196,14 @@ mod tests {
);
});
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root1")
);
// Add a second item to a non-empty pane
workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("two.txt — root1")
);
project.read_with(cx, |project, cx| {
@@ -4227,7 +4222,7 @@ mod tests {
.await
.unwrap();
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root1")
);
project.read_with(cx, |project, cx| {
@@ -4247,14 +4242,14 @@ mod tests {
.await
.unwrap();
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root1, root2")
);
// Remove a project folder
project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
assert_eq!(
cx.current_window_title(window_id).as_deref(),
cx.current_window_title(window.window_id()).as_deref(),
Some("one.txt — root2")
);
}
@@ -4267,18 +4262,19 @@ mod tests {
fs.insert_tree("/root", json!({ "one": "" })).await;
let project = Project::test(fs, ["root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
// When there are no dirty items, there's nothing to do.
let item1 = cx.add_view(window_id, |_| TestItem::new());
let item1 = window.add_view(cx, |_| TestItem::new());
workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
assert!(task.await.unwrap());
// When there are dirty untitled items, prompt to save each one. If the user
// cancels any prompt, then abort.
let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
let item3 = cx.add_view(window_id, |cx| {
let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
let item3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@@ -4289,9 +4285,9 @@ mod tests {
});
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
cx.foreground().run_until_parked();
cx.simulate_prompt_answer(window_id, 2 /* cancel */);
cx.simulate_prompt_answer(window.window_id(), 2 /* cancel */);
cx.foreground().run_until_parked();
assert!(!cx.has_pending_prompt(window_id));
assert!(!cx.has_pending_prompt(window.window_id()));
assert!(!task.await.unwrap());
}
@@ -4302,26 +4298,27 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let item1 = cx.add_view(window_id, |cx| {
let item1 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let item2 = cx.add_view(window_id, |cx| {
let item2 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_conflict(true)
.with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
});
let item3 = cx.add_view(window_id, |cx| {
let item3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_conflict(true)
.with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
});
let item4 = cx.add_view(window_id, |cx| {
let item4 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new_untitled(cx)])
@@ -4349,10 +4346,10 @@ mod tests {
assert_eq!(pane.items_len(), 4);
assert_eq!(pane.active_item().unwrap().id(), item1.id());
});
assert!(cx.has_pending_prompt(window_id));
assert!(cx.has_pending_prompt(window.window_id()));
// Confirm saving item 1.
cx.simulate_prompt_answer(window_id, 0);
cx.simulate_prompt_answer(window.window_id(), 0);
cx.foreground().run_until_parked();
// Item 1 is saved. There's a prompt to save item 3.
@@ -4363,10 +4360,10 @@ mod tests {
assert_eq!(pane.items_len(), 3);
assert_eq!(pane.active_item().unwrap().id(), item3.id());
});
assert!(cx.has_pending_prompt(window_id));
assert!(cx.has_pending_prompt(window.window_id()));
// Cancel saving item 3.
cx.simulate_prompt_answer(window_id, 1);
cx.simulate_prompt_answer(window.window_id(), 1);
cx.foreground().run_until_parked();
// Item 3 is reloaded. There's a prompt to save item 4.
@@ -4377,10 +4374,10 @@ mod tests {
assert_eq!(pane.items_len(), 2);
assert_eq!(pane.active_item().unwrap().id(), item4.id());
});
assert!(cx.has_pending_prompt(window_id));
assert!(cx.has_pending_prompt(window.window_id()));
// Confirm saving item 4.
cx.simulate_prompt_answer(window_id, 0);
cx.simulate_prompt_answer(window.window_id(), 0);
cx.foreground().run_until_parked();
// There's a prompt for a path for item 4.
@@ -4404,13 +4401,14 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
// Create several workspace items with single project entries, and two
// workspace items with multiple project entries.
let single_entry_items = (0..=4)
.map(|project_entry_id| {
cx.add_view(window_id, |cx| {
window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_project_items(&[TestProjectItem::new(
@@ -4421,7 +4419,7 @@ mod tests {
})
})
.collect::<Vec<_>>();
let item_2_3 = cx.add_view(window_id, |cx| {
let item_2_3 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_singleton(false)
@@ -4430,7 +4428,7 @@ mod tests {
single_entry_items[3].read(cx).project_items[0].clone(),
])
});
let item_3_4 = cx.add_view(window_id, |cx| {
let item_3_4 = window.add_view(cx, |cx| {
TestItem::new()
.with_dirty(true)
.with_singleton(false)
@@ -4482,7 +4480,7 @@ mod tests {
&[ProjectEntryId::from_proto(0)]
);
});
cx.simulate_prompt_answer(window_id, 0);
cx.simulate_prompt_answer(window.window_id(), 0);
cx.foreground().run_until_parked();
left_pane.read_with(cx, |pane, cx| {
@@ -4491,7 +4489,7 @@ mod tests {
&[ProjectEntryId::from_proto(2)]
);
});
cx.simulate_prompt_answer(window_id, 0);
cx.simulate_prompt_answer(window.window_id(), 0);
cx.foreground().run_until_parked();
close.await.unwrap();
@@ -4507,10 +4505,11 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let item = cx.add_view(window_id, |cx| {
let item = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let item_id = item.id();
@@ -4550,7 +4549,7 @@ mod tests {
item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
// Deactivating the window still saves the file.
cx.simulate_window_activation(Some(window_id));
cx.simulate_window_activation(Some(window.window_id()));
item.update(cx, |item, cx| {
cx.focus_self();
item.is_dirty = true;
@@ -4592,7 +4591,7 @@ mod tests {
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
.await
.unwrap();
assert!(!cx.has_pending_prompt(window_id));
assert!(!cx.has_pending_prompt(window.window_id()));
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
// Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
@@ -4613,7 +4612,7 @@ mod tests {
let _close_items =
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
deterministic.run_until_parked();
assert!(cx.has_pending_prompt(window_id));
assert!(cx.has_pending_prompt(window.window_id()));
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
}
@@ -4624,9 +4623,10 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let item = cx.add_view(window_id, |cx| {
let item = window.add_view(cx, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
});
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
@@ -4677,7 +4677,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let panel = workspace.update(cx, |workspace, cx| {
let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
@@ -4824,7 +4825,8 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
// Add panel_1 on the left, panel_2 on the right.
@@ -4979,7 +4981,7 @@ mod tests {
// If focus is transferred to another view that's not a panel or another pane, we still show
// the panel as zoomed.
let focus_receiver = cx.add_view(window_id, |_| EmptyView);
let focus_receiver = window.add_view(cx, |_| EmptyView);
focus_receiver.update(cx, |_, cx| cx.focus_self());
workspace.read_with(cx, |workspace, _| {
assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -11,4 +11,4 @@
(begin "begin" @open "end" @close)
(module "module" @open "end" @close)
(_ . "def" @open "end" @close)
(_ . "class" @open "end" @close)
(_ . "class" @open "end" @close)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -982,7 +982,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@@ -1294,7 +1296,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
// Open a file within an existing worktree.
workspace
@@ -1335,7 +1339,9 @@ mod tests {
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
// Create a new untitled buffer
@@ -1428,7 +1434,9 @@ mod tests {
let project = Project::test(app_state.fs.clone(), [], cx).await;
project.update(cx, |project, _| project.languages().add(rust_lang()));
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
// Create a new untitled buffer
cx.dispatch_action(window_id, NewFile);
@@ -1479,7 +1487,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let window_id = window.window_id();
let entries = cx.read(|cx| workspace.file_project_paths(cx));
let file1 = entries[0].clone();
@@ -1553,7 +1563,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let entries = cx.read(|cx| workspace.file_project_paths(cx));
@@ -1830,7 +1842,9 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let entries = cx.read(|cx| workspace.file_project_paths(cx));
@@ -2072,7 +2086,8 @@ mod tests {
cx.foreground().run_until_parked();
let (window_id, _view) = cx.add_window(|_| TestView);
let window = cx.add_window(|_| TestView);
let window_id = window.window_id();
// Test loading the keymap base at all
assert_key_bindings_for(
@@ -2242,7 +2257,8 @@ mod tests {
cx.foreground().run_until_parked();
let (window_id, _view) = cx.add_window(|_| TestView);
let window = cx.add_window(|_| TestView);
let window_id = window.window_id();
// Test loading the keymap base at all
assert_key_bindings_for(

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,47 +12,44 @@ type TabBarButtonProps = TabBarButtonOptions & {
state?: Partial<Record<InteractiveState, Partial<TabBarButtonOptions>>>
}
export function tab_bar_button(
theme: Theme,
{ icon, color = "base" }: TabBarButtonProps
) {
export function tab_bar_button(theme: Theme, { icon, color = "base" }: TabBarButtonProps) {
const button_spacing = 8
return interactive({
base: {
icon: {
color: foreground(theme.middle, color),
asset: icon,
dimensions: {
width: 15,
height: 15,
return (
interactive({
base: {
icon: {
color: foreground(theme.middle, color),
asset: icon,
dimensions: {
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: {
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: {
container: {
background: background(theme.middle, color, "pressed"),
state: {
hovered: {
container: {
background: background(theme.middle, color, "hovered"),
}
},
clicked: {
container: {
background: background(theme.middle, color, "pressed"),
}
},
},
},
})
})
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,5 +24,7 @@
"useUnknownInCatchVariables": false,
"baseUrl": "."
},
"exclude": ["node_modules"]
"exclude": [
"node_modules"
]
}