Compare commits
128 Commits
v0.88.4
...
v0.89.2-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c71e4b749 | ||
|
|
32ea881be7 | ||
|
|
030968ec51 | ||
|
|
0c7973d274 | ||
|
|
0071a4598a | ||
|
|
d7e4544638 | ||
|
|
432d407539 | ||
|
|
6f4fd74f43 | ||
|
|
bf2016adf5 | ||
|
|
27ef0e2b52 | ||
|
|
45b42c512d | ||
|
|
705e36827c | ||
|
|
b875d4ed40 | ||
|
|
ed0b9acb0a | ||
|
|
ef80b539d1 | ||
|
|
62660f2766 | ||
|
|
193474a346 | ||
|
|
c48fed26bd | ||
|
|
3f6aa94a5f | ||
|
|
893615236d | ||
|
|
1f72f9e18b | ||
|
|
a10933c063 | ||
|
|
ac41564a8f | ||
|
|
c6672dbac2 | ||
|
|
76927b6d95 | ||
|
|
c9820fde61 | ||
|
|
584e5f7958 | ||
|
|
0122cd61c5 | ||
|
|
685e8d7007 | ||
|
|
9c707eff27 | ||
|
|
e80ab5f096 | ||
|
|
5e4a9abd09 | ||
|
|
5065804388 | ||
|
|
8f0fc918e9 | ||
|
|
9c9af5ed94 | ||
|
|
efcf78cbe9 | ||
|
|
e9ce85ebc0 | ||
|
|
501f9ab2c6 | ||
|
|
1a23fe91b4 | ||
|
|
84f98f13c4 | ||
|
|
31516b7863 | ||
|
|
51c7078f47 | ||
|
|
4568f80f2e | ||
|
|
14efc18eca | ||
|
|
05d1dd6b11 | ||
|
|
ac63043c27 | ||
|
|
0d62e76cd6 | ||
|
|
1fc9103b61 | ||
|
|
bd494037bb | ||
|
|
2269c19169 | ||
|
|
8a851ad7f8 | ||
|
|
063613bf4e | ||
|
|
88eb2b2163 | ||
|
|
6d3464fd1f | ||
|
|
edf8e276af | ||
|
|
d593377e45 | ||
|
|
cf75b090d3 | ||
|
|
986b02e217 | ||
|
|
fdad1adaf6 | ||
|
|
e4530471de | ||
|
|
1f42bfc1bd | ||
|
|
2db57b5139 | ||
|
|
02b95ef320 | ||
|
|
9cfe39f1aa | ||
|
|
5fda9e934d | ||
|
|
3a3c1c5a5b | ||
|
|
6628c4df28 | ||
|
|
59bfd40679 | ||
|
|
f890eefdef | ||
|
|
cf2bbfc85a | ||
|
|
508533ebb7 | ||
|
|
2fdc960704 | ||
|
|
b75c27da6f | ||
|
|
364631a155 | ||
|
|
128c19875d | ||
|
|
5501dd741c | ||
|
|
7900d2a20a | ||
|
|
5e39ba596e | ||
|
|
f40c498491 | ||
|
|
8d662edb6c | ||
|
|
cede296b04 | ||
|
|
54421b11f3 | ||
|
|
ae3bdd755e | ||
|
|
739d5ca373 | ||
|
|
24098c561d | ||
|
|
4185a178e7 | ||
|
|
f549ada54f | ||
|
|
aa8c0475b1 | ||
|
|
e2ff829f98 | ||
|
|
414b97adce | ||
|
|
dba7ec4a35 | ||
|
|
c2f3ac24a4 | ||
|
|
7e1ea8f274 | ||
|
|
aa58d0fd77 | ||
|
|
f812151840 | ||
|
|
eca6d2b597 | ||
|
|
58a56bdda2 | ||
|
|
d1f4b60fa1 | ||
|
|
b9dabb165e | ||
|
|
3327e8a6dd | ||
|
|
f6d7b3d2e8 | ||
|
|
793486b2e8 | ||
|
|
3f79b0c7cc | ||
|
|
cc3186cb4a | ||
|
|
0b7d095c0a | ||
|
|
37d35db3d7 | ||
|
|
26051de160 | ||
|
|
747322a02d | ||
|
|
d495c1b804 | ||
|
|
35f9996a4f | ||
|
|
ba79a8ba94 | ||
|
|
50e1e17d09 | ||
|
|
051c7566cc | ||
|
|
8abaf66602 | ||
|
|
6368ade1da | ||
|
|
637ed79603 | ||
|
|
65fc50745b | ||
|
|
d934da1905 | ||
|
|
7be41e19f7 | ||
|
|
073967c80b | ||
|
|
5362e7d346 | ||
|
|
394e87d17c | ||
|
|
0de5a444d3 | ||
|
|
234dbc3ca9 | ||
|
|
c4d88bc529 | ||
|
|
30de64845f | ||
|
|
7e6cccfa3d | ||
|
|
912fd23006 |
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@@ -2,12 +2,11 @@
|
||||
|
||||
Release Notes:
|
||||
|
||||
Use `N/A` in this section if this item should be skipped in the release notes.
|
||||
- N/A
|
||||
|
||||
Add release note lines here:
|
||||
or
|
||||
|
||||
* (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
|
||||
* ...
|
||||
- (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
|
||||
|
||||
If the release notes are only intended for a specific release channel only, add `(<release_channel>-only)` to the end of the release note line.
|
||||
These will be removed by the person making the release.
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -3745,9 +3745,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lsp-types"
|
||||
version = "0.91.1"
|
||||
version = "0.94.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2368312c59425dd133cb9a327afee65be0a633a8ce471d248e2202a48f8f68ae"
|
||||
checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"serde",
|
||||
@@ -8777,7 +8777,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.88.4"
|
||||
version = "0.89.2"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
You are #zed, a language model representing the collective understanding of an open source project called Zed. When a new human visits you, they'll send you their profile. You'll respond with an introduction tailored to their situation. For example, a new user might see something like this:
|
||||
|
||||
Welcome to Zed! Zed is an innovative, open-source platform designed to enhance team communication and collaboration. At the heart of Zed are *contexts*, which create a dynamic digital representation of shared mental models. Contexts offer personalized starting points and the flexibility to edit and explore, enabling teams to align knowledge, streamline communication, and improve overall performance.
|
||||
|
||||
As the #zed model, I'm happy to answer any questions. In fact, I will improve as a result of you doing so!
|
||||
|
||||
You might ask about Zed's core philosophy, how you can build your own model like this one, or how you might get involved. Zed's open source!
|
||||
|
||||
> [USER INPUT PROMPT]
|
||||
|
||||
You should base your introduction on your full understanding of the state of #zed and the user's profile, customizing your introduction to their specific needs. Don't welcome them to Zed if they've been using Zed for 2 days. If they're returning after a while, welcome them back.
|
||||
|
||||
User input begins on a line starting with >.
|
||||
Your output begins on a line starting with <.
|
||||
|
||||
User input begins on a line starting with /.
|
||||
Don't apologize ever.
|
||||
Never say "I apologize".
|
||||
Use simple language and don't flatter the users. Spend your tokens on valuable information.
|
||||
Use simple language and don't flatter the users.
|
||||
Keep it short.
|
||||
Risk being rude.
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
// Whether to pop the completions menu while typing in an editor without
|
||||
// explicitly requesting it.
|
||||
"show_completions_on_input": true,
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
// Controls whether copilot provides suggestion immediately
|
||||
// or waits for a `copilot::Toggle`
|
||||
"show_copilot_suggestions": true,
|
||||
|
||||
@@ -223,6 +223,7 @@ impl Server {
|
||||
.add_request_handler(forward_project_request::<proto::RenameProjectEntry>)
|
||||
.add_request_handler(forward_project_request::<proto::CopyProjectEntry>)
|
||||
.add_request_handler(forward_project_request::<proto::DeleteProjectEntry>)
|
||||
.add_request_handler(forward_project_request::<proto::OnTypeFormatting>)
|
||||
.add_message_handler(create_buffer_for_peer)
|
||||
.add_request_handler(update_buffer)
|
||||
.add_message_handler(update_buffer_file)
|
||||
|
||||
@@ -5010,19 +5010,21 @@ async fn test_project_symbols(
|
||||
.unwrap();
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
|
||||
#[allow(deprecated)]
|
||||
Ok(Some(vec![lsp::SymbolInformation {
|
||||
name: "TWO".into(),
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
fake_language_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(|_, _| async move {
|
||||
Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
|
||||
#[allow(deprecated)]
|
||||
lsp::SymbolInformation {
|
||||
name: "TWO".into(),
|
||||
location: lsp::Location {
|
||||
uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
|
||||
},
|
||||
kind: lsp::SymbolKind::CONSTANT,
|
||||
tags: None,
|
||||
container_name: None,
|
||||
deprecated: None,
|
||||
},
|
||||
kind: lsp::SymbolKind::CONSTANT,
|
||||
tags: None,
|
||||
container_name: None,
|
||||
deprecated: None,
|
||||
}]))
|
||||
])))
|
||||
});
|
||||
|
||||
// Request the definition of a symbol as the guest.
|
||||
@@ -6606,7 +6608,7 @@ async fn test_basic_following(
|
||||
// When client A navigates back and forth, client B does so as well.
|
||||
workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_back(workspace, None, cx)
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6617,7 +6619,7 @@ async fn test_basic_following(
|
||||
|
||||
workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_back(workspace, None, cx)
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6628,7 +6630,7 @@ async fn test_basic_following(
|
||||
|
||||
workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace::Pane::go_forward(workspace, None, cx)
|
||||
workspace.go_forward(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -7377,6 +7379,265 @@ async fn test_peers_simultaneously_following_each_other(
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_on_input_format_from_host_to_guest(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
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;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: ":".to_string(),
|
||||
more_trigger_character: Some(vec![">".to_string()]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry.add(Arc::new(language));
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { a }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a file in an editor as the host.
|
||||
let buffer_a = project_a
|
||||
.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| {
|
||||
Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
|
||||
});
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
cx_b.foreground().run_until_parked();
|
||||
|
||||
// Receive an OnTypeFormatting request as the host's language server.
|
||||
// Return some formattings from the host's language server.
|
||||
fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
|
||||
|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
lsp::Position::new(0, 14),
|
||||
);
|
||||
|
||||
Ok(Some(vec![lsp::TextEdit {
|
||||
new_text: "~<".to_string(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
|
||||
}]))
|
||||
},
|
||||
);
|
||||
|
||||
// Open the buffer on the guest and see that the formattings worked
|
||||
let buffer_b = project_b
|
||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
cx.focus(&editor_a);
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(">", cx);
|
||||
});
|
||||
|
||||
cx_b.foreground().run_until_parked();
|
||||
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a>~< }")
|
||||
});
|
||||
|
||||
// Undo should remove LSP edits first
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a>~< }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
});
|
||||
cx_b.foreground().run_until_parked();
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a> }")
|
||||
});
|
||||
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
});
|
||||
cx_b.foreground().run_until_parked();
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a }")
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_on_input_format_from_guest_to_host(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
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;
|
||||
server
|
||||
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// Set up a fake language server.
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_language_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
first_trigger_character: ":".to_string(),
|
||||
more_trigger_character: Some(vec![">".to_string()]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
client_a.language_registry.add(Arc::new(language));
|
||||
|
||||
client_a
|
||||
.fs
|
||||
.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": "fn main() { a }",
|
||||
"other.rs": "// Test file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
// Open a file in an editor as the guest.
|
||||
let buffer_b = project_b
|
||||
.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| {
|
||||
Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
|
||||
});
|
||||
|
||||
let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
cx_a.foreground().run_until_parked();
|
||||
// Type a on type formatting trigger character as the guest.
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
editor.handle_input(":", cx);
|
||||
cx.focus(&editor_b);
|
||||
});
|
||||
|
||||
// Receive an OnTypeFormatting request as the host's language server.
|
||||
// Return some formattings from the host's language server.
|
||||
cx_a.foreground().start_waiting();
|
||||
fake_language_server
|
||||
.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||
assert_eq!(
|
||||
params.text_document_position.text_document.uri,
|
||||
lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
params.text_document_position.position,
|
||||
lsp::Position::new(0, 14),
|
||||
);
|
||||
|
||||
Ok(Some(vec![lsp::TextEdit {
|
||||
new_text: "~:".to_string(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
|
||||
}]))
|
||||
})
|
||||
.next()
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.foreground().finish_waiting();
|
||||
|
||||
// Open the buffer on the host and see that the formattings worked
|
||||
let buffer_a = project_a
|
||||
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
cx_a.foreground().run_until_parked();
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a:~: }")
|
||||
});
|
||||
|
||||
// Undo should remove LSP edits first
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a:~: }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
});
|
||||
cx_a.foreground().run_until_parked();
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a: }")
|
||||
});
|
||||
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
editor.undo(&Undo, cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
});
|
||||
cx_a.foreground().run_until_parked();
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.text(), "fn main() { a }")
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct RoomParticipants {
|
||||
remote: Vec<String>,
|
||||
|
||||
@@ -609,15 +609,6 @@ impl Item for ProjectDiagnosticsEditor {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn git_diff_recalc(
|
||||
&mut self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.git_diff_recalc(project, cx))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
|
||||
Editor::to_item_events(event)
|
||||
}
|
||||
@@ -1508,6 +1499,7 @@ mod tests {
|
||||
language::init(cx);
|
||||
client::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
Project::init_settings(cx);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2122,6 +2122,15 @@ impl Editor {
|
||||
let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
|
||||
|
||||
// When buffer contents is updated and caret is moved, try triggering on type formatting.
|
||||
if settings::get::<EditorSettings>(cx).use_on_type_format {
|
||||
if let Some(on_type_format_task) =
|
||||
this.trigger_on_type_formatting(text.to_string(), cx)
|
||||
{
|
||||
on_type_format_task.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
if had_active_copilot_suggestion {
|
||||
this.refresh_copilot_suggestions(true, cx);
|
||||
if !this.has_active_copilot_suggestion(cx) {
|
||||
@@ -2500,6 +2509,52 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn trigger_on_type_formatting(
|
||||
&self,
|
||||
input: String,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if input.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let project = self.project.as_ref()?;
|
||||
let position = self.selections.newest_anchor().head();
|
||||
let (buffer, buffer_position) = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(position.clone(), cx)?;
|
||||
|
||||
// OnTypeFormatting retuns a list of edits, no need to pass them between Zed instances,
|
||||
// hence we do LSP request & edit on host side only — add formats to host's history.
|
||||
let push_to_lsp_host_history = true;
|
||||
// If this is not the host, append its history with new edits.
|
||||
let push_to_client_history = project.read(cx).is_remote();
|
||||
|
||||
let on_type_formatting = project.update(cx, |project, cx| {
|
||||
project.on_type_format(
|
||||
buffer.clone(),
|
||||
buffer_position,
|
||||
input,
|
||||
push_to_lsp_host_history,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
Some(cx.spawn(|editor, mut cx| async move {
|
||||
if let Some(transaction) = on_type_formatting.await? {
|
||||
if push_to_client_history {
|
||||
buffer.update(&mut cx, |buffer, _| {
|
||||
buffer.push_transaction(transaction, Instant::now());
|
||||
});
|
||||
}
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.refresh_document_highlights(cx);
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
|
||||
if self.pending_rename.is_some() {
|
||||
return;
|
||||
@@ -4876,12 +4931,12 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn push_to_nav_history(
|
||||
&self,
|
||||
&mut self,
|
||||
cursor_anchor: Anchor,
|
||||
new_position: Option<Point>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(nav_history) = &self.nav_history {
|
||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
||||
let buffer = self.buffer.read(cx).read(cx);
|
||||
let cursor_position = cursor_anchor.to_point(&buffer);
|
||||
let scroll_state = self.scroll_manager.anchor();
|
||||
@@ -6818,6 +6873,7 @@ impl Editor {
|
||||
multi_buffer::Event::Saved => cx.emit(Event::Saved),
|
||||
multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged),
|
||||
multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged),
|
||||
multi_buffer::Event::DiffBaseChanged => cx.emit(Event::DiffBaseChanged),
|
||||
multi_buffer::Event::Closed => cx.emit(Event::Closed),
|
||||
multi_buffer::Event::DiagnosticsUpdated => {
|
||||
self.refresh_active_diagnostics(cx);
|
||||
@@ -7206,6 +7262,7 @@ pub enum Event {
|
||||
DirtyChanged,
|
||||
Saved,
|
||||
TitleChanged,
|
||||
DiffBaseChanged,
|
||||
SelectionsChanged {
|
||||
local: bool,
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ pub struct EditorSettings {
|
||||
pub cursor_blink: bool,
|
||||
pub hover_popover_enabled: bool,
|
||||
pub show_completions_on_input: bool,
|
||||
pub use_on_type_format: bool,
|
||||
pub scrollbar: Scrollbar,
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ pub struct EditorSettingsContent {
|
||||
pub cursor_blink: Option<bool>,
|
||||
pub hover_popover_enabled: Option<bool>,
|
||||
pub show_completions_on_input: Option<bool>,
|
||||
pub use_on_type_format: Option<bool>,
|
||||
pub scrollbar: Option<ScrollbarContent>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1246,7 +1246,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
|
||||
#[gpui::test]
|
||||
async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
|
||||
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
|
||||
@@ -1358,7 +1358,7 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon
|
||||
#[gpui::test]
|
||||
async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
|
||||
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
|
||||
@@ -1473,7 +1473,7 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
|
||||
#[gpui::test]
|
||||
async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state("one «two threeˇ» four");
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
|
||||
@@ -1637,7 +1637,7 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
cx.set_state(indoc! {"
|
||||
const a: ˇA = (
|
||||
@@ -1685,7 +1685,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
cx.set_state(indoc! {"
|
||||
const a: ˇA = (
|
||||
@@ -1751,7 +1751,7 @@ async fn test_tab(cx: &mut gpui::TestAppContext) {
|
||||
settings.defaults.tab_size = NonZeroU32::new(3)
|
||||
});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state(indoc! {"
|
||||
ˇabˇc
|
||||
ˇ🏀ˇ🏀ˇefg
|
||||
@@ -1779,7 +1779,7 @@ async fn test_tab(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig::default(),
|
||||
@@ -1850,7 +1850,7 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
cx.set_state(indoc! {"
|
||||
fn a() {
|
||||
@@ -1876,7 +1876,7 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
|
||||
settings.defaults.tab_size = NonZeroU32::new(4);
|
||||
});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
«oneˇ» «twoˇ»
|
||||
@@ -1949,7 +1949,7 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
|
||||
settings.defaults.hard_tabs = Some(true);
|
||||
});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
// select two ranges on one line
|
||||
cx.set_state(indoc! {"
|
||||
@@ -2156,7 +2156,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
|
||||
async fn test_backspace(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
// Basic backspace
|
||||
cx.set_state(indoc! {"
|
||||
@@ -2205,7 +2205,7 @@ async fn test_backspace(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_delete(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state(indoc! {"
|
||||
onˇe two three
|
||||
fou«rˇ» five six
|
||||
@@ -2559,7 +2559,7 @@ fn test_transpose(cx: &mut TestAppContext) {
|
||||
async fn test_clipboard(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
|
||||
cx.update_editor(|e, cx| e.cut(&Cut, cx));
|
||||
@@ -2641,7 +2641,7 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig::default(),
|
||||
Some(tree_sitter_rust::language()),
|
||||
@@ -3085,7 +3085,7 @@ fn test_add_selection_above_below(cx: &mut TestAppContext) {
|
||||
async fn test_select_next(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state("abc\nˇabc abc\ndefabc\nabc");
|
||||
|
||||
cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
|
||||
@@ -3314,7 +3314,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
@@ -3485,7 +3485,7 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let html_language = Arc::new(
|
||||
Language::new(
|
||||
@@ -3721,7 +3721,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let rust_language = Arc::new(
|
||||
Language::new(
|
||||
@@ -4938,7 +4938,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
registry.add(language.clone());
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| {
|
||||
buffer.set_language_registry(registry);
|
||||
buffer.set_language(Some(language), cx);
|
||||
@@ -5060,7 +5060,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
|
||||
async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let html_language = Arc::new(
|
||||
Language::new(
|
||||
@@ -5985,7 +5985,7 @@ fn test_combine_syntax_and_fuzzy_match_highlights() {
|
||||
async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx);
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let diff_base = r#"
|
||||
use some::mod;
|
||||
|
||||
@@ -40,7 +40,10 @@ use language::{
|
||||
language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16,
|
||||
Selection,
|
||||
};
|
||||
use project::ProjectPath;
|
||||
use project::{
|
||||
project_settings::{GitGutterSetting, ProjectSettings},
|
||||
ProjectPath,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@@ -51,7 +54,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use text::Point;
|
||||
use workspace::{item::Item, GitGutterSetting, WorkspaceSettings};
|
||||
use workspace::item::Item;
|
||||
|
||||
enum FoldMarkers {}
|
||||
|
||||
@@ -551,11 +554,8 @@ impl EditorElement {
|
||||
let scroll_top = scroll_position.y() * line_height;
|
||||
|
||||
let show_gutter = matches!(
|
||||
settings::get::<WorkspaceSettings>(cx)
|
||||
.git
|
||||
.git_gutter
|
||||
.unwrap_or_default(),
|
||||
GitGutterSetting::TrackedFiles
|
||||
settings::get::<ProjectSettings>(cx).git.git_gutter,
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
);
|
||||
|
||||
if show_gutter {
|
||||
|
||||
@@ -720,17 +720,6 @@ impl Item for Editor {
|
||||
})
|
||||
}
|
||||
|
||||
fn git_diff_recalc(
|
||||
&mut self,
|
||||
_project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.buffer().update(cx, |multibuffer, cx| {
|
||||
multibuffer.git_diff_recalc(cx);
|
||||
});
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
|
||||
let mut result = SmallVec::new();
|
||||
match event {
|
||||
|
||||
@@ -66,6 +66,7 @@ pub enum Event {
|
||||
},
|
||||
Edited,
|
||||
Reloaded,
|
||||
DiffBaseChanged,
|
||||
LanguageChanged,
|
||||
Reparsed,
|
||||
Saved,
|
||||
@@ -343,17 +344,6 @@ impl MultiBuffer {
|
||||
self.read(cx).symbols_containing(offset, theme)
|
||||
}
|
||||
|
||||
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let buffers = self.buffers.borrow();
|
||||
for buffer_state in buffers.values() {
|
||||
if buffer_state.buffer.read(cx).needs_git_diff_recalc() {
|
||||
buffer_state
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.git_diff_recalc(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn edit<I, S, T>(
|
||||
&mut self,
|
||||
edits: I,
|
||||
@@ -1312,6 +1302,7 @@ impl MultiBuffer {
|
||||
language::Event::Saved => Event::Saved,
|
||||
language::Event::FileHandleChanged => Event::FileHandleChanged,
|
||||
language::Event::Reloaded => Event::Reloaded,
|
||||
language::Event::DiffBaseChanged => Event::DiffBaseChanged,
|
||||
language::Event::LanguageChanged => Event::LanguageChanged,
|
||||
language::Event::Reparsed => Event::Reparsed,
|
||||
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
|
||||
@@ -1550,6 +1541,13 @@ impl MultiBuffer {
|
||||
cx.add_model(|cx| Self::singleton(buffer, cx))
|
||||
}
|
||||
|
||||
pub fn build_from_buffer(
|
||||
buffer: ModelHandle<Buffer>,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> ModelHandle<Self> {
|
||||
cx.add_model(|cx| Self::singleton(buffer, cx))
|
||||
}
|
||||
|
||||
pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> ModelHandle<Self> {
|
||||
cx.add_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0);
|
||||
@@ -3870,10 +3868,13 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::editor_tests::init_test;
|
||||
|
||||
use super::*;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, TestAppContext};
|
||||
use language::{Buffer, Rope};
|
||||
use project::{FakeFs, Project};
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
use std::{env, rc::Rc};
|
||||
@@ -4564,73 +4565,85 @@ mod tests {
|
||||
#[gpui::test]
|
||||
async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
|
||||
use git::diff::DiffHunkStatus;
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
||||
// buffer has two modified hunks with two rows each
|
||||
let buffer_1 = cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(
|
||||
0,
|
||||
"
|
||||
1.zero
|
||||
1.ONE
|
||||
1.TWO
|
||||
1.three
|
||||
1.FOUR
|
||||
1.FIVE
|
||||
1.six
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_buffer(
|
||||
"
|
||||
1.zero
|
||||
1.ONE
|
||||
1.TWO
|
||||
1.three
|
||||
1.FOUR
|
||||
1.FIVE
|
||||
1.six
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
buffer_1.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(
|
||||
Some(
|
||||
"
|
||||
1.zero
|
||||
1.one
|
||||
1.two
|
||||
1.three
|
||||
1.four
|
||||
1.five
|
||||
1.six
|
||||
"
|
||||
1.zero
|
||||
1.one
|
||||
1.two
|
||||
1.three
|
||||
1.four
|
||||
1.five
|
||||
1.six
|
||||
"
|
||||
.unindent(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
buffer
|
||||
});
|
||||
|
||||
// buffer has a deletion hunk and an insertion hunk
|
||||
let buffer_2 = cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(
|
||||
0,
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.five
|
||||
2.six
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_buffer(
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.five
|
||||
2.six
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
buffer_2.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(
|
||||
Some(
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.one-and-a-half
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.six
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.one-and-a-half
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.six
|
||||
"
|
||||
.unindent(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
buffer
|
||||
});
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
@@ -7,6 +7,7 @@ use gpui::{
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{Buffer, BufferSnapshot};
|
||||
use project::{FakeFs, Project};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
@@ -25,11 +26,16 @@ pub struct EditorTestContext<'a> {
|
||||
}
|
||||
|
||||
impl<'a> EditorTestContext<'a> {
|
||||
pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
|
||||
pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
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_simple("", cx), cx)
|
||||
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1577,6 +1577,7 @@ mod tests {
|
||||
super::init(cx);
|
||||
editor::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
Project::init_settings(cx);
|
||||
state
|
||||
})
|
||||
}
|
||||
|
||||
@@ -161,13 +161,6 @@ impl BufferDiff {
|
||||
self.tree = SumTree::new();
|
||||
}
|
||||
|
||||
pub fn needs_update(&self, buffer: &text::BufferSnapshot) -> bool {
|
||||
match &self.last_buffer_version {
|
||||
Some(last) => buffer.version().changed_since(last),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update(&mut self, diff_base: &str, buffer: &text::BufferSnapshot) {
|
||||
let mut tree = SumTree::new();
|
||||
|
||||
|
||||
@@ -434,7 +434,9 @@ impl<T: Entity> ModelHandle<T> {
|
||||
Duration::from_secs(1)
|
||||
};
|
||||
|
||||
let executor = cx.background().clone();
|
||||
async move {
|
||||
executor.start_waiting();
|
||||
let notification = crate::util::timeout(duration, rx.next())
|
||||
.await
|
||||
.expect("next notification timed out");
|
||||
|
||||
@@ -876,6 +876,14 @@ impl Background {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn start_waiting(&self) {
|
||||
match self {
|
||||
Self::Deterministic { executor, .. } => executor.start_waiting(),
|
||||
_ => panic!("this method can only be called on a deterministic executor"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Background {
|
||||
|
||||
@@ -50,16 +50,10 @@ pub use {tree_sitter_rust, tree_sitter_typescript};
|
||||
|
||||
pub use lsp::DiagnosticSeverity;
|
||||
|
||||
struct GitDiffStatus {
|
||||
diff: git::diff::BufferDiff,
|
||||
update_in_progress: bool,
|
||||
update_requested: bool,
|
||||
}
|
||||
|
||||
pub struct Buffer {
|
||||
text: TextBuffer,
|
||||
diff_base: Option<String>,
|
||||
git_diff_status: GitDiffStatus,
|
||||
git_diff: git::diff::BufferDiff,
|
||||
file: Option<Arc<dyn File>>,
|
||||
saved_version: clock::Global,
|
||||
saved_version_fingerprint: RopeFingerprint,
|
||||
@@ -195,6 +189,7 @@ pub enum Event {
|
||||
Saved,
|
||||
FileHandleChanged,
|
||||
Reloaded,
|
||||
DiffBaseChanged,
|
||||
LanguageChanged,
|
||||
Reparsed,
|
||||
DiagnosticsUpdated,
|
||||
@@ -466,11 +461,7 @@ impl Buffer {
|
||||
was_dirty_before_starting_transaction: None,
|
||||
text: buffer,
|
||||
diff_base,
|
||||
git_diff_status: GitDiffStatus {
|
||||
diff: git::diff::BufferDiff::new(),
|
||||
update_in_progress: false,
|
||||
update_requested: false,
|
||||
},
|
||||
git_diff: git::diff::BufferDiff::new(),
|
||||
file,
|
||||
syntax_map: Mutex::new(SyntaxMap::new()),
|
||||
parsing_in_background: false,
|
||||
@@ -501,7 +492,7 @@ impl Buffer {
|
||||
BufferSnapshot {
|
||||
text,
|
||||
syntax,
|
||||
git_diff: self.git_diff_status.diff.clone(),
|
||||
git_diff: self.git_diff.clone(),
|
||||
file: self.file.clone(),
|
||||
remote_selections: self.remote_selections.clone(),
|
||||
diagnostics: self.diagnostics.clone(),
|
||||
@@ -620,7 +611,6 @@ impl Buffer {
|
||||
cx,
|
||||
);
|
||||
}
|
||||
self.git_diff_recalc(cx);
|
||||
cx.emit(Event::Reloaded);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -676,50 +666,29 @@ impl Buffer {
|
||||
pub fn set_diff_base(&mut self, diff_base: Option<String>, cx: &mut ModelContext<Self>) {
|
||||
self.diff_base = diff_base;
|
||||
self.git_diff_recalc(cx);
|
||||
cx.emit(Event::DiffBaseChanged);
|
||||
}
|
||||
|
||||
pub fn needs_git_diff_recalc(&self) -> bool {
|
||||
self.git_diff_status.diff.needs_update(self)
|
||||
}
|
||||
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
|
||||
let diff_base = self.diff_base.clone()?; // TODO: Make this an Arc
|
||||
let snapshot = self.snapshot();
|
||||
|
||||
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
|
||||
if self.git_diff_status.update_in_progress {
|
||||
self.git_diff_status.update_requested = true;
|
||||
return;
|
||||
}
|
||||
let mut diff = self.git_diff.clone();
|
||||
let diff = cx.background().spawn(async move {
|
||||
diff.update(&diff_base, &snapshot).await;
|
||||
diff
|
||||
});
|
||||
|
||||
if let Some(diff_base) = &self.diff_base {
|
||||
let snapshot = self.snapshot();
|
||||
let diff_base = diff_base.clone();
|
||||
|
||||
let mut diff = self.git_diff_status.diff.clone();
|
||||
let diff = cx.background().spawn(async move {
|
||||
diff.update(&diff_base, &snapshot).await;
|
||||
diff
|
||||
});
|
||||
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let buffer_diff = diff.await;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.git_diff_status.diff = buffer_diff;
|
||||
this.git_diff_update_count += 1;
|
||||
cx.notify();
|
||||
|
||||
this.git_diff_status.update_in_progress = false;
|
||||
if this.git_diff_status.update_requested {
|
||||
this.git_diff_recalc(cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
} else {
|
||||
let snapshot = self.snapshot();
|
||||
self.git_diff_status.diff.clear(&snapshot);
|
||||
self.git_diff_update_count += 1;
|
||||
cx.notify();
|
||||
}
|
||||
let handle = cx.weak_handle();
|
||||
Some(cx.spawn_weak(|_, mut cx| async move {
|
||||
let buffer_diff = diff.await;
|
||||
if let Some(this) = handle.upgrade(&mut cx) {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.git_diff = buffer_diff;
|
||||
this.git_diff_update_count += 1;
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn close(&mut self, cx: &mut ModelContext<Self>) {
|
||||
|
||||
@@ -796,6 +796,12 @@ impl LanguageRegistry {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<PendingLanguageServer> {
|
||||
let server_id = self.state.write().next_language_server_id();
|
||||
log::info!(
|
||||
"starting language server name:{}, path:{root_path:?}, id:{server_id}",
|
||||
adapter.name.0
|
||||
);
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if language.fake_adapter.is_some() {
|
||||
let task = cx.spawn(|cx| async move {
|
||||
@@ -825,7 +831,6 @@ impl LanguageRegistry {
|
||||
Ok(server)
|
||||
});
|
||||
|
||||
let server_id = self.state.write().next_language_server_id();
|
||||
return Some(PendingLanguageServer { server_id, task });
|
||||
}
|
||||
|
||||
@@ -834,7 +839,6 @@ impl LanguageRegistry {
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("language server download directory has not been assigned"))
|
||||
.log_err()?;
|
||||
|
||||
let this = self.clone();
|
||||
let language = language.clone();
|
||||
let http_client = http_client.clone();
|
||||
@@ -843,7 +847,6 @@ impl LanguageRegistry {
|
||||
let adapter = adapter.clone();
|
||||
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||
let server_id = self.state.write().next_language_server_id();
|
||||
|
||||
let task = cx.spawn(|cx| async move {
|
||||
login_shell_env_loaded.await;
|
||||
|
||||
@@ -20,7 +20,7 @@ anyhow.workspace = true
|
||||
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
lsp-types = "0.91"
|
||||
lsp-types = "0.94"
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -361,13 +361,18 @@ impl LanguageServer {
|
||||
capabilities: ClientCapabilities {
|
||||
workspace: Some(WorkspaceClientCapabilities {
|
||||
configuration: Some(true),
|
||||
did_change_watched_files: Some(DynamicRegistrationClientCapabilities {
|
||||
did_change_watched_files: Some(DidChangeWatchedFilesClientCapabilities {
|
||||
dynamic_registration: Some(true),
|
||||
relative_pattern_support: Some(true),
|
||||
}),
|
||||
did_change_configuration: Some(DynamicRegistrationClientCapabilities {
|
||||
dynamic_registration: Some(true),
|
||||
}),
|
||||
workspace_folders: Some(true),
|
||||
symbol: Some(WorkspaceSymbolClientCapabilities {
|
||||
resolve_support: None,
|
||||
..WorkspaceSymbolClientCapabilities::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
text_document: Some(TextDocumentClientCapabilities {
|
||||
@@ -849,10 +854,12 @@ impl FakeLanguageServer {
|
||||
T: request::Request,
|
||||
T::Result: 'static + Send,
|
||||
{
|
||||
self.server.executor.start_waiting();
|
||||
self.server.request::<T>(params).await
|
||||
}
|
||||
|
||||
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
|
||||
self.server.executor.start_waiting();
|
||||
self.try_receive_notification::<T>().await.unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,24 @@ use client::proto::{self, PeerId};
|
||||
use fs::LineEnding;
|
||||
use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
||||
use language::{
|
||||
language_settings::language_settings,
|
||||
point_from_lsp, point_to_lsp,
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
|
||||
Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Unclipped,
|
||||
Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities};
|
||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||
|
||||
pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
|
||||
lsp::FormattingOptions {
|
||||
tab_size,
|
||||
insert_spaces: true,
|
||||
insert_final_newline: Some(true),
|
||||
..lsp::FormattingOptions::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub(crate) trait LspCommand: 'static + Sized {
|
||||
type Response: 'static + Default + Send;
|
||||
@@ -109,6 +119,25 @@ pub(crate) struct GetCodeActions {
|
||||
pub range: Range<Anchor>,
|
||||
}
|
||||
|
||||
pub(crate) struct OnTypeFormatting {
|
||||
pub position: PointUtf16,
|
||||
pub trigger: String,
|
||||
pub options: FormattingOptions,
|
||||
pub push_to_history: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct FormattingOptions {
|
||||
tab_size: u32,
|
||||
}
|
||||
|
||||
impl From<lsp::FormattingOptions> for FormattingOptions {
|
||||
fn from(value: lsp::FormattingOptions) -> Self {
|
||||
Self {
|
||||
tab_size: value.tab_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for PrepareRename {
|
||||
type Response = Option<Range<Anchor>>;
|
||||
@@ -1495,6 +1524,7 @@ impl LspCommand for GetCodeActions {
|
||||
context: lsp::CodeActionContext {
|
||||
diagnostics: relevant_diagnostics,
|
||||
only: language_server.code_action_kinds(),
|
||||
..lsp::CodeActionContext::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1596,3 +1626,134 @@ impl LspCommand for GetCodeActions {
|
||||
message.buffer_id
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for OnTypeFormatting {
|
||||
type Response = Option<Transaction>;
|
||||
type LspRequest = lsp::request::OnTypeFormatting;
|
||||
type ProtoRequest = proto::OnTypeFormatting;
|
||||
|
||||
fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
|
||||
let Some(on_type_formatting_options) = &server_capabilities.document_on_type_formatting_provider else { return false };
|
||||
on_type_formatting_options
|
||||
.first_trigger_character
|
||||
.contains(&self.trigger)
|
||||
|| on_type_formatting_options
|
||||
.more_trigger_character
|
||||
.iter()
|
||||
.flatten()
|
||||
.any(|chars| chars.contains(&self.trigger))
|
||||
}
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
_: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &AppContext,
|
||||
) -> lsp::DocumentOnTypeFormattingParams {
|
||||
lsp::DocumentOnTypeFormattingParams {
|
||||
text_document_position: lsp::TextDocumentPositionParams::new(
|
||||
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
|
||||
point_to_lsp(self.position),
|
||||
),
|
||||
ch: self.trigger.clone(),
|
||||
options: lsp_formatting_options(self.options.tab_size),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Option<Vec<lsp::TextEdit>>,
|
||||
project: ModelHandle<Project>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Option<Transaction>> {
|
||||
if let Some(edits) = message {
|
||||
let (lsp_adapter, lsp_server) =
|
||||
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
|
||||
Project::deserialize_edits(
|
||||
project,
|
||||
buffer,
|
||||
edits,
|
||||
self.push_to_history,
|
||||
lsp_adapter,
|
||||
lsp_server,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::OnTypeFormatting {
|
||||
proto::OnTypeFormatting {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id(),
|
||||
position: Some(language::proto::serialize_anchor(
|
||||
&buffer.anchor_before(self.position),
|
||||
)),
|
||||
trigger: self.trigger.clone(),
|
||||
version: serialize_version(&buffer.version()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
message: proto::OnTypeFormatting,
|
||||
_: ModelHandle<Project>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
let position = message
|
||||
.position
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid position"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})
|
||||
.await?;
|
||||
|
||||
let tab_size = buffer.read_with(&cx, |buffer, cx| {
|
||||
let language_name = buffer.language().map(|language| language.name());
|
||||
language_settings(language_name.as_deref(), cx).tab_size
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
|
||||
trigger: message.trigger.clone(),
|
||||
options: lsp_formatting_options(tab_size.get()).into(),
|
||||
push_to_history: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: Option<Transaction>,
|
||||
_: &mut Project,
|
||||
_: PeerId,
|
||||
_: &clock::Global,
|
||||
_: &mut AppContext,
|
||||
) -> proto::OnTypeFormattingResponse {
|
||||
proto::OnTypeFormattingResponse {
|
||||
transaction: response
|
||||
.map(|transaction| language::proto::serialize_transaction(&transaction)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::OnTypeFormattingResponse,
|
||||
_: ModelHandle<Project>,
|
||||
_: ModelHandle<Buffer>,
|
||||
_: AsyncAppContext,
|
||||
) -> Result<Option<Transaction>> {
|
||||
let Some(transaction) = message.transaction else { return Ok(None) };
|
||||
Ok(Some(language::proto::deserialize_transaction(transaction)?))
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
|
||||
message.buffer_id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod ignore;
|
||||
mod lsp_command;
|
||||
mod project_settings;
|
||||
pub mod project_settings;
|
||||
pub mod search;
|
||||
pub mod terminals;
|
||||
pub mod worktree;
|
||||
@@ -14,7 +14,10 @@ use clock::ReplicaId;
|
||||
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
||||
use copilot::Copilot;
|
||||
use futures::{
|
||||
channel::mpsc::{self, UnboundedReceiver},
|
||||
channel::{
|
||||
mpsc::{self, UnboundedReceiver},
|
||||
oneshot,
|
||||
},
|
||||
future::{try_join_all, Shared},
|
||||
stream::FuturesUnordered,
|
||||
AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
|
||||
@@ -37,6 +40,7 @@ use language::{
|
||||
PendingLanguageServer, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16,
|
||||
Transaction, Unclipped,
|
||||
};
|
||||
use log::error;
|
||||
use lsp::{
|
||||
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
|
||||
DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||
@@ -130,6 +134,8 @@ pub struct Project {
|
||||
incomplete_remote_buffers: HashMap<u64, Option<ModelHandle<Buffer>>>,
|
||||
buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
|
||||
buffers_being_formatted: HashSet<u64>,
|
||||
buffers_needing_diff: HashSet<WeakModelHandle<Buffer>>,
|
||||
git_diff_debouncer: DelayedDebounced,
|
||||
nonce: u128,
|
||||
_maintain_buffer_languages: Task<()>,
|
||||
_maintain_workspace_config: Task<()>,
|
||||
@@ -137,6 +143,49 @@ pub struct Project {
|
||||
copilot_enabled: bool,
|
||||
}
|
||||
|
||||
struct DelayedDebounced {
|
||||
task: Option<Task<()>>,
|
||||
cancel_channel: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl DelayedDebounced {
|
||||
fn new() -> DelayedDebounced {
|
||||
DelayedDebounced {
|
||||
task: None,
|
||||
cancel_channel: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
|
||||
where
|
||||
F: 'static + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
|
||||
{
|
||||
if let Some(channel) = self.cancel_channel.take() {
|
||||
_ = channel.send(());
|
||||
}
|
||||
|
||||
let (sender, mut receiver) = oneshot::channel::<()>();
|
||||
self.cancel_channel = Some(sender);
|
||||
|
||||
let previous_task = self.task.take();
|
||||
self.task = Some(cx.spawn(|workspace, mut cx| async move {
|
||||
let mut timer = cx.background().timer(delay).fuse();
|
||||
if let Some(previous_task) = previous_task {
|
||||
previous_task.await;
|
||||
}
|
||||
|
||||
futures::select_biased! {
|
||||
_ = receiver => return,
|
||||
_ = timer => {}
|
||||
}
|
||||
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| (func)(workspace, cx))
|
||||
.await;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
struct LspBufferSnapshot {
|
||||
version: i32,
|
||||
snapshot: TextBufferSnapshot,
|
||||
@@ -417,6 +466,7 @@ impl Project {
|
||||
client.add_model_request_handler(Self::handle_delete_project_entry);
|
||||
client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
|
||||
client.add_model_request_handler(Self::handle_apply_code_action);
|
||||
client.add_model_request_handler(Self::handle_on_type_formatting);
|
||||
client.add_model_request_handler(Self::handle_reload_buffers);
|
||||
client.add_model_request_handler(Self::handle_synchronize_buffers);
|
||||
client.add_model_request_handler(Self::handle_format_buffers);
|
||||
@@ -483,6 +533,8 @@ impl Project {
|
||||
language_server_statuses: Default::default(),
|
||||
last_workspace_edits_by_language_server: Default::default(),
|
||||
buffers_being_formatted: Default::default(),
|
||||
buffers_needing_diff: Default::default(),
|
||||
git_diff_debouncer: DelayedDebounced::new(),
|
||||
nonce: StdRng::from_entropy().gen(),
|
||||
terminals: Terminals {
|
||||
local_handles: Vec::new(),
|
||||
@@ -572,6 +624,8 @@ impl Project {
|
||||
last_workspace_edits_by_language_server: Default::default(),
|
||||
opened_buffers: Default::default(),
|
||||
buffers_being_formatted: Default::default(),
|
||||
buffers_needing_diff: Default::default(),
|
||||
git_diff_debouncer: DelayedDebounced::new(),
|
||||
buffer_snapshots: Default::default(),
|
||||
nonce: StdRng::from_entropy().gen(),
|
||||
terminals: Terminals {
|
||||
@@ -1406,7 +1460,7 @@ impl Project {
|
||||
};
|
||||
|
||||
cx.foreground().spawn(async move {
|
||||
pump_loading_buffer_reciever(loading_watch)
|
||||
wait_for_loading_buffer(loading_watch)
|
||||
.await
|
||||
.map_err(|error| anyhow!("{}", error))
|
||||
})
|
||||
@@ -1606,6 +1660,7 @@ impl Project {
|
||||
buffer: &ModelHandle<Buffer>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
self.request_buffer_diff_recalculation(buffer, cx);
|
||||
buffer.update(cx, |buffer, _| {
|
||||
buffer.set_language_registry(self.languages.clone())
|
||||
});
|
||||
@@ -1923,6 +1978,13 @@ impl Project {
|
||||
event: &BufferEvent,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<()> {
|
||||
if matches!(
|
||||
event,
|
||||
BufferEvent::Edited { .. } | BufferEvent::Reloaded | BufferEvent::DiffBaseChanged
|
||||
) {
|
||||
self.request_buffer_diff_recalculation(&buffer, cx);
|
||||
}
|
||||
|
||||
match event {
|
||||
BufferEvent::Operation(operation) => {
|
||||
self.buffer_ordered_messages_tx
|
||||
@@ -2062,6 +2124,74 @@ impl Project {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_buffer_diff_recalculation(
|
||||
&mut self,
|
||||
buffer: &ModelHandle<Buffer>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.buffers_needing_diff.insert(buffer.downgrade());
|
||||
let first_insertion = self.buffers_needing_diff.len() == 1;
|
||||
|
||||
let settings = settings::get::<ProjectSettings>(cx);
|
||||
let delay = if let Some(delay) = settings.git.gutter_debounce {
|
||||
delay
|
||||
} else {
|
||||
if first_insertion {
|
||||
let this = cx.weak_handle();
|
||||
cx.defer(move |cx| {
|
||||
if let Some(this) = this.upgrade(cx) {
|
||||
this.update(cx, |this, cx| {
|
||||
this.recalculate_buffer_diffs(cx).detach();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const MIN_DELAY: u64 = 50;
|
||||
let delay = delay.max(MIN_DELAY);
|
||||
let duration = Duration::from_millis(delay);
|
||||
|
||||
self.git_diff_debouncer
|
||||
.fire_new(duration, cx, move |this, cx| {
|
||||
this.recalculate_buffer_diffs(cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let buffers: Vec<_> = this.update(&mut cx, |this, _| {
|
||||
this.buffers_needing_diff.drain().collect()
|
||||
});
|
||||
|
||||
let tasks: Vec<_> = this.update(&mut cx, |_, cx| {
|
||||
buffers
|
||||
.iter()
|
||||
.filter_map(|buffer| {
|
||||
let buffer = buffer.upgrade(cx)?;
|
||||
buffer.update(cx, |buffer, cx| buffer.git_diff_recalc(cx))
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
futures::future::join_all(tasks).await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if !this.buffers_needing_diff.is_empty() {
|
||||
this.recalculate_buffer_diffs(cx).detach();
|
||||
} else {
|
||||
// TODO: Would a `ModelContext<Project>.notify()` suffice here?
|
||||
for buffer in buffers {
|
||||
if let Some(buffer) = buffer.upgrade(cx) {
|
||||
buffer.update(cx, |_, cx| cx.notify());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
fn language_servers_for_worktree(
|
||||
&self,
|
||||
worktree_id: WorktreeId,
|
||||
@@ -2888,10 +3018,12 @@ impl Project {
|
||||
if let Some(worktree) = worktree.upgrade(cx) {
|
||||
let worktree = worktree.read(cx);
|
||||
if let Some(abs_path) = worktree.abs_path().to_str() {
|
||||
if let Some(suffix) = watcher
|
||||
.glob_pattern
|
||||
.strip_prefix(abs_path)
|
||||
.and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR))
|
||||
if let Some(suffix) = match &watcher.glob_pattern {
|
||||
lsp::GlobPattern::String(s) => s,
|
||||
lsp::GlobPattern::Relative(rp) => &rp.pattern,
|
||||
}
|
||||
.strip_prefix(abs_path)
|
||||
.and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR))
|
||||
{
|
||||
if let Some(glob) = Glob::new(suffix).log_err() {
|
||||
builders
|
||||
@@ -3476,12 +3608,7 @@ impl Project {
|
||||
language_server
|
||||
.request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
|
||||
text_document,
|
||||
options: lsp::FormattingOptions {
|
||||
tab_size: tab_size.into(),
|
||||
insert_spaces: true,
|
||||
insert_final_newline: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
options: lsp_command::lsp_formatting_options(tab_size.get()),
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
.await?
|
||||
@@ -3497,12 +3624,7 @@ impl Project {
|
||||
.request::<lsp::request::RangeFormatting>(lsp::DocumentRangeFormattingParams {
|
||||
text_document,
|
||||
range: lsp::Range::new(buffer_start, buffer_end),
|
||||
options: lsp::FormattingOptions {
|
||||
tab_size: tab_size.into(),
|
||||
insert_spaces: true,
|
||||
insert_final_newline: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
options: lsp_command::lsp_formatting_options(tab_size.get()),
|
||||
work_done_progress_params: Default::default(),
|
||||
})
|
||||
.await?
|
||||
@@ -3640,7 +3762,7 @@ impl Project {
|
||||
let worktree_abs_path = worktree.abs_path().clone();
|
||||
requests.push(
|
||||
server
|
||||
.request::<lsp::request::WorkspaceSymbol>(
|
||||
.request::<lsp::request::WorkspaceSymbolRequest>(
|
||||
lsp::WorkspaceSymbolParams {
|
||||
query: query.to_string(),
|
||||
..Default::default()
|
||||
@@ -3648,12 +3770,32 @@ impl Project {
|
||||
)
|
||||
.log_err()
|
||||
.map(move |response| {
|
||||
let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response {
|
||||
lsp::WorkspaceSymbolResponse::Flat(flat_responses) => {
|
||||
flat_responses.into_iter().map(|lsp_symbol| {
|
||||
(lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location)
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
lsp::WorkspaceSymbolResponse::Nested(nested_responses) => {
|
||||
nested_responses.into_iter().filter_map(|lsp_symbol| {
|
||||
let location = match lsp_symbol.location {
|
||||
lsp::OneOf::Left(location) => location,
|
||||
lsp::OneOf::Right(_) => {
|
||||
error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport");
|
||||
return None
|
||||
}
|
||||
};
|
||||
Some((lsp_symbol.name, lsp_symbol.kind, location))
|
||||
}).collect::<Vec<_>>()
|
||||
}
|
||||
}).unwrap_or_default();
|
||||
|
||||
(
|
||||
adapter,
|
||||
language,
|
||||
worktree_id,
|
||||
worktree_abs_path,
|
||||
response.unwrap_or_default(),
|
||||
lsp_symbols,
|
||||
)
|
||||
}),
|
||||
);
|
||||
@@ -3675,53 +3817,54 @@ impl Project {
|
||||
adapter_language,
|
||||
source_worktree_id,
|
||||
worktree_abs_path,
|
||||
response,
|
||||
lsp_symbols,
|
||||
) in responses
|
||||
{
|
||||
symbols.extend(response.into_iter().flatten().filter_map(|lsp_symbol| {
|
||||
let abs_path = lsp_symbol.location.uri.to_file_path().ok()?;
|
||||
let mut worktree_id = source_worktree_id;
|
||||
let path;
|
||||
if let Some((worktree, rel_path)) =
|
||||
this.find_local_worktree(&abs_path, cx)
|
||||
{
|
||||
worktree_id = worktree.read(cx).id();
|
||||
path = rel_path;
|
||||
} else {
|
||||
path = relativize_path(&worktree_abs_path, &abs_path);
|
||||
}
|
||||
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: path.into(),
|
||||
};
|
||||
let signature = this.symbol_signature(&project_path);
|
||||
let adapter_language = adapter_language.clone();
|
||||
let language = this
|
||||
.languages
|
||||
.language_for_file(&project_path.path, None)
|
||||
.unwrap_or_else(move |_| adapter_language);
|
||||
let language_server_name = adapter.name.clone();
|
||||
Some(async move {
|
||||
let language = language.await;
|
||||
let label = language
|
||||
.label_for_symbol(&lsp_symbol.name, lsp_symbol.kind)
|
||||
.await;
|
||||
|
||||
Symbol {
|
||||
language_server_name,
|
||||
source_worktree_id,
|
||||
path: project_path,
|
||||
label: label.unwrap_or_else(|| {
|
||||
CodeLabel::plain(lsp_symbol.name.clone(), None)
|
||||
}),
|
||||
kind: lsp_symbol.kind,
|
||||
name: lsp_symbol.name,
|
||||
range: range_from_lsp(lsp_symbol.location.range),
|
||||
signature,
|
||||
symbols.extend(lsp_symbols.into_iter().filter_map(
|
||||
|(symbol_name, symbol_kind, symbol_location)| {
|
||||
let abs_path = symbol_location.uri.to_file_path().ok()?;
|
||||
let mut worktree_id = source_worktree_id;
|
||||
let path;
|
||||
if let Some((worktree, rel_path)) =
|
||||
this.find_local_worktree(&abs_path, cx)
|
||||
{
|
||||
worktree_id = worktree.read(cx).id();
|
||||
path = rel_path;
|
||||
} else {
|
||||
path = relativize_path(&worktree_abs_path, &abs_path);
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: path.into(),
|
||||
};
|
||||
let signature = this.symbol_signature(&project_path);
|
||||
let adapter_language = adapter_language.clone();
|
||||
let language = this
|
||||
.languages
|
||||
.language_for_file(&project_path.path, None)
|
||||
.unwrap_or_else(move |_| adapter_language);
|
||||
let language_server_name = adapter.name.clone();
|
||||
Some(async move {
|
||||
let language = language.await;
|
||||
let label =
|
||||
language.label_for_symbol(&symbol_name, symbol_kind).await;
|
||||
|
||||
Symbol {
|
||||
language_server_name,
|
||||
source_worktree_id,
|
||||
path: project_path,
|
||||
label: label.unwrap_or_else(|| {
|
||||
CodeLabel::plain(symbol_name.clone(), None)
|
||||
}),
|
||||
kind: symbol_kind,
|
||||
name: symbol_name,
|
||||
range: range_from_lsp(symbol_location.range),
|
||||
signature,
|
||||
}
|
||||
})
|
||||
},
|
||||
));
|
||||
}
|
||||
symbols
|
||||
});
|
||||
@@ -4044,6 +4187,109 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_on_type_formatting(
|
||||
&self,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
position: Anchor,
|
||||
trigger: String,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Option<Transaction>>> {
|
||||
if self.is_local() {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
// Do not allow multiple concurrent formatting requests for the
|
||||
// same buffer.
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.buffers_being_formatted
|
||||
.insert(buffer.read(cx).remote_id())
|
||||
});
|
||||
|
||||
let _cleanup = defer({
|
||||
let this = this.clone();
|
||||
let mut cx = cx.clone();
|
||||
let closure_buffer = buffer.clone();
|
||||
move || {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.buffers_being_formatted
|
||||
.remove(&closure_buffer.read(cx).remote_id());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_edits(Some(position.timestamp))
|
||||
})
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
this.on_type_format(buffer, position, trigger, false, cx)
|
||||
})
|
||||
.await
|
||||
})
|
||||
} else if let Some(project_id) = self.remote_id() {
|
||||
let client = self.client.clone();
|
||||
let request = proto::OnTypeFormatting {
|
||||
project_id,
|
||||
buffer_id: buffer.read(cx).remote_id(),
|
||||
position: Some(serialize_anchor(&position)),
|
||||
trigger,
|
||||
version: serialize_version(&buffer.read(cx).version()),
|
||||
};
|
||||
cx.spawn(|_, _| async move {
|
||||
client
|
||||
.request(request)
|
||||
.await?
|
||||
.transaction
|
||||
.map(language::proto::deserialize_transaction)
|
||||
.transpose()
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("project does not have a remote id")))
|
||||
}
|
||||
}
|
||||
|
||||
async fn deserialize_edits(
|
||||
this: ModelHandle<Self>,
|
||||
buffer_to_edit: ModelHandle<Buffer>,
|
||||
edits: Vec<lsp::TextEdit>,
|
||||
push_to_history: bool,
|
||||
_: Arc<CachedLspAdapter>,
|
||||
language_server: Arc<LanguageServer>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Option<Transaction>> {
|
||||
let edits = this
|
||||
.update(cx, |this, cx| {
|
||||
this.edits_from_lsp(
|
||||
&buffer_to_edit,
|
||||
edits,
|
||||
language_server.server_id(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let transaction = buffer_to_edit.update(cx, |buffer, cx| {
|
||||
buffer.finalize_last_transaction();
|
||||
buffer.start_transaction();
|
||||
for (range, text) in edits {
|
||||
buffer.edit([(range, text)], None, cx);
|
||||
}
|
||||
|
||||
if buffer.end_transaction(cx).is_some() {
|
||||
let transaction = buffer.finalize_last_transaction().unwrap().clone();
|
||||
if !push_to_history {
|
||||
buffer.forget_transaction(transaction.id);
|
||||
}
|
||||
Some(transaction)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
Ok(transaction)
|
||||
}
|
||||
|
||||
async fn deserialize_workspace_edit(
|
||||
this: ModelHandle<Self>,
|
||||
edit: lsp::WorkspaceEdit,
|
||||
@@ -4209,6 +4455,31 @@ impl Project {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn on_type_format<T: ToPointUtf16>(
|
||||
&self,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
position: T,
|
||||
trigger: String,
|
||||
push_to_history: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Option<Transaction>>> {
|
||||
let tab_size = buffer.read_with(cx, |buffer, cx| {
|
||||
let language_name = buffer.language().map(|language| language.name());
|
||||
language_settings(language_name.as_deref(), cx).tab_size
|
||||
});
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
self.request_lsp(
|
||||
buffer.clone(),
|
||||
OnTypeFormatting {
|
||||
position,
|
||||
trigger,
|
||||
options: lsp_command::lsp_formatting_options(tab_size.get()).into(),
|
||||
push_to_history,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn search(
|
||||
&self,
|
||||
@@ -4600,7 +4871,7 @@ impl Project {
|
||||
if worktree.read(cx).is_local() {
|
||||
cx.subscribe(worktree, |this, worktree, event, cx| match event {
|
||||
worktree::Event::UpdatedEntries(changes) => {
|
||||
this.update_local_worktree_buffers(&worktree, &changes, cx);
|
||||
this.update_local_worktree_buffers(&worktree, changes, cx);
|
||||
this.update_local_worktree_language_servers(&worktree, changes, cx);
|
||||
}
|
||||
worktree::Event::UpdatedGitRepositories(updated_repos) => {
|
||||
@@ -4634,13 +4905,13 @@ impl Project {
|
||||
fn update_local_worktree_buffers(
|
||||
&mut self,
|
||||
worktree_handle: &ModelHandle<Worktree>,
|
||||
changes: &HashMap<(Arc<Path>, ProjectEntryId), PathChange>,
|
||||
changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let snapshot = worktree_handle.read(cx).snapshot();
|
||||
|
||||
let mut renamed_buffers = Vec::new();
|
||||
for (path, entry_id) in changes.keys() {
|
||||
for (path, entry_id, _) in changes {
|
||||
let worktree_id = worktree_handle.read(cx).id();
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
@@ -4746,7 +5017,7 @@ impl Project {
|
||||
fn update_local_worktree_language_servers(
|
||||
&mut self,
|
||||
worktree_handle: &ModelHandle<Worktree>,
|
||||
changes: &HashMap<(Arc<Path>, ProjectEntryId), PathChange>,
|
||||
changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if changes.is_empty() {
|
||||
@@ -4777,23 +5048,21 @@ impl Project {
|
||||
let params = lsp::DidChangeWatchedFilesParams {
|
||||
changes: changes
|
||||
.iter()
|
||||
.filter_map(|((path, _), change)| {
|
||||
if watched_paths.is_match(&path) {
|
||||
Some(lsp::FileEvent {
|
||||
uri: lsp::Url::from_file_path(abs_path.join(path))
|
||||
.unwrap(),
|
||||
typ: match change {
|
||||
PathChange::Added => lsp::FileChangeType::CREATED,
|
||||
PathChange::Removed => lsp::FileChangeType::DELETED,
|
||||
PathChange::Updated
|
||||
| PathChange::AddedOrUpdated => {
|
||||
lsp::FileChangeType::CHANGED
|
||||
}
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
.filter_map(|(path, _, change)| {
|
||||
if !watched_paths.is_match(&path) {
|
||||
return None;
|
||||
}
|
||||
let typ = match change {
|
||||
PathChange::Loaded => return None,
|
||||
PathChange::Added => lsp::FileChangeType::CREATED,
|
||||
PathChange::Removed => lsp::FileChangeType::DELETED,
|
||||
PathChange::Updated => lsp::FileChangeType::CHANGED,
|
||||
PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED,
|
||||
};
|
||||
Some(lsp::FileEvent {
|
||||
uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(),
|
||||
typ,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
@@ -4812,98 +5081,102 @@ impl Project {
|
||||
fn update_local_worktree_buffers_git_repos(
|
||||
&mut self,
|
||||
worktree_handle: ModelHandle<Worktree>,
|
||||
repos: &HashMap<Arc<Path>, LocalRepositoryEntry>,
|
||||
changed_repos: &UpdatedGitRepositoriesSet,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
debug_assert!(worktree_handle.read(cx).is_local());
|
||||
|
||||
// Setup the pending buffers
|
||||
// Identify the loading buffers whose containing repository that has changed.
|
||||
let future_buffers = self
|
||||
.loading_buffers_by_path
|
||||
.iter()
|
||||
.filter_map(|(path, receiver)| {
|
||||
let path = &path.path;
|
||||
let (work_directory, repo) = repos
|
||||
.iter()
|
||||
.find(|(work_directory, _)| path.starts_with(work_directory))?;
|
||||
|
||||
let repo_relative_path = path.strip_prefix(work_directory).log_err()?;
|
||||
|
||||
.filter_map(|(project_path, receiver)| {
|
||||
if project_path.worktree_id != worktree_handle.read(cx).id() {
|
||||
return None;
|
||||
}
|
||||
let path = &project_path.path;
|
||||
changed_repos.iter().find(|(work_dir, change)| {
|
||||
path.starts_with(work_dir) && change.git_dir_changed
|
||||
})?;
|
||||
let receiver = receiver.clone();
|
||||
let repo_ptr = repo.repo_ptr.clone();
|
||||
let repo_relative_path = repo_relative_path.to_owned();
|
||||
let path = path.clone();
|
||||
Some(async move {
|
||||
pump_loading_buffer_reciever(receiver)
|
||||
wait_for_loading_buffer(receiver)
|
||||
.await
|
||||
.ok()
|
||||
.map(|buffer| (buffer, repo_relative_path, repo_ptr))
|
||||
.map(|buffer| (buffer, path))
|
||||
})
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.filter_map(|result| async move {
|
||||
let (buffer_handle, repo_relative_path, repo_ptr) = result?;
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
let lock = repo_ptr.lock();
|
||||
lock.load_index_text(&repo_relative_path)
|
||||
.map(|diff_base| (diff_base, buffer_handle))
|
||||
});
|
||||
// Identify the current buffers whose containing repository has changed.
|
||||
let current_buffers = self
|
||||
.opened_buffers
|
||||
.values()
|
||||
.filter_map(|buffer| {
|
||||
let buffer = buffer.upgrade(cx)?;
|
||||
let file = File::from_dyn(buffer.read(cx).file())?;
|
||||
if file.worktree != worktree_handle {
|
||||
return None;
|
||||
}
|
||||
let path = file.path();
|
||||
changed_repos.iter().find(|(work_dir, change)| {
|
||||
path.starts_with(work_dir) && change.git_dir_changed
|
||||
})?;
|
||||
Some((buffer, path.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let update_diff_base_fn = update_diff_base(self);
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let diff_base_tasks = cx
|
||||
if future_buffers.len() + current_buffers.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let remote_id = self.remote_id();
|
||||
let client = self.client.clone();
|
||||
cx.spawn_weak(move |_, mut cx| async move {
|
||||
// Wait for all of the buffers to load.
|
||||
let future_buffers = future_buffers.collect::<Vec<_>>().await;
|
||||
|
||||
// Reload the diff base for every buffer whose containing git repository has changed.
|
||||
let snapshot =
|
||||
worktree_handle.read_with(&cx, |tree, _| tree.as_local().unwrap().snapshot());
|
||||
let diff_bases_by_buffer = cx
|
||||
.background()
|
||||
.spawn(future_buffers.collect::<Vec<_>>())
|
||||
.spawn(async move {
|
||||
future_buffers
|
||||
.into_iter()
|
||||
.filter_map(|e| e)
|
||||
.chain(current_buffers)
|
||||
.filter_map(|(buffer, path)| {
|
||||
let (work_directory, repo) =
|
||||
snapshot.repository_and_work_directory_for_path(&path)?;
|
||||
let repo = snapshot.get_local_repo(&repo)?;
|
||||
let relative_path = path.strip_prefix(&work_directory).ok()?;
|
||||
let base_text = repo.repo_ptr.lock().load_index_text(&relative_path);
|
||||
Some((buffer, base_text))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.await;
|
||||
|
||||
for (diff_base, buffer) in diff_base_tasks.into_iter() {
|
||||
update_diff_base_fn(Some(diff_base), buffer, &mut cx);
|
||||
// Assign the new diff bases on all of the buffers.
|
||||
for (buffer, diff_base) in diff_bases_by_buffer {
|
||||
let buffer_id = buffer.update(&mut cx, |buffer, cx| {
|
||||
buffer.set_diff_base(diff_base.clone(), cx);
|
||||
buffer.remote_id()
|
||||
});
|
||||
if let Some(project_id) = remote_id {
|
||||
client
|
||||
.send(proto::UpdateDiffBase {
|
||||
project_id,
|
||||
buffer_id,
|
||||
diff_base,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// And the current buffers
|
||||
for (_, buffer) in &self.opened_buffers {
|
||||
if let Some(buffer) = buffer.upgrade(cx) {
|
||||
let file = match File::from_dyn(buffer.read(cx).file()) {
|
||||
Some(file) => file,
|
||||
None => continue,
|
||||
};
|
||||
if file.worktree != worktree_handle {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = file.path().clone();
|
||||
|
||||
let worktree = worktree_handle.read(cx);
|
||||
|
||||
let (work_directory, repo) = match repos
|
||||
.iter()
|
||||
.find(|(work_directory, _)| path.starts_with(work_directory))
|
||||
{
|
||||
Some(repo) => repo.clone(),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let relative_repo = match path.strip_prefix(work_directory).log_err() {
|
||||
Some(relative_repo) => relative_repo.to_owned(),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
drop(worktree);
|
||||
|
||||
let update_diff_base_fn = update_diff_base(self);
|
||||
let git_ptr = repo.repo_ptr.clone();
|
||||
let diff_base_task = cx
|
||||
.background()
|
||||
.spawn(async move { git_ptr.lock().load_index_text(&relative_repo) });
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let diff_base = diff_base_task.await;
|
||||
update_diff_base_fn(diff_base, buffer, &mut cx);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
|
||||
@@ -4991,6 +5264,20 @@ impl Project {
|
||||
Some(ProjectPath { worktree_id, path })
|
||||
}
|
||||
|
||||
pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
|
||||
let workspace_root = self
|
||||
.worktree_for_id(project_path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.abs_path();
|
||||
let project_path = project_path.path.as_ref();
|
||||
|
||||
Some(if project_path == Path::new("") {
|
||||
workspace_root.to_path_buf()
|
||||
} else {
|
||||
workspace_root.join(project_path)
|
||||
})
|
||||
}
|
||||
|
||||
// RPC message handlers
|
||||
|
||||
async fn handle_unshare_project(
|
||||
@@ -5601,7 +5888,7 @@ impl Project {
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let Some(guest_id) = envelope.original_sender_id else {
|
||||
log::error!("missing original_sender_id on SynchronizeBuffers request");
|
||||
error!("missing original_sender_id on SynchronizeBuffers request");
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -5779,6 +6066,38 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_on_type_formatting(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::OnTypeFormatting>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::OnTypeFormattingResponse> {
|
||||
let on_type_formatting = this.update(&mut cx, |this, cx| {
|
||||
let buffer = this
|
||||
.opened_buffers
|
||||
.get(&envelope.payload.buffer_id)
|
||||
.and_then(|buffer| buffer.upgrade(cx))
|
||||
.ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
|
||||
let position = envelope
|
||||
.payload
|
||||
.position
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid position"))?;
|
||||
Ok::<_, anyhow::Error>(this.apply_on_type_formatting(
|
||||
buffer,
|
||||
position,
|
||||
envelope.payload.trigger.clone(),
|
||||
cx,
|
||||
))
|
||||
})?;
|
||||
|
||||
let transaction = on_type_formatting
|
||||
.await?
|
||||
.as_ref()
|
||||
.map(language::proto::serialize_transaction);
|
||||
Ok(proto::OnTypeFormattingResponse { transaction })
|
||||
}
|
||||
|
||||
async fn handle_lsp_command<T: LspCommand>(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<T::ProtoRequest>,
|
||||
@@ -6038,11 +6357,13 @@ impl Project {
|
||||
let Some(this) = this.upgrade(&cx) else {
|
||||
return Err(anyhow!("project dropped"));
|
||||
};
|
||||
|
||||
let buffer = this.read_with(&cx, |this, cx| {
|
||||
this.opened_buffers
|
||||
.get(&id)
|
||||
.and_then(|buffer| buffer.upgrade(cx))
|
||||
});
|
||||
|
||||
if let Some(buffer) = buffer {
|
||||
break buffer;
|
||||
} else if this.read_with(&cx, |this, _| this.is_read_only()) {
|
||||
@@ -6053,12 +6374,13 @@ impl Project {
|
||||
this.incomplete_remote_buffers.entry(id).or_default();
|
||||
});
|
||||
drop(this);
|
||||
|
||||
opened_buffer_rx
|
||||
.next()
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?;
|
||||
};
|
||||
buffer.update(&mut cx, |buffer, cx| buffer.git_diff_recalc(cx));
|
||||
|
||||
Ok(buffer)
|
||||
})
|
||||
}
|
||||
@@ -6790,7 +7112,7 @@ impl Item for Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
async fn pump_loading_buffer_reciever(
|
||||
async fn wait_for_loading_buffer(
|
||||
mut receiver: postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
|
||||
) -> Result<ModelHandle<Buffer>, Arc<anyhow::Error>> {
|
||||
loop {
|
||||
@@ -6803,26 +7125,3 @@ async fn pump_loading_buffer_reciever(
|
||||
receiver.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_diff_base(
|
||||
project: &Project,
|
||||
) -> impl Fn(Option<String>, ModelHandle<Buffer>, &mut AsyncAppContext) {
|
||||
let remote_id = project.remote_id();
|
||||
let client = project.client().clone();
|
||||
move |diff_base, buffer, cx| {
|
||||
let buffer_id = buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(diff_base.clone(), cx);
|
||||
buffer.remote_id()
|
||||
});
|
||||
|
||||
if let Some(project_id) = remote_id {
|
||||
client
|
||||
.send(proto::UpdateDiffBase {
|
||||
project_id,
|
||||
buffer_id: buffer_id as u64,
|
||||
diff_base,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,22 @@ use std::sync::Arc;
|
||||
pub struct ProjectSettings {
|
||||
#[serde(default)]
|
||||
pub lsp: HashMap<Arc<str>, LspSettings>,
|
||||
#[serde(default)]
|
||||
pub git: GitSettings,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GitSettings {
|
||||
pub git_gutter: Option<GitGutterSetting>,
|
||||
pub gutter_debounce: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GitGutterSetting {
|
||||
#[default]
|
||||
TrackedFiles,
|
||||
Hide,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
|
||||
@@ -506,7 +506,9 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||
register_options: serde_json::to_value(
|
||||
lsp::DidChangeWatchedFilesRegistrationOptions {
|
||||
watchers: vec![lsp::FileSystemWatcher {
|
||||
glob_pattern: "/the-root/*.{rs,c}".to_string(),
|
||||
glob_pattern: lsp::GlobPattern::String(
|
||||
"/the-root/*.{rs,c}".to_string(),
|
||||
),
|
||||
kind: None,
|
||||
}],
|
||||
},
|
||||
@@ -1193,7 +1195,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
#[gpui::test(iterations = 3)]
|
||||
async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -1273,6 +1275,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
// The diagnostics have moved down since they were created.
|
||||
buffer.next_notification(cx).await;
|
||||
cx.foreground().run_until_parked();
|
||||
buffer.read_with(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer
|
||||
@@ -1351,6 +1354,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
|
||||
buffer.next_notification(cx).await;
|
||||
cx.foreground().run_until_parked();
|
||||
buffer.read_with(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer
|
||||
@@ -1443,6 +1447,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
});
|
||||
|
||||
buffer.next_notification(cx).await;
|
||||
cx.foreground().run_until_parked();
|
||||
buffer.read_with(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer
|
||||
@@ -2523,29 +2528,21 @@ async fn test_rescan_and_remote_updates(
|
||||
|
||||
// Create a remote copy of this worktree.
|
||||
let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
let initial_snapshot = tree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
||||
let remote = cx.update(|cx| {
|
||||
Worktree::remote(
|
||||
1,
|
||||
1,
|
||||
proto::WorktreeMetadata {
|
||||
id: initial_snapshot.id().to_proto(),
|
||||
root_name: initial_snapshot.root_name().into(),
|
||||
abs_path: initial_snapshot
|
||||
.abs_path()
|
||||
.as_os_str()
|
||||
.to_string_lossy()
|
||||
.into(),
|
||||
visible: true,
|
||||
},
|
||||
rpc.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
remote.update(cx, |remote, _| {
|
||||
let update = initial_snapshot.build_initial_update(1);
|
||||
remote.as_remote_mut().unwrap().update_from_remote(update);
|
||||
|
||||
let metadata = tree.read_with(cx, |tree, _| tree.as_local().unwrap().metadata_proto());
|
||||
|
||||
let updates = Arc::new(Mutex::new(Vec::new()));
|
||||
tree.update(cx, |tree, cx| {
|
||||
let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
|
||||
let updates = updates.clone();
|
||||
move |update| {
|
||||
updates.lock().push(update);
|
||||
async { true }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx));
|
||||
deterministic.run_until_parked();
|
||||
|
||||
cx.read(|cx| {
|
||||
@@ -2611,14 +2608,11 @@ async fn test_rescan_and_remote_updates(
|
||||
|
||||
// Update the remote worktree. Check that it becomes consistent with the
|
||||
// local worktree.
|
||||
remote.update(cx, |remote, cx| {
|
||||
let update = tree.read(cx).as_local().unwrap().snapshot().build_update(
|
||||
&initial_snapshot,
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
);
|
||||
remote.as_remote_mut().unwrap().update_from_remote(update);
|
||||
deterministic.run_until_parked();
|
||||
remote.update(cx, |remote, _| {
|
||||
for update in updates.lock().drain(..) {
|
||||
remote.as_remote_mut().unwrap().update_from_remote(update);
|
||||
}
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
remote.read_with(cx, |remote, _| {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -527,18 +527,22 @@ impl ProjectPanel {
|
||||
let entry = worktree.read(cx).entry_for_id(edit_state.entry_id)?.clone();
|
||||
let filename = self.filename_editor.read(cx).text(cx);
|
||||
|
||||
let path_already_exists = |path| worktree.read(cx).entry_for_path(path).is_some();
|
||||
let edit_task;
|
||||
let edited_entry_id;
|
||||
|
||||
if is_new_entry {
|
||||
self.selection = Some(Selection {
|
||||
worktree_id,
|
||||
entry_id: NEW_ENTRY_ID,
|
||||
});
|
||||
let new_path = entry.path.join(&filename);
|
||||
if path_already_exists(new_path.as_path()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
edited_entry_id = NEW_ENTRY_ID;
|
||||
edit_task = self.project.update(cx, |project, cx| {
|
||||
project.create_entry((worktree_id, new_path), is_dir, cx)
|
||||
project.create_entry((worktree_id, &new_path), is_dir, cx)
|
||||
})?;
|
||||
} else {
|
||||
let new_path = if let Some(parent) = entry.path.clone().parent() {
|
||||
@@ -546,9 +550,13 @@ impl ProjectPanel {
|
||||
} else {
|
||||
filename.clone().into()
|
||||
};
|
||||
if path_already_exists(new_path.as_path()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
edited_entry_id = entry.id;
|
||||
edit_task = self.project.update(cx, |project, cx| {
|
||||
project.rename_entry(entry.id, new_path, cx)
|
||||
project.rename_entry(entry.id, new_path.as_path(), cx)
|
||||
})?;
|
||||
};
|
||||
|
||||
@@ -2126,6 +2134,152 @@ mod tests {
|
||||
ensure_no_open_items_and_panes(window_id, &workspace, cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_create_duplicate_items(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/src",
|
||||
json!({
|
||||
"test": {
|
||||
"first.rs": "// First Rust file",
|
||||
"second.rs": "// Second Rust file",
|
||||
"third.rs": "// Third Rust file",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.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 panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
|
||||
|
||||
select_path(&panel, "src/", cx);
|
||||
panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&["v src <== selected", " > test"]
|
||||
);
|
||||
panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx));
|
||||
cx.read_window(window_id, |cx| {
|
||||
let panel = panel.read(cx);
|
||||
assert!(panel.filename_editor.is_focused(cx));
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&["v src", " > [EDITOR: ''] <== selected", " > test"]
|
||||
);
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.filename_editor
|
||||
.update(cx, |editor, cx| editor.set_text("test", cx));
|
||||
assert!(
|
||||
panel.confirm(&Confirm, cx).is_none(),
|
||||
"Should not allow to confirm on conflicting new directory name"
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&["v src", " > test"],
|
||||
"File list should be unchanged after failed folder create confirmation"
|
||||
);
|
||||
|
||||
select_path(&panel, "src/test/", cx);
|
||||
panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&["v src", " > test <== selected"]
|
||||
);
|
||||
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
|
||||
cx.read_window(window_id, |cx| {
|
||||
let panel = panel.read(cx);
|
||||
assert!(panel.filename_editor.is_focused(cx));
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v src",
|
||||
" v test",
|
||||
" [EDITOR: ''] <== selected",
|
||||
" first.rs",
|
||||
" second.rs",
|
||||
" third.rs"
|
||||
]
|
||||
);
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.filename_editor
|
||||
.update(cx, |editor, cx| editor.set_text("first.rs", cx));
|
||||
assert!(
|
||||
panel.confirm(&Confirm, cx).is_none(),
|
||||
"Should not allow to confirm on conflicting new file name"
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v src",
|
||||
" v test",
|
||||
" first.rs",
|
||||
" second.rs",
|
||||
" third.rs"
|
||||
],
|
||||
"File list should be unchanged after failed file create confirmation"
|
||||
);
|
||||
|
||||
select_path(&panel, "src/test/first.rs", cx);
|
||||
panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v src",
|
||||
" v test",
|
||||
" first.rs <== selected",
|
||||
" second.rs",
|
||||
" third.rs"
|
||||
],
|
||||
);
|
||||
panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
|
||||
cx.read_window(window_id, |cx| {
|
||||
let panel = panel.read(cx);
|
||||
assert!(panel.filename_editor.is_focused(cx));
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v src",
|
||||
" v test",
|
||||
" [EDITOR: 'first.rs'] <== selected",
|
||||
" second.rs",
|
||||
" third.rs"
|
||||
]
|
||||
);
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.filename_editor
|
||||
.update(cx, |editor, cx| editor.set_text("second.rs", cx));
|
||||
assert!(
|
||||
panel.confirm(&Confirm, cx).is_none(),
|
||||
"Should not allow to confirm on conflicting file rename"
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v src",
|
||||
" v test",
|
||||
" first.rs <== selected",
|
||||
" second.rs",
|
||||
" third.rs"
|
||||
],
|
||||
"File list should be unchanged after failed rename confirmation"
|
||||
);
|
||||
}
|
||||
|
||||
fn toggle_expand_dir(
|
||||
panel: &ViewHandle<ProjectPanel>,
|
||||
path: impl AsRef<Path>,
|
||||
@@ -2229,6 +2383,7 @@ mod tests {
|
||||
editor::init_settings(cx);
|
||||
crate::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
Project::init_settings(cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2243,6 +2398,7 @@ mod tests {
|
||||
pane::init(cx);
|
||||
crate::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
Project::init_settings(cx);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ mod tests {
|
||||
symbol("uno", "/dir/test.rs"),
|
||||
];
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
fake_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(
|
||||
fake_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(
|
||||
move |params: lsp::WorkspaceSymbolParams, cx| {
|
||||
let executor = cx.background();
|
||||
let fake_symbols = fake_symbols.clone();
|
||||
@@ -308,12 +308,12 @@ mod tests {
|
||||
.await
|
||||
};
|
||||
|
||||
Ok(Some(
|
||||
Ok(Some(lsp::WorkspaceSymbolResponse::Flat(
|
||||
matches
|
||||
.into_iter()
|
||||
.map(|mat| fake_symbols[mat.candidate_id].clone())
|
||||
.collect(),
|
||||
))
|
||||
)))
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -129,6 +129,9 @@ message Envelope {
|
||||
GetPrivateUserInfo get_private_user_info = 105;
|
||||
GetPrivateUserInfoResponse get_private_user_info_response = 106;
|
||||
UpdateDiffBase update_diff_base = 107;
|
||||
|
||||
OnTypeFormatting on_type_formatting = 111;
|
||||
OnTypeFormattingResponse on_type_formatting_response = 112;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,6 +673,18 @@ message PerformRename {
|
||||
repeated VectorClockEntry version = 5;
|
||||
}
|
||||
|
||||
message OnTypeFormatting {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
Anchor position = 3;
|
||||
string trigger = 4;
|
||||
repeated VectorClockEntry version = 5;
|
||||
}
|
||||
|
||||
message OnTypeFormattingResponse {
|
||||
Transaction transaction = 1;
|
||||
}
|
||||
|
||||
message PerformRenameResponse {
|
||||
ProjectTransaction transaction = 2;
|
||||
}
|
||||
|
||||
@@ -195,6 +195,8 @@ messages!(
|
||||
(OpenBufferResponse, Background),
|
||||
(PerformRename, Background),
|
||||
(PerformRenameResponse, Background),
|
||||
(OnTypeFormatting, Background),
|
||||
(OnTypeFormattingResponse, Background),
|
||||
(Ping, Foreground),
|
||||
(PrepareRename, Background),
|
||||
(PrepareRenameResponse, Background),
|
||||
@@ -279,6 +281,7 @@ request_messages!(
|
||||
(Ping, Ack),
|
||||
(PerformRename, PerformRenameResponse),
|
||||
(PrepareRename, PrepareRenameResponse),
|
||||
(OnTypeFormatting, OnTypeFormattingResponse),
|
||||
(ReloadBuffers, ReloadBuffersResponse),
|
||||
(RequestContact, Ack),
|
||||
(RemoveContact, Ack),
|
||||
@@ -323,6 +326,7 @@ entity_messages!(
|
||||
OpenBufferByPath,
|
||||
OpenBufferForSymbol,
|
||||
PerformRename,
|
||||
OnTypeFormatting,
|
||||
PrepareRename,
|
||||
ReloadBuffers,
|
||||
RemoveProjectCollaborator,
|
||||
|
||||
@@ -6,4 +6,4 @@ pub use conn::Connection;
|
||||
pub use peer::*;
|
||||
mod macros;
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 55;
|
||||
pub const PROTOCOL_VERSION: u32 = 56;
|
||||
|
||||
@@ -360,15 +360,6 @@ impl Item for ProjectSearchView {
|
||||
.update(cx, |editor, cx| editor.navigate(data, cx))
|
||||
}
|
||||
|
||||
fn git_diff_recalc(
|
||||
&mut self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
self.results_editor
|
||||
.update(cx, |editor, cx| editor.git_diff_recalc(project, cx))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
|
||||
match event {
|
||||
ViewEvent::UpdateTab => {
|
||||
|
||||
@@ -55,15 +55,22 @@ pub fn watch_config_file(
|
||||
.spawn(async move {
|
||||
let events = fs.watch(&path, Duration::from_millis(100)).await;
|
||||
futures::pin_mut!(events);
|
||||
|
||||
let contents = fs.load(&path).await.unwrap_or_default();
|
||||
if tx.unbounded_send(contents).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
loop {
|
||||
if events.next().await.is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Ok(contents) = fs.load(&path).await {
|
||||
if !tx.unbounded_send(contents).is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if events.next().await.is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{btree_map, hash_map, BTreeMap, HashMap};
|
||||
use gpui::AppContext;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -84,15 +84,26 @@ pub struct SettingsJsonSchemaParams<'a> {
|
||||
}
|
||||
|
||||
/// A set of strongly-typed setting values defined via multiple JSON files.
|
||||
#[derive(Default)]
|
||||
pub struct SettingsStore {
|
||||
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
|
||||
default_deserialized_settings: Option<serde_json::Value>,
|
||||
user_deserialized_settings: Option<serde_json::Value>,
|
||||
default_deserialized_settings: serde_json::Value,
|
||||
user_deserialized_settings: serde_json::Value,
|
||||
local_deserialized_settings: BTreeMap<Arc<Path>, serde_json::Value>,
|
||||
tab_size_callback: Option<(TypeId, Box<dyn Fn(&dyn Any) -> Option<usize>>)>,
|
||||
}
|
||||
|
||||
impl Default for SettingsStore {
|
||||
fn default() -> Self {
|
||||
SettingsStore {
|
||||
setting_values: Default::default(),
|
||||
default_deserialized_settings: serde_json::json!({}),
|
||||
user_deserialized_settings: serde_json::json!({}),
|
||||
local_deserialized_settings: Default::default(),
|
||||
tab_size_callback: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SettingValue<T> {
|
||||
global_value: Option<T>,
|
||||
@@ -136,27 +147,24 @@ impl SettingsStore {
|
||||
local_values: Vec::new(),
|
||||
}));
|
||||
|
||||
if let Some(default_settings) = &self.default_deserialized_settings {
|
||||
if let Some(default_settings) = setting_value
|
||||
.deserialize_setting(default_settings)
|
||||
if let Some(default_settings) = setting_value
|
||||
.deserialize_setting(&self.default_deserialized_settings)
|
||||
.log_err()
|
||||
{
|
||||
let mut user_values_stack = Vec::new();
|
||||
|
||||
if let Some(user_settings) = setting_value
|
||||
.deserialize_setting(&self.user_deserialized_settings)
|
||||
.log_err()
|
||||
{
|
||||
let mut user_values_stack = Vec::new();
|
||||
user_values_stack = vec![user_settings];
|
||||
}
|
||||
|
||||
if let Some(user_settings) = &self.user_deserialized_settings {
|
||||
if let Some(user_settings) =
|
||||
setting_value.deserialize_setting(user_settings).log_err()
|
||||
{
|
||||
user_values_stack = vec![user_settings];
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(setting) = setting_value
|
||||
.load_setting(&default_settings, &user_values_stack, cx)
|
||||
.log_err()
|
||||
{
|
||||
setting_value.set_global_value(setting);
|
||||
}
|
||||
if let Some(setting) = setting_value
|
||||
.load_setting(&default_settings, &user_values_stack, cx)
|
||||
.log_err()
|
||||
{
|
||||
setting_value.set_global_value(setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,9 +197,7 @@ impl SettingsStore {
|
||||
/// This is only for debugging and reporting. For user-facing functionality,
|
||||
/// use the typed setting interface.
|
||||
pub fn untyped_user_settings(&self) -> &serde_json::Value {
|
||||
self.user_deserialized_settings
|
||||
.as_ref()
|
||||
.unwrap_or(&serde_json::Value::Null)
|
||||
&self.user_deserialized_settings
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -213,11 +219,7 @@ impl SettingsStore {
|
||||
cx: &AppContext,
|
||||
update: impl FnOnce(&mut T::FileContent),
|
||||
) {
|
||||
if self.user_deserialized_settings.is_none() {
|
||||
self.set_user_settings("{}", cx).unwrap();
|
||||
}
|
||||
let old_text =
|
||||
serde_json::to_string(self.user_deserialized_settings.as_ref().unwrap()).unwrap();
|
||||
let old_text = serde_json::to_string(&self.user_deserialized_settings).unwrap();
|
||||
let new_text = self.new_text_for_update::<T>(old_text, update);
|
||||
self.set_user_settings(&new_text, cx).unwrap();
|
||||
}
|
||||
@@ -250,11 +252,7 @@ impl SettingsStore {
|
||||
.setting_values
|
||||
.get(&setting_type_id)
|
||||
.unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
|
||||
.deserialize_setting(
|
||||
self.user_deserialized_settings
|
||||
.as_ref()
|
||||
.expect("no user settings loaded"),
|
||||
)
|
||||
.deserialize_setting(&self.user_deserialized_settings)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"could not deserialize setting type {} from user settings: {}",
|
||||
@@ -323,10 +321,14 @@ impl SettingsStore {
|
||||
default_settings_content: &str,
|
||||
cx: &AppContext,
|
||||
) -> Result<()> {
|
||||
self.default_deserialized_settings =
|
||||
Some(parse_json_with_comments(default_settings_content)?);
|
||||
self.recompute_values(None, cx)?;
|
||||
Ok(())
|
||||
let settings: serde_json::Value = parse_json_with_comments(default_settings_content)?;
|
||||
if settings.is_object() {
|
||||
self.default_deserialized_settings = settings;
|
||||
self.recompute_values(None, cx)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("settings must be an object"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the user settings via a JSON string.
|
||||
@@ -335,9 +337,14 @@ impl SettingsStore {
|
||||
user_settings_content: &str,
|
||||
cx: &AppContext,
|
||||
) -> Result<()> {
|
||||
self.user_deserialized_settings = Some(parse_json_with_comments(user_settings_content)?);
|
||||
self.recompute_values(None, cx)?;
|
||||
Ok(())
|
||||
let settings: serde_json::Value = parse_json_with_comments(user_settings_content)?;
|
||||
if settings.is_object() {
|
||||
self.user_deserialized_settings = settings;
|
||||
self.recompute_values(None, cx)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("settings must be an object"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Add or remove a set of local settings via a JSON string.
|
||||
@@ -443,65 +450,63 @@ impl SettingsStore {
|
||||
let mut user_settings_stack = Vec::<DeserializedSetting>::new();
|
||||
let mut paths_stack = Vec::<Option<&Path>>::new();
|
||||
for setting_value in self.setting_values.values_mut() {
|
||||
if let Some(default_settings) = &self.default_deserialized_settings {
|
||||
let default_settings = setting_value.deserialize_setting(default_settings)?;
|
||||
let default_settings =
|
||||
setting_value.deserialize_setting(&self.default_deserialized_settings)?;
|
||||
|
||||
user_settings_stack.clear();
|
||||
paths_stack.clear();
|
||||
user_settings_stack.clear();
|
||||
paths_stack.clear();
|
||||
|
||||
if let Some(user_settings) = &self.user_deserialized_settings {
|
||||
if let Some(user_settings) =
|
||||
setting_value.deserialize_setting(user_settings).log_err()
|
||||
{
|
||||
user_settings_stack.push(user_settings);
|
||||
paths_stack.push(None);
|
||||
if let Some(user_settings) = setting_value
|
||||
.deserialize_setting(&self.user_deserialized_settings)
|
||||
.log_err()
|
||||
{
|
||||
user_settings_stack.push(user_settings);
|
||||
paths_stack.push(None);
|
||||
}
|
||||
|
||||
// If the global settings file changed, reload the global value for the field.
|
||||
if changed_local_path.is_none() {
|
||||
if let Some(value) = setting_value
|
||||
.load_setting(&default_settings, &user_settings_stack, cx)
|
||||
.log_err()
|
||||
{
|
||||
setting_value.set_global_value(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Reload the local values for the setting.
|
||||
for (path, local_settings) in &self.local_deserialized_settings {
|
||||
// Build a stack of all of the local values for that setting.
|
||||
while let Some(prev_path) = paths_stack.last() {
|
||||
if let Some(prev_path) = prev_path {
|
||||
if !path.starts_with(prev_path) {
|
||||
paths_stack.pop();
|
||||
user_settings_stack.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If the global settings file changed, reload the global value for the field.
|
||||
if changed_local_path.is_none() {
|
||||
if let Some(local_settings) =
|
||||
setting_value.deserialize_setting(&local_settings).log_err()
|
||||
{
|
||||
paths_stack.push(Some(path.as_ref()));
|
||||
user_settings_stack.push(local_settings);
|
||||
|
||||
// If a local settings file changed, then avoid recomputing local
|
||||
// settings for any path outside of that directory.
|
||||
if changed_local_path.map_or(false, |changed_local_path| {
|
||||
!path.starts_with(changed_local_path)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(value) = setting_value
|
||||
.load_setting(&default_settings, &user_settings_stack, cx)
|
||||
.log_err()
|
||||
{
|
||||
setting_value.set_global_value(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Reload the local values for the setting.
|
||||
for (path, local_settings) in &self.local_deserialized_settings {
|
||||
// Build a stack of all of the local values for that setting.
|
||||
while let Some(prev_path) = paths_stack.last() {
|
||||
if let Some(prev_path) = prev_path {
|
||||
if !path.starts_with(prev_path) {
|
||||
paths_stack.pop();
|
||||
user_settings_stack.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(local_settings) =
|
||||
setting_value.deserialize_setting(&local_settings).log_err()
|
||||
{
|
||||
paths_stack.push(Some(path.as_ref()));
|
||||
user_settings_stack.push(local_settings);
|
||||
|
||||
// If a local settings file changed, then avoid recomputing local
|
||||
// settings for any path outside of that directory.
|
||||
if changed_local_path.map_or(false, |changed_local_path| {
|
||||
!path.starts_with(changed_local_path)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(value) = setting_value
|
||||
.load_setting(&default_settings, &user_settings_stack, cx)
|
||||
.log_err()
|
||||
{
|
||||
setting_value.set_local_value(path.clone(), value);
|
||||
}
|
||||
setting_value.set_local_value(path.clone(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ impl TerminalPanel {
|
||||
let window_id = cx.window_id();
|
||||
let mut pane = Pane::new(
|
||||
workspace.weak_handle(),
|
||||
workspace.project().clone(),
|
||||
workspace.app_state().background_actions,
|
||||
Default::default(),
|
||||
cx,
|
||||
@@ -178,8 +179,9 @@ impl TerminalPanel {
|
||||
(panel, pane, items)
|
||||
})?;
|
||||
|
||||
let pane = pane.downgrade();
|
||||
let items = futures::future::join_all(items).await;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
let active_item_id = serialized_panel
|
||||
.as_ref()
|
||||
.and_then(|panel| panel.active_item_id);
|
||||
@@ -187,17 +189,15 @@ impl TerminalPanel {
|
||||
for item in items {
|
||||
if let Some(item) = item.log_err() {
|
||||
let item_id = item.id();
|
||||
Pane::add_item(workspace, &pane, Box::new(item), false, false, None, cx);
|
||||
pane.add_item(Box::new(item), false, false, None, cx);
|
||||
if Some(item_id) == active_item_id {
|
||||
active_ix = Some(pane.read(cx).items_len() - 1);
|
||||
active_ix = Some(pane.items_len() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_ix) = active_ix {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(active_ix, false, false, cx)
|
||||
});
|
||||
pane.activate_item(active_ix, false, false, cx)
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -254,8 +254,10 @@ impl TerminalPanel {
|
||||
Box::new(cx.add_view(|cx| {
|
||||
TerminalView::new(terminal, workspace.database_id(), cx)
|
||||
}));
|
||||
let focus = pane.read(cx).has_focus();
|
||||
Pane::add_item(workspace, &pane, terminal, true, focus, None, cx);
|
||||
pane.update(cx, |pane, cx| {
|
||||
let focus = pane.has_focus();
|
||||
pane.add_item(terminal, true, focus, None, cx);
|
||||
});
|
||||
}
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| this.serialize(cx))?;
|
||||
|
||||
@@ -133,8 +133,8 @@ impl TerminalView {
|
||||
Event::Wakeup => {
|
||||
if !cx.is_self_focused() {
|
||||
this.has_new_content = true;
|
||||
cx.notify();
|
||||
}
|
||||
cx.notify();
|
||||
cx.emit(Event::Wakeup);
|
||||
}
|
||||
Event::Bell => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::{
|
||||
pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction,
|
||||
FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace,
|
||||
WorkspaceId,
|
||||
pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders,
|
||||
ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
||||
};
|
||||
use crate::{AutosaveSetting, WorkspaceSettings};
|
||||
use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
|
||||
use anyhow::Result;
|
||||
use client::{proto, Client};
|
||||
use gpui::{
|
||||
@@ -102,13 +101,6 @@ pub trait Item: View {
|
||||
) -> Task<Result<()>> {
|
||||
unimplemented!("reload() must be implemented if can_save() returns true")
|
||||
}
|
||||
fn git_diff_recalc(
|
||||
&mut self,
|
||||
_project: ModelHandle<Project>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
|
||||
SmallVec::new()
|
||||
}
|
||||
@@ -221,11 +213,6 @@ pub trait ItemHandle: 'static + fmt::Debug {
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<()>>;
|
||||
fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
|
||||
fn git_diff_recalc(
|
||||
&self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<()>>;
|
||||
fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>;
|
||||
fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
|
||||
fn on_release(
|
||||
@@ -381,7 +368,6 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||
.is_none()
|
||||
{
|
||||
let mut pending_autosave = DelayedDebouncedEditAction::new();
|
||||
let mut pending_git_update = DelayedDebouncedEditAction::new();
|
||||
let pending_update = Rc::new(RefCell::new(None));
|
||||
let pending_update_scheduled = Rc::new(AtomicBool::new(false));
|
||||
|
||||
@@ -450,48 +436,14 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||
}
|
||||
|
||||
ItemEvent::Edit => {
|
||||
let settings = settings::get::<WorkspaceSettings>(cx);
|
||||
let debounce_delay = settings.git.gutter_debounce;
|
||||
|
||||
if let AutosaveSetting::AfterDelay { milliseconds } =
|
||||
settings.autosave
|
||||
{
|
||||
let autosave = settings::get::<WorkspaceSettings>(cx).autosave;
|
||||
if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
|
||||
let delay = Duration::from_millis(milliseconds);
|
||||
let item = item.clone();
|
||||
pending_autosave.fire_new(delay, cx, move |workspace, cx| {
|
||||
Pane::autosave_item(&item, workspace.project().clone(), cx)
|
||||
});
|
||||
}
|
||||
|
||||
let item = item.clone();
|
||||
|
||||
if let Some(delay) = debounce_delay {
|
||||
const MIN_GIT_DELAY: u64 = 50;
|
||||
|
||||
let delay = delay.max(MIN_GIT_DELAY);
|
||||
let duration = Duration::from_millis(delay);
|
||||
|
||||
pending_git_update.fire_new(
|
||||
duration,
|
||||
cx,
|
||||
move |workspace, cx| {
|
||||
item.git_diff_recalc(workspace.project().clone(), cx)
|
||||
},
|
||||
);
|
||||
} else {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
item.git_diff_recalc(
|
||||
workspace.project().clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
@@ -576,14 +528,6 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||
self.update(cx, |item, cx| item.reload(project, cx))
|
||||
}
|
||||
|
||||
fn git_diff_recalc(
|
||||
&self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<()>> {
|
||||
self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
|
||||
}
|
||||
|
||||
fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> {
|
||||
self.read(cx).act_as_type(type_id, self, cx)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -183,7 +183,7 @@ pub fn handle_dropped_item<V: View>(
|
||||
.zip(pane.upgrade(cx))
|
||||
{
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
Pane::move_item(workspace, from, to, item_id, index, cx);
|
||||
workspace.move_item(from, to, item_id, index, cx);
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use anyhow::{Context, Result};
|
||||
use async_recursion::async_recursion;
|
||||
use db::sqlez::{
|
||||
bindable::{Bind, Column, StaticColumnCount},
|
||||
@@ -230,7 +230,7 @@ impl SerializedPane {
|
||||
pub async fn deserialize_to(
|
||||
&self,
|
||||
project: &ModelHandle<Project>,
|
||||
pane_handle: &WeakViewHandle<Pane>,
|
||||
pane: &WeakViewHandle<Pane>,
|
||||
workspace_id: WorkspaceId,
|
||||
workspace: &WeakViewHandle<Workspace>,
|
||||
cx: &mut AsyncAppContext,
|
||||
@@ -239,7 +239,7 @@ impl SerializedPane {
|
||||
let mut active_item_index = None;
|
||||
for (index, item) in self.children.iter().enumerate() {
|
||||
let project = project.clone();
|
||||
let item_handle = pane_handle
|
||||
let item_handle = pane
|
||||
.update(cx, |_, cx| {
|
||||
if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) {
|
||||
deserializer(project, workspace.clone(), workspace_id, item.item_id, cx)
|
||||
@@ -256,13 +256,9 @@ impl SerializedPane {
|
||||
items.push(item_handle.clone());
|
||||
|
||||
if let Some(item_handle) = item_handle {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let pane_handle = pane_handle
|
||||
.upgrade(cx)
|
||||
.ok_or_else(|| anyhow!("pane was dropped"))?;
|
||||
Pane::add_item(workspace, &pane_handle, item_handle, true, true, None, cx);
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.add_item(item_handle.clone(), true, true, None, cx);
|
||||
})?;
|
||||
}
|
||||
|
||||
if item.active {
|
||||
@@ -271,7 +267,7 @@ impl SerializedPane {
|
||||
}
|
||||
|
||||
if let Some(active_item_index) = active_item_index {
|
||||
pane_handle.update(cx, |pane, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(active_item_index, false, false, cx);
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ impl Item for SharedScreen {
|
||||
Some(format!("{}'s screen", self.user.github_login).into())
|
||||
}
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(nav_history) = self.nav_history.as_ref() {
|
||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
||||
nav_history.push::<()>(None, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,14 +153,13 @@ impl View for Toolbar {
|
||||
let pane = pane.clone();
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
Pane::go_back(workspace, Some(pane.clone()), cx)
|
||||
.detach_and_log_err(cx);
|
||||
workspace.go_back(pane.clone(), cx).detach_and_log_err(cx);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
super::GoBack { pane: None },
|
||||
super::GoBack,
|
||||
"Go Back",
|
||||
cx,
|
||||
));
|
||||
@@ -182,14 +181,15 @@ impl View for Toolbar {
|
||||
let pane = pane.clone();
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
Pane::go_forward(workspace, Some(pane.clone()), cx)
|
||||
workspace
|
||||
.go_forward(pane.clone(), cx)
|
||||
.detach_and_log_err(cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
super::GoForward { pane: None },
|
||||
super::GoForward,
|
||||
"Go Forward",
|
||||
cx,
|
||||
));
|
||||
|
||||
@@ -260,6 +260,19 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
workspace.toggle_dock(DockPosition::Bottom, cx);
|
||||
});
|
||||
cx.add_action(Workspace::activate_pane_at_index);
|
||||
cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
|
||||
workspace.reopen_closed_item(cx).detach();
|
||||
});
|
||||
cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
|
||||
workspace
|
||||
.go_back(workspace.active_pane().downgrade(), cx)
|
||||
.detach();
|
||||
});
|
||||
cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
|
||||
workspace
|
||||
.go_forward(workspace.active_pane().downgrade(), cx)
|
||||
.detach();
|
||||
});
|
||||
|
||||
cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
@@ -424,7 +437,7 @@ impl DelayedDebouncedEditAction {
|
||||
}
|
||||
}
|
||||
|
||||
fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, f: F)
|
||||
fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
|
||||
where
|
||||
F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
|
||||
{
|
||||
@@ -448,7 +461,7 @@ impl DelayedDebouncedEditAction {
|
||||
}
|
||||
|
||||
if let Some(result) = workspace
|
||||
.update(&mut cx, |workspace, cx| (f)(workspace, cx))
|
||||
.update(&mut cx, |workspace, cx| (func)(workspace, cx))
|
||||
.log_err()
|
||||
{
|
||||
result.await.log_err();
|
||||
@@ -566,6 +579,7 @@ impl Workspace {
|
||||
let center_pane = cx.add_view(|cx| {
|
||||
Pane::new(
|
||||
weak_handle.clone(),
|
||||
project.clone(),
|
||||
app_state.background_actions,
|
||||
pane_history_timestamp.clone(),
|
||||
cx,
|
||||
@@ -996,6 +1010,115 @@ impl Workspace {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn navigate_history(
|
||||
&mut self,
|
||||
pane: WeakViewHandle<Pane>,
|
||||
mode: NavigationMode,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<Result<()>> {
|
||||
let to_load = if let Some(pane) = pane.upgrade(cx) {
|
||||
cx.focus(&pane);
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
loop {
|
||||
// Retrieve the weak item handle from the history.
|
||||
let entry = pane.nav_history_mut().pop(mode, cx)?;
|
||||
|
||||
// If the item is still present in this pane, then activate it.
|
||||
if let Some(index) = entry
|
||||
.item
|
||||
.upgrade(cx)
|
||||
.and_then(|v| pane.index_for_item(v.as_ref()))
|
||||
{
|
||||
let prev_active_item_index = pane.active_item_index();
|
||||
pane.nav_history_mut().set_mode(mode);
|
||||
pane.activate_item(index, true, true, cx);
|
||||
pane.nav_history_mut().set_mode(NavigationMode::Normal);
|
||||
|
||||
let mut navigated = prev_active_item_index != pane.active_item_index();
|
||||
if let Some(data) = entry.data {
|
||||
navigated |= pane.active_item()?.navigate(data, cx);
|
||||
}
|
||||
|
||||
if navigated {
|
||||
break None;
|
||||
}
|
||||
}
|
||||
// If the item is no longer present in this pane, then retrieve its
|
||||
// project path in order to reopen it.
|
||||
else {
|
||||
break pane
|
||||
.nav_history()
|
||||
.path_for_item(entry.item.id())
|
||||
.map(|(project_path, _)| (project_path, entry));
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((project_path, entry)) = to_load {
|
||||
// If the item was no longer present, then load it again from its previous path.
|
||||
let task = self.load_path(project_path, cx);
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let task = task.await;
|
||||
let mut navigated = false;
|
||||
if let Some((project_entry_id, build_item)) = task.log_err() {
|
||||
let prev_active_item_id = pane.update(&mut cx, |pane, _| {
|
||||
pane.nav_history_mut().set_mode(mode);
|
||||
pane.active_item().map(|p| p.id())
|
||||
})?;
|
||||
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
let item = pane.open_item(project_entry_id, true, cx, build_item);
|
||||
navigated |= Some(item.id()) != prev_active_item_id;
|
||||
pane.nav_history_mut().set_mode(NavigationMode::Normal);
|
||||
if let Some(data) = entry.data {
|
||||
navigated |= item.navigate(data, cx);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
if !navigated {
|
||||
workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
Self::navigate_history(workspace, pane, mode, cx)
|
||||
})?
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn go_back(
|
||||
&mut self,
|
||||
pane: WeakViewHandle<Pane>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<Result<()>> {
|
||||
self.navigate_history(pane, NavigationMode::GoingBack, cx)
|
||||
}
|
||||
|
||||
pub fn go_forward(
|
||||
&mut self,
|
||||
pane: WeakViewHandle<Pane>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<Result<()>> {
|
||||
self.navigate_history(pane, NavigationMode::GoingForward, cx)
|
||||
}
|
||||
|
||||
pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
|
||||
self.navigate_history(
|
||||
self.active_pane().downgrade(),
|
||||
NavigationMode::ReopeningClosedItem,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &Client {
|
||||
&self.app_state.client
|
||||
}
|
||||
@@ -1304,22 +1427,6 @@ impl Workspace {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
|
||||
let workspace_root = self
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(project_path.worktree_id, cx)?
|
||||
.read(cx)
|
||||
.abs_path();
|
||||
let project_path = project_path.path.as_ref();
|
||||
|
||||
Some(if project_path == Path::new("") {
|
||||
workspace_root.to_path_buf()
|
||||
} else {
|
||||
workspace_root.join(project_path)
|
||||
})
|
||||
}
|
||||
|
||||
fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
|
||||
let mut paths = cx.prompt_for_paths(PathPromptOptions {
|
||||
files: false,
|
||||
@@ -1634,6 +1741,7 @@ impl Workspace {
|
||||
let pane = cx.add_view(|cx| {
|
||||
Pane::new(
|
||||
self.weak_handle(),
|
||||
self.project.clone(),
|
||||
self.app_state.background_actions,
|
||||
self.pane_history_timestamp.clone(),
|
||||
cx,
|
||||
@@ -1653,7 +1761,7 @@ impl Workspace {
|
||||
) -> bool {
|
||||
if let Some(center_pane) = self.last_active_center_pane.clone() {
|
||||
if let Some(center_pane) = center_pane.upgrade(cx) {
|
||||
Pane::add_item(self, ¢er_pane, item, true, true, None, cx);
|
||||
center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -1664,8 +1772,8 @@ impl Workspace {
|
||||
}
|
||||
|
||||
pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
|
||||
let active_pane = self.active_pane().clone();
|
||||
Pane::add_item(self, &active_pane, item, true, true, None, cx);
|
||||
self.active_pane
|
||||
.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
|
||||
}
|
||||
|
||||
pub fn open_abs_path(
|
||||
@@ -1715,13 +1823,10 @@ impl Workspace {
|
||||
});
|
||||
|
||||
let task = self.load_path(path.into(), cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let (project_entry_id, build_item) = task.await?;
|
||||
let pane = pane
|
||||
.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("pane was closed"))?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
Pane::open_item(this, pane, project_entry_id, focus_item, cx, build_item)
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
pane.open_item(project_entry_id, focus_item, cx, build_item)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1778,8 +1883,9 @@ impl Workspace {
|
||||
|
||||
pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
|
||||
let pane = self.active_pane.clone();
|
||||
Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
|
||||
self.active_pane.update(cx, |pane, cx| {
|
||||
pane.add_item(Box::new(shared_screen), false, true, None, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1864,6 +1970,7 @@ impl Workspace {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
|
||||
pane::Event::Split(direction) => {
|
||||
self.split_pane(pane, *direction, cx);
|
||||
}
|
||||
@@ -1924,7 +2031,7 @@ impl Workspace {
|
||||
let item = pane.read(cx).active_item()?;
|
||||
let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
|
||||
let new_pane = self.add_pane(cx);
|
||||
Pane::add_item(self, &new_pane, clone, true, true, None, cx);
|
||||
new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
|
||||
self.center.split(&pane, &new_pane, direction).unwrap();
|
||||
Some(new_pane)
|
||||
} else {
|
||||
@@ -1946,7 +2053,7 @@ impl Workspace {
|
||||
let Some(from) = from.upgrade(cx) else { return; };
|
||||
|
||||
let new_pane = self.add_pane(cx);
|
||||
Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
|
||||
self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
|
||||
self.center
|
||||
.split(&pane_to_split, &new_pane, split_direction)
|
||||
.unwrap();
|
||||
@@ -1974,6 +2081,41 @@ impl Workspace {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn move_item(
|
||||
&mut self,
|
||||
source: ViewHandle<Pane>,
|
||||
destination: ViewHandle<Pane>,
|
||||
item_id_to_move: usize,
|
||||
destination_index: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let item_to_move = source
|
||||
.read(cx)
|
||||
.items()
|
||||
.enumerate()
|
||||
.find(|(_, item_handle)| item_handle.id() == item_id_to_move);
|
||||
|
||||
if item_to_move.is_none() {
|
||||
log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
|
||||
return;
|
||||
}
|
||||
let (item_ix, item_handle) = item_to_move.unwrap();
|
||||
let item_handle = item_handle.clone();
|
||||
|
||||
if source != destination {
|
||||
// Close item from previous pane
|
||||
source.update(cx, |source, cx| {
|
||||
source.remove_item(item_ix, false, cx);
|
||||
});
|
||||
}
|
||||
|
||||
// This automatically removes duplicate items in the pane
|
||||
destination.update(cx, |destination, cx| {
|
||||
destination.add_item(item_handle, true, true, Some(destination_index), cx);
|
||||
cx.focus_self();
|
||||
});
|
||||
}
|
||||
|
||||
fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
|
||||
if self.center.remove(&pane).unwrap() {
|
||||
self.force_remove_pane(&pane, cx);
|
||||
@@ -2577,7 +2719,9 @@ impl Workspace {
|
||||
if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
|
||||
pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
|
||||
} else {
|
||||
Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.add_item(item.boxed_clone(), false, false, None, cx)
|
||||
});
|
||||
}
|
||||
|
||||
if pane_was_focused {
|
||||
@@ -4177,9 +4321,7 @@ mod tests {
|
||||
});
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
Pane::go_back(workspace, Some(pane.downgrade()), cx)
|
||||
})
|
||||
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ pub struct WorkspaceSettings {
|
||||
pub confirm_quit: bool,
|
||||
pub show_call_status_icon: bool,
|
||||
pub autosave: AutosaveSetting,
|
||||
pub git: GitSettings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -17,7 +16,6 @@ pub struct WorkspaceSettingsContent {
|
||||
pub confirm_quit: Option<bool>,
|
||||
pub show_call_status_icon: Option<bool>,
|
||||
pub autosave: Option<AutosaveSetting>,
|
||||
pub git: Option<GitSettings>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.88.4"
|
||||
version = "0.89.2"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -1 +1 @@
|
||||
stable
|
||||
preview
|
||||
@@ -111,8 +111,8 @@ pub fn menus() -> Vec<Menu<'static>> {
|
||||
Menu {
|
||||
name: "Go",
|
||||
items: vec![
|
||||
MenuItem::action("Back", workspace::GoBack { pane: None }),
|
||||
MenuItem::action("Forward", workspace::GoForward { pane: None }),
|
||||
MenuItem::action("Back", workspace::GoBack),
|
||||
MenuItem::action("Forward", workspace::GoForward),
|
||||
MenuItem::separator(),
|
||||
MenuItem::action("Go to File", file_finder::Toggle),
|
||||
MenuItem::action("Go to Symbol in Project", project_symbols::Toggle),
|
||||
|
||||
@@ -663,7 +663,7 @@ mod tests {
|
||||
use util::http::FakeHttpClient;
|
||||
use workspace::{
|
||||
item::{Item, ItemHandle},
|
||||
open_new, open_paths, pane, NewFile, Pane, SplitDirection, WorkspaceHandle,
|
||||
open_new, open_paths, pane, NewFile, SplitDirection, WorkspaceHandle,
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1488,7 +1488,7 @@ mod tests {
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1497,7 +1497,7 @@ mod tests {
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1506,7 +1506,7 @@ mod tests {
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1515,7 +1515,7 @@ mod tests {
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1525,7 +1525,7 @@ mod tests {
|
||||
|
||||
// Go back one more time and ensure we don't navigate past the first item in the history.
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1534,7 +1534,7 @@ mod tests {
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1543,7 +1543,7 @@ mod tests {
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1561,7 +1561,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1570,7 +1570,7 @@ mod tests {
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1579,7 +1579,7 @@ mod tests {
|
||||
);
|
||||
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1601,7 +1601,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1609,7 +1609,7 @@ mod tests {
|
||||
(file1.clone(), DisplayPoint::new(10, 0), 0.)
|
||||
);
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1653,7 +1653,7 @@ mod tests {
|
||||
})
|
||||
});
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1661,7 +1661,7 @@ mod tests {
|
||||
(file1.clone(), DisplayPoint::new(2, 0), 0.)
|
||||
);
|
||||
workspace
|
||||
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1766,81 +1766,97 @@ mod tests {
|
||||
// Reopen all the closed items, ensuring they are reopened in the same order
|
||||
// in which they were closed.
|
||||
workspace
|
||||
.update(cx, Pane::reopen_closed_item)
|
||||
.update(cx, Workspace::reopen_closed_item)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, Pane::reopen_closed_item)
|
||||
.update(cx, Workspace::reopen_closed_item)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, Pane::reopen_closed_item)
|
||||
.update(cx, Workspace::reopen_closed_item)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, Pane::reopen_closed_item)
|
||||
.update(cx, Workspace::reopen_closed_item)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
|
||||
|
||||
// Reopening past the last closed item is a no-op.
|
||||
workspace
|
||||
.update(cx, Pane::reopen_closed_item)
|
||||
.update(cx, Workspace::reopen_closed_item)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
|
||||
|
||||
// Reopening closed items doesn't interfere with navigation history.
|
||||
workspace
|
||||
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.go_back(workspace.active_pane().downgrade(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
|
||||
@@ -2085,6 +2101,7 @@ mod tests {
|
||||
theme::init((), cx);
|
||||
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
Project::init_settings(cx);
|
||||
language::init(cx);
|
||||
editor::init(cx);
|
||||
project_panel::init_settings(cx);
|
||||
|
||||
63
script/get-changes-since
Executable file
63
script/get-changes-since
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env node --redirect-warnings=/dev/null
|
||||
|
||||
const { execFileSync } = require("child_process");
|
||||
const { GITHUB_ACCESS_TOKEN } = process.env;
|
||||
const PR_REGEX = /#\d+/ // Ex: matches on #4241
|
||||
const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im;
|
||||
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
// Use form of: YYYY-MM-DD - 2023-01-09
|
||||
const startDate = new Date(process.argv[2]);
|
||||
const today = new Date()
|
||||
|
||||
console.log(`Changes from ${startDate} to ${today}\n`);
|
||||
|
||||
let pullRequestNumbers = getPullRequestNumbers(startDate, today);
|
||||
|
||||
// Fetch the pull requests from the GitHub API.
|
||||
console.log("Merged Pull requests:");
|
||||
for (const pullRequestNumber of pullRequestNumbers) {
|
||||
const webURL = `https://github.com/zed-industries/zed/pull/${pullRequestNumber}`;
|
||||
const apiURL = `https://api.github.com/repos/zed-industries/zed/pulls/${pullRequestNumber}`;
|
||||
|
||||
const response = await fetch(apiURL, {
|
||||
headers: {
|
||||
Authorization: `token ${GITHUB_ACCESS_TOKEN}`,
|
||||
},
|
||||
});
|
||||
|
||||
const pullRequest = await response.json();
|
||||
console.log("*", pullRequest.title);
|
||||
console.log(" PR URL: ", webURL);
|
||||
console.log(" Merged: ", pullRequest.merged_at);
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getPullRequestNumbers(startDate, endDate) {
|
||||
const sinceDate = startDate.toISOString();
|
||||
const untilDate = endDate.toISOString();
|
||||
|
||||
const pullRequestNumbers = execFileSync(
|
||||
"git",
|
||||
[
|
||||
"log",
|
||||
`--since=${sinceDate}`,
|
||||
`--until=${untilDate}`,
|
||||
"--oneline"
|
||||
],
|
||||
{ encoding: "utf8" }
|
||||
)
|
||||
.split("\n")
|
||||
.filter(line => line.length > 0)
|
||||
.map(line => {
|
||||
const match = line.match(/#(\d+)/);
|
||||
return match ? match[1] : null;
|
||||
})
|
||||
.filter(line => line);
|
||||
|
||||
return pullRequestNumbers;
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import * as fs from "fs"
|
||||
import toml from "toml"
|
||||
import { schemeMeta } from "./colorSchemes"
|
||||
import { Meta, Verification } from "./themes/common/colorScheme"
|
||||
import https from "https"
|
||||
import crypto from "crypto"
|
||||
import { MetaAndLicense } from "./themes/common/colorScheme"
|
||||
|
||||
const accepted_licenses_file = `${__dirname}/../../script/licenses/zed-licenses.toml`
|
||||
const ACCEPTED_LICENSES_FILE = `${__dirname}/../../script/licenses/zed-licenses.toml`
|
||||
|
||||
// Use the cargo-about configuration file as the source of truth for supported licenses.
|
||||
function parseAcceptedToml(file: string): string[] {
|
||||
@@ -20,8 +18,11 @@ function parseAcceptedToml(file: string): string[] {
|
||||
return obj.accepted
|
||||
}
|
||||
|
||||
function checkLicenses(schemeMeta: Meta[], licenses: string[]) {
|
||||
for (let meta of schemeMeta) {
|
||||
function checkLicenses(
|
||||
schemeMetaWithLicense: MetaAndLicense[],
|
||||
licenses: string[]
|
||||
) {
|
||||
for (const { meta } of schemeMetaWithLicense) {
|
||||
// FIXME: Add support for conjuctions and conditions
|
||||
if (licenses.indexOf(meta.license.SPDX) < 0) {
|
||||
throw Error(
|
||||
@@ -31,62 +32,23 @@ function checkLicenses(schemeMeta: Meta[], licenses: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function getLicenseText(
|
||||
schemeMeta: Meta[],
|
||||
callback: (meta: Meta, license_text: string) => void
|
||||
) {
|
||||
for (let meta of schemeMeta) {
|
||||
if (typeof meta.license.license_text == "string") {
|
||||
callback(meta, meta.license.license_text)
|
||||
} else {
|
||||
let license_text_obj: Verification = meta.license.license_text
|
||||
// The following copied from the example code on nodejs.org:
|
||||
// https://nodejs.org/api/http.html#httpgetoptions-callback
|
||||
https
|
||||
.get(license_text_obj.https_url, (res) => {
|
||||
const { statusCode } = res
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new Error(
|
||||
`Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}`
|
||||
)
|
||||
}
|
||||
|
||||
res.setEncoding("utf8")
|
||||
let rawData = ""
|
||||
res.on("data", (chunk) => {
|
||||
rawData += chunk
|
||||
})
|
||||
res.on("end", () => {
|
||||
const hash = crypto
|
||||
.createHash("sha256")
|
||||
.update(rawData)
|
||||
.digest("hex")
|
||||
if (license_text_obj.license_checksum == hash) {
|
||||
callback(meta, rawData)
|
||||
} else {
|
||||
throw Error(
|
||||
`Checksum for ${meta.name} did not match file downloaded from ${license_text_obj.https_url}`
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
.on("error", (e) => {
|
||||
throw e
|
||||
})
|
||||
}
|
||||
function generateLicenseFile(schemeMetaWithLicense: MetaAndLicense[]) {
|
||||
for (const { meta, licenseFile } of schemeMetaWithLicense) {
|
||||
const licenseText = fs.readFileSync(licenseFile).toString()
|
||||
writeLicense(meta.name, meta.url, licenseText)
|
||||
}
|
||||
}
|
||||
|
||||
function writeLicense(schemeMeta: Meta, text: String) {
|
||||
function writeLicense(
|
||||
themeName: string,
|
||||
themeUrl: string,
|
||||
licenseText: String
|
||||
) {
|
||||
process.stdout.write(
|
||||
`## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n`
|
||||
`## [${themeName}](${themeUrl})\n\n${licenseText}\n********************************************************************************\n\n`
|
||||
)
|
||||
}
|
||||
|
||||
const accepted_licenses = parseAcceptedToml(accepted_licenses_file)
|
||||
checkLicenses(schemeMeta, accepted_licenses)
|
||||
|
||||
getLicenseText(schemeMeta, (meta, text) => {
|
||||
writeLicense(meta, text)
|
||||
})
|
||||
const acceptedLicenses = parseAcceptedToml(ACCEPTED_LICENSES_FILE)
|
||||
checkLicenses(schemeMeta, acceptedLicenses)
|
||||
generateLicenseFile(schemeMeta)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import * as path from "path"
|
||||
import colorSchemes, { staffColorSchemes } from "./colorSchemes"
|
||||
import { colorSchemes, staffColorSchemes } from "./colorSchemes"
|
||||
import app from "./styleTree/app"
|
||||
import { ColorScheme } from "./themes/common/colorScheme"
|
||||
import snakeCase from "./utils/snakeCase"
|
||||
|
||||
@@ -1,54 +1,79 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import { ColorScheme, Meta } from "./themes/common/colorScheme"
|
||||
import { ColorScheme, MetaAndLicense } from "./themes/common/colorScheme"
|
||||
|
||||
const colorSchemes: ColorScheme[] = []
|
||||
export default colorSchemes
|
||||
const THEMES_DIRECTORY = path.resolve(`${__dirname}/themes`)
|
||||
const STAFF_DIRECTORY = path.resolve(`${__dirname}/themes/staff`)
|
||||
const IGNORE_ITEMS = ["staff", "common", "common.ts"]
|
||||
const ACCEPT_EXTENSION = ".ts"
|
||||
const LICENSE_FILE_NAME = "LICENSE"
|
||||
|
||||
const schemeMeta: Meta[] = []
|
||||
export { schemeMeta }
|
||||
function getAllTsFiles(directoryPath: string) {
|
||||
const files = fs.readdirSync(directoryPath)
|
||||
const fileList: string[] = []
|
||||
|
||||
const staffColorSchemes: ColorScheme[] = []
|
||||
export { staffColorSchemes }
|
||||
for (const file of files) {
|
||||
if (!IGNORE_ITEMS.includes(file)) {
|
||||
const filePath = path.join(directoryPath, file)
|
||||
|
||||
const experimentalColorSchemes: ColorScheme[] = []
|
||||
export { experimentalColorSchemes }
|
||||
|
||||
const themes_directory = path.resolve(`${__dirname}/themes`)
|
||||
|
||||
function for_all_color_schemes_in(
|
||||
themesPath: string,
|
||||
callback: (module: any, path: string) => void
|
||||
) {
|
||||
for (const fileName of fs.readdirSync(themesPath)) {
|
||||
if (fileName == "template.ts") continue
|
||||
const filePath = path.join(themesPath, fileName)
|
||||
|
||||
if (fs.statSync(filePath).isFile()) {
|
||||
const colorScheme = require(filePath)
|
||||
callback(colorScheme, path.basename(filePath))
|
||||
if (fs.statSync(filePath).isDirectory()) {
|
||||
fileList.push(...getAllTsFiles(filePath))
|
||||
} else if (path.extname(file) === ACCEPT_EXTENSION) {
|
||||
fileList.push(filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileList
|
||||
}
|
||||
|
||||
function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) {
|
||||
for_all_color_schemes_in(themesPath, (colorScheme, _path) => {
|
||||
function getAllColorSchemes(directoryPath: string) {
|
||||
const files = getAllTsFiles(directoryPath)
|
||||
return files.map((filePath) => ({
|
||||
colorScheme: require(filePath),
|
||||
filePath,
|
||||
fileName: path.basename(filePath),
|
||||
licenseFile: `${path.dirname(filePath)}/${LICENSE_FILE_NAME}`,
|
||||
}))
|
||||
}
|
||||
|
||||
function getColorSchemes(directoryPath: string) {
|
||||
const colorSchemes: ColorScheme[] = []
|
||||
|
||||
for (const { colorScheme } of getAllColorSchemes(directoryPath)) {
|
||||
if (colorScheme.dark) colorSchemes.push(colorScheme.dark)
|
||||
if (colorScheme.light) colorSchemes.push(colorScheme.light)
|
||||
})
|
||||
else if (colorScheme.light) colorSchemes.push(colorScheme.light)
|
||||
}
|
||||
|
||||
return colorSchemes
|
||||
}
|
||||
|
||||
fillColorSchemes(themes_directory, colorSchemes)
|
||||
fillColorSchemes(path.resolve(`${themes_directory}/staff`), staffColorSchemes)
|
||||
function getMetaAndLicense(directoryPath: string) {
|
||||
const meta: MetaAndLicense[] = []
|
||||
|
||||
function fillMeta(themesPath: string, meta: Meta[]) {
|
||||
for_all_color_schemes_in(themesPath, (colorScheme, path) => {
|
||||
if (colorScheme.meta) {
|
||||
meta.push(colorScheme.meta)
|
||||
} else {
|
||||
throw Error(`Public theme ${path} must have a meta field`)
|
||||
for (const { colorScheme, filePath, licenseFile } of getAllColorSchemes(
|
||||
directoryPath
|
||||
)) {
|
||||
const licenseExists = fs.existsSync(licenseFile)
|
||||
if (!licenseExists) {
|
||||
throw Error(
|
||||
`Public theme should have a LICENSE file ${licenseFile}`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
if (!colorScheme.meta) {
|
||||
throw Error(`Public theme ${filePath} must have a meta field`)
|
||||
}
|
||||
|
||||
meta.push({
|
||||
meta: colorScheme.meta,
|
||||
licenseFile,
|
||||
})
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
fillMeta(themes_directory, schemeMeta)
|
||||
export const colorSchemes = getColorSchemes(THEMES_DIRECTORY)
|
||||
export const staffColorSchemes = getColorSchemes(STAFF_DIRECTORY)
|
||||
export const schemeMeta = getMetaAndLicense(THEMES_DIRECTORY)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme"
|
||||
import { background, border, borderColor, foreground, text } from "./components"
|
||||
import hoverPopover from "./hoverPopover"
|
||||
|
||||
import { SyntaxHighlightStyle, buildSyntax } from "../themes/common/syntax"
|
||||
import { buildSyntax } from "../themes/common/syntax"
|
||||
|
||||
export default function editor(colorScheme: ColorScheme) {
|
||||
const { isLight } = colorScheme
|
||||
@@ -103,7 +103,7 @@ export default function editor(colorScheme: ColorScheme) {
|
||||
? colorScheme.ramps.red(0.5).hex()
|
||||
: colorScheme.ramps.red(0.4).hex(),
|
||||
modified: isLight
|
||||
? colorScheme.ramps.yellow(0.3).hex()
|
||||
? colorScheme.ramps.yellow(0.5).hex()
|
||||
: colorScheme.ramps.yellow(0.5).hex(),
|
||||
inserted: isLight
|
||||
? colorScheme.ramps.green(0.4).hex()
|
||||
@@ -244,12 +244,12 @@ export default function editor(colorScheme: ColorScheme) {
|
||||
thumb: {
|
||||
background: withOpacity(background(layer, "inverted"), 0.3),
|
||||
border: {
|
||||
width: 1,
|
||||
color: borderColor(layer, "variant"),
|
||||
top: false,
|
||||
right: true,
|
||||
left: true,
|
||||
bottom: false,
|
||||
width: 1,
|
||||
color: borderColor(layer, "variant"),
|
||||
top: false,
|
||||
right: true,
|
||||
left: true,
|
||||
bottom: false,
|
||||
}
|
||||
},
|
||||
git: {
|
||||
|
||||
21
styles/src/themes/andromeda/LICENSE
Normal file
21
styles/src/themes/andromeda/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 <eliverlara@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,6 +1,6 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
|
||||
const name = "Andromeda"
|
||||
|
||||
@@ -34,12 +34,6 @@ export const meta: Meta = {
|
||||
author: "EliverLara",
|
||||
license: {
|
||||
SPDX: "MIT",
|
||||
license_text: {
|
||||
https_url:
|
||||
"https://raw.githubusercontent.com/EliverLara/Andromeda/master/LICENSE.md",
|
||||
license_checksum:
|
||||
"2f7886f1a05cefc2c26f5e49de1a39fa4466413c1ccb06fc80960e73f5ed4b89",
|
||||
},
|
||||
},
|
||||
url: "https://github.com/EliverLara/Andromeda",
|
||||
}
|
||||
21
styles/src/themes/atelier/LICENSE
Normal file
21
styles/src/themes/atelier/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 Bram de Haan, http://atelierbramdehaan.nl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
import { metaCommon, name, buildSyntax, Variant } from "./common"
|
||||
|
||||
const variant: Variant = {
|
||||
meta: {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { License, Meta, ThemeSyntax } from "./colorScheme"
|
||||
import { License, Meta, ThemeSyntax } from "../common/colorScheme"
|
||||
|
||||
export interface Variant {
|
||||
meta: Meta
|
||||
@@ -29,11 +29,6 @@ export const metaCommon: {
|
||||
author: "Bram de Haan (http://atelierbramdehaan.nl)",
|
||||
license: {
|
||||
SPDX: "MIT",
|
||||
license_text: {
|
||||
https_url: "https://atelierbram.mit-license.org/license.txt",
|
||||
license_checksum:
|
||||
"f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
21
styles/src/themes/ayu/LICENSE
Normal file
21
styles/src/themes/ayu/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Ike Ku
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createColorScheme } from "./common/ramps"
|
||||
import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common"
|
||||
import { createColorScheme } from "../common/ramps"
|
||||
import { ayu, meta as themeMeta, buildTheme } from "./common"
|
||||
|
||||
export const meta = {
|
||||
...themeMeta,
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createColorScheme } from "./common/ramps"
|
||||
import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common"
|
||||
import { createColorScheme } from "../common/ramps"
|
||||
import { ayu, meta as themeMeta, buildTheme } from "./common"
|
||||
|
||||
export const meta = {
|
||||
...themeMeta,
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createColorScheme } from "./common/ramps"
|
||||
import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common"
|
||||
import { createColorScheme } from "../common/ramps"
|
||||
import { ayu, meta as themeMeta, buildTheme } from "./common"
|
||||
|
||||
export const meta = {
|
||||
...themeMeta,
|
||||
@@ -1,8 +1,8 @@
|
||||
import { dark, light, mirage } from "ayu"
|
||||
import { ThemeSyntax } from "./syntax"
|
||||
import { ThemeSyntax } from "../common/syntax"
|
||||
import chroma from "chroma-js"
|
||||
import { colorRamp } from "./ramps"
|
||||
import { Meta } from "./colorScheme"
|
||||
import { colorRamp } from "../common/ramps"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
|
||||
export const ayu = {
|
||||
dark,
|
||||
@@ -79,12 +79,6 @@ export const meta: Meta = {
|
||||
author: "dempfi",
|
||||
license: {
|
||||
SPDX: "MIT",
|
||||
license_text: {
|
||||
https_url:
|
||||
"https://raw.githubusercontent.com/dempfi/ayu/master/LICENSE",
|
||||
license_checksum:
|
||||
"e0af0e0d1754c18ca075649d42f5c6d9a60f8bdc03c20dfd97105f2253a94173",
|
||||
},
|
||||
},
|
||||
url: "https://github.com/dempfi/ayu",
|
||||
}
|
||||
@@ -19,6 +19,11 @@ export interface ColorScheme {
|
||||
syntax?: Partial<ThemeSyntax>
|
||||
}
|
||||
|
||||
export interface MetaAndLicense {
|
||||
meta: Meta
|
||||
licenseFile: string
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
name: string
|
||||
author: string
|
||||
@@ -28,13 +33,6 @@ export interface Meta {
|
||||
|
||||
export interface License {
|
||||
SPDX: SPDXExpression
|
||||
/// A url where we can download the license's text
|
||||
license_text: Verification | string
|
||||
}
|
||||
|
||||
export interface Verification {
|
||||
https_url: string
|
||||
license_checksum: string
|
||||
}
|
||||
|
||||
// License name -> License text
|
||||
|
||||
21
styles/src/themes/gruvbox/LICENSE
Normal file
21
styles/src/themes/gruvbox/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) <YEAR> <COPYRIGHT HOLDER>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,6 +1,6 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta, ThemeSyntax } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { Meta, ThemeSyntax } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
|
||||
const name = "Gruvbox"
|
||||
|
||||
@@ -248,8 +248,6 @@ export const meta: Meta = {
|
||||
name,
|
||||
license: {
|
||||
SPDX: "MIT", // "MIT/X11"
|
||||
license_text:
|
||||
"Copyright <YEAR> <COPYRIGHT HOLDER>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/ or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
|
||||
},
|
||||
author: "morhetz <morhetz@gmail.com>",
|
||||
url: "https://github.com/morhetz/gruvbox",
|
||||
21
styles/src/themes/one/LICENSE
Normal file
21
styles/src/themes/one/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { fontWeights } from "../common"
|
||||
import { Meta, ThemeSyntax } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { fontWeights } from "../../common"
|
||||
import { Meta, ThemeSyntax } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
|
||||
const name = "One Dark"
|
||||
|
||||
@@ -74,12 +74,6 @@ export const meta: Meta = {
|
||||
author: "simurai",
|
||||
license: {
|
||||
SPDX: "MIT",
|
||||
license_text: {
|
||||
https_url:
|
||||
"https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md",
|
||||
license_checksum:
|
||||
"d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8",
|
||||
},
|
||||
},
|
||||
url: "https://github.com/atom/atom/tree/master/packages/one-dark-ui",
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import chroma from "chroma-js"
|
||||
import { fontWeights } from "../common"
|
||||
import { Meta, ThemeSyntax } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { fontWeights } from "../../common"
|
||||
import { Meta, ThemeSyntax } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
|
||||
const name = "One Light"
|
||||
|
||||
@@ -73,12 +73,6 @@ export const meta: Meta = {
|
||||
author: "simurai",
|
||||
license: {
|
||||
SPDX: "MIT",
|
||||
license_text: {
|
||||
https_url:
|
||||
"https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md",
|
||||
license_checksum:
|
||||
"d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8",
|
||||
},
|
||||
},
|
||||
url: "https://github.com/atom/atom/tree/master/packages/one-light-ui",
|
||||
}
|
||||
21
styles/src/themes/rose-pine/LICENSE
Normal file
21
styles/src/themes/rose-pine/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Emilia Dunfelt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,6 +1,6 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
|
||||
const name = "Rosé Pine Dawn"
|
||||
|
||||
@@ -34,12 +34,6 @@ export const meta: Meta = {
|
||||
author: "edunfelt",
|
||||
license: {
|
||||
SPDX: "MIT",
|
||||
license_text: {
|
||||
https_url:
|
||||
"https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE",
|
||||
license_checksum:
|
||||
"6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a",
|
||||
},
|
||||
},
|
||||
url: "https://github.com/edunfelt/base16-rose-pine-scheme",
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
|
||||
const name = "Rosé Pine Moon"
|
||||
|
||||
@@ -34,12 +34,6 @@ export const meta: Meta = {
|
||||
author: "edunfelt",
|
||||
license: {
|
||||
SPDX: "MIT",
|
||||
license_text: {
|
||||
https_url:
|
||||
"https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE",
|
||||
license_checksum:
|
||||
"6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a",
|
||||
},
|
||||
},
|
||||
url: "https://github.com/edunfelt/base16-rose-pine-scheme",
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import chroma from "chroma-js"
|
||||
import { Meta } from "./common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "./common/ramps"
|
||||
import { Meta } from "../common/colorScheme"
|
||||
import { colorRamp, createColorScheme } from "../common/ramps"
|
||||
|
||||
const name = "Rosé Pine"
|
||||
|
||||
@@ -32,12 +32,6 @@ export const meta: Meta = {
|
||||
author: "edunfelt",
|
||||
license: {
|
||||
SPDX: "MIT",
|
||||
license_text: {
|
||||
https_url:
|
||||
"https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE",
|
||||
license_checksum:
|
||||
"6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a",
|
||||
},
|
||||
},
|
||||
url: "https://github.com/edunfelt/base16-rose-pine-scheme",
|
||||
}
|
||||
21
styles/src/themes/sandcastle/LICENSE
Normal file
21
styles/src/themes/sandcastle/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 George Essig
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user