mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 15:44:20 +03:00
Merge remote-tracking branch 'origin/main' into invite-codes-2
This commit is contained in:
commit
d8dbbf1c05
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -31,6 +31,11 @@ jobs:
|
|||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
profile: minimal
|
profile: minimal
|
||||||
|
|
||||||
|
- name: Install Node
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@
|
|||||||
/crates/collab/.env.toml
|
/crates/collab/.env.toml
|
||||||
/crates/collab/static/styles.css
|
/crates/collab/static/styles.css
|
||||||
/vendor/bin
|
/vendor/bin
|
||||||
|
/assets/themes/*.json
|
||||||
|
@ -1 +0,0 @@
|
|||||||
collaborators = ["nathansobo", "as-cii", "maxbrunsfeld", "iamnbutler", "gibusu", "Kethku"]
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -909,6 +909,7 @@ dependencies = [
|
|||||||
"fuzzy",
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
"picker",
|
"picker",
|
||||||
|
"project",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"theme",
|
"theme",
|
||||||
|
@ -18,7 +18,10 @@
|
|||||||
"cmd-s": "workspace::Save",
|
"cmd-s": "workspace::Save",
|
||||||
"cmd-=": "zed::IncreaseBufferFontSize",
|
"cmd-=": "zed::IncreaseBufferFontSize",
|
||||||
"cmd--": "zed::DecreaseBufferFontSize",
|
"cmd--": "zed::DecreaseBufferFontSize",
|
||||||
"cmd-,": "zed::OpenSettings"
|
"cmd-,": "zed::OpenSettings",
|
||||||
|
"cmd-q": "zed::Quit",
|
||||||
|
"cmd-n": "workspace::OpenNew",
|
||||||
|
"cmd-o": "workspace::Open"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -210,7 +213,6 @@
|
|||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-F": "project_search::Deploy",
|
"cmd-shift-F": "project_search::Deploy",
|
||||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||||
"cmd-k t": "theme_selector::Reload",
|
|
||||||
"cmd-k cmd-s": "zed::OpenKeymap",
|
"cmd-k cmd-s": "zed::OpenKeymap",
|
||||||
"cmd-t": "project_symbols::Toggle",
|
"cmd-t": "project_symbols::Toggle",
|
||||||
"cmd-p": "file_finder::Toggle",
|
"cmd-p": "file_finder::Toggle",
|
||||||
|
0
assets/themes/.gitkeep
Normal file
0
assets/themes/.gitkeep
Normal file
@ -470,6 +470,19 @@ impl Server {
|
|||||||
state.unregister_project(request.payload.project_id, request.sender_id)?;
|
state.unregister_project(request.payload.project_id, request.sender_id)?;
|
||||||
(state.user_id_for_connection(request.sender_id)?, project)
|
(state.user_id_for_connection(request.sender_id)?, project)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
broadcast(
|
||||||
|
request.sender_id,
|
||||||
|
project.guests.keys().copied(),
|
||||||
|
|conn_id| {
|
||||||
|
self.peer.send(
|
||||||
|
conn_id,
|
||||||
|
proto::UnregisterProject {
|
||||||
|
project_id: request.payload.project_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
for (_, receipts) in project.join_requests {
|
for (_, receipts) in project.join_requests {
|
||||||
for receipt in receipts {
|
for receipt in receipts {
|
||||||
self.peer.respond(
|
self.peer.respond(
|
||||||
@ -1665,7 +1678,7 @@ mod tests {
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
executor::{self, Deterministic},
|
executor::{self, Deterministic},
|
||||||
geometry::vector::vec2f,
|
geometry::vector::vec2f,
|
||||||
ModelHandle, TestAppContext, ViewHandle,
|
ModelHandle, Task, TestAppContext, ViewHandle,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
||||||
@ -1697,7 +1710,7 @@ mod tests {
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use theme::ThemeRegistry;
|
use theme::ThemeRegistry;
|
||||||
use workspace::{Item, SplitDirection, ToggleFollow, Workspace, WorkspaceParams};
|
use workspace::{Item, SplitDirection, ToggleFollow, Workspace};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
@ -1930,6 +1943,14 @@ mod tests {
|
|||||||
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// When client A (the host) leaves, the project gets unshared and guests are notified.
|
||||||
|
cx_a.update(|_| drop(project_a));
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
project_b2.read_with(cx_b, |project, _| {
|
||||||
|
assert!(project.is_read_only());
|
||||||
|
assert!(project.collaborators().is_empty());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 10)]
|
#[gpui::test(iterations = 10)]
|
||||||
@ -4357,13 +4378,7 @@ mod tests {
|
|||||||
|
|
||||||
// Join the project as client B.
|
// Join the project as client B.
|
||||||
let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
|
let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
|
||||||
let mut params = cx_b.update(WorkspaceParams::test);
|
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
|
||||||
params.languages = lang_registry.clone();
|
|
||||||
params.project = project_b.clone();
|
|
||||||
params.client = client_b.client.clone();
|
|
||||||
params.user_store = client_b.user_store.clone();
|
|
||||||
|
|
||||||
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(¶ms, cx));
|
|
||||||
let editor_b = workspace_b
|
let editor_b = workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "main.rs"), true, cx)
|
workspace.open_path((worktree_id, "main.rs"), true, cx)
|
||||||
@ -4598,13 +4613,7 @@ mod tests {
|
|||||||
|
|
||||||
// Join the worktree as client B.
|
// Join the worktree as client B.
|
||||||
let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
|
let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
|
||||||
let mut params = cx_b.update(WorkspaceParams::test);
|
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx));
|
||||||
params.languages = lang_registry.clone();
|
|
||||||
params.project = project_b.clone();
|
|
||||||
params.client = client_b.client.clone();
|
|
||||||
params.user_store = client_b.user_store.clone();
|
|
||||||
|
|
||||||
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(¶ms, cx));
|
|
||||||
let editor_b = workspace_b
|
let editor_b = workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace.open_path((worktree_id, "one.rs"), true, cx)
|
workspace.open_path((worktree_id, "one.rs"), true, cx)
|
||||||
@ -6637,13 +6646,21 @@ mod tests {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
Channel::init(&client);
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
||||||
Project::init(&client);
|
let app_state = Arc::new(workspace::AppState {
|
||||||
cx.update(|cx| {
|
client: client.clone(),
|
||||||
workspace::init(&client, cx);
|
user_store: user_store.clone(),
|
||||||
|
languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
|
||||||
|
themes: ThemeRegistry::new((), cx.font_cache()),
|
||||||
|
fs: FakeFs::new(cx.background()),
|
||||||
|
build_window_options: || Default::default(),
|
||||||
|
initialize_workspace: |_, _, _| unimplemented!(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
Channel::init(&client);
|
||||||
|
Project::init(&client);
|
||||||
|
cx.update(|cx| workspace::init(app_state.clone(), cx));
|
||||||
|
|
||||||
client
|
client
|
||||||
.authenticate_and_connect(false, &cx.to_async())
|
.authenticate_and_connect(false, &cx.to_async())
|
||||||
.await
|
.await
|
||||||
@ -6882,23 +6899,7 @@ mod tests {
|
|||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) -> ViewHandle<Workspace> {
|
) -> ViewHandle<Workspace> {
|
||||||
let (window_id, _) = cx.add_window(|_| EmptyView);
|
let (window_id, _) = cx.add_window(|_| EmptyView);
|
||||||
cx.add_view(window_id, |cx| {
|
cx.add_view(window_id, |cx| Workspace::new(project.clone(), cx))
|
||||||
let fs = project.read(cx).fs().clone();
|
|
||||||
Workspace::new(
|
|
||||||
&WorkspaceParams {
|
|
||||||
fs,
|
|
||||||
project: project.clone(),
|
|
||||||
user_store: self.user_store.clone(),
|
|
||||||
languages: self.language_registry.clone(),
|
|
||||||
themes: ThemeRegistry::new((), cx.font_cache().clone()),
|
|
||||||
channel_list: cx.add_model(|cx| {
|
|
||||||
ChannelList::new(self.user_store.clone(), self.client.clone(), cx)
|
|
||||||
}),
|
|
||||||
client: self.client.clone(),
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn simulate_host(
|
async fn simulate_host(
|
||||||
|
@ -12,6 +12,7 @@ editor = { path = "../editor" }
|
|||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
picker = { path = "../picker" }
|
picker = { path = "../picker" }
|
||||||
|
project = { path = "../project" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
@ -20,6 +21,7 @@ workspace = { path = "../workspace" }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
|
project = { path = "../project", features = ["test-support"] }
|
||||||
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
ctor = "0.1"
|
ctor = "0.1"
|
||||||
|
@ -299,7 +299,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use workspace::{Workspace, WorkspaceParams};
|
use project::Project;
|
||||||
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_humanize_action_name() {
|
fn test_humanize_action_name() {
|
||||||
@ -319,15 +320,16 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_command_palette(cx: &mut TestAppContext) {
|
async fn test_command_palette(cx: &mut TestAppContext) {
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let app_state = cx.update(AppState::test);
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
workspace::init(¶ms.client, cx);
|
workspace::init(app_state.clone(), cx);
|
||||||
init(cx);
|
init(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||||
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
let editor = cx.add_view(window_id, |cx| {
|
let editor = cx.add_view(window_id, |cx| {
|
||||||
let mut editor = Editor::single_line(None, cx);
|
let mut editor = Editor::single_line(None, cx);
|
||||||
editor.set_text("abc", cx);
|
editor.set_text("abc", cx);
|
||||||
|
@ -23,7 +23,7 @@ use theme::IconButton;
|
|||||||
use workspace::{
|
use workspace::{
|
||||||
menu::{Confirm, SelectNext, SelectPrev},
|
menu::{Confirm, SelectNext, SelectPrev},
|
||||||
sidebar::SidebarItem,
|
sidebar::SidebarItem,
|
||||||
AppState, JoinProject, Workspace,
|
JoinProject, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
@ -60,7 +60,6 @@ pub struct ContactsPanel {
|
|||||||
filter_editor: ViewHandle<Editor>,
|
filter_editor: ViewHandle<Editor>,
|
||||||
collapsed_sections: Vec<Section>,
|
collapsed_sections: Vec<Section>,
|
||||||
selection: Option<usize>,
|
selection: Option<usize>,
|
||||||
app_state: Arc<AppState>,
|
|
||||||
_maintain_contacts: Subscription,
|
_maintain_contacts: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||||||
|
|
||||||
impl ContactsPanel {
|
impl ContactsPanel {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
app_state: Arc<AppState>,
|
user_store: ModelHandle<UserStore>,
|
||||||
workspace: WeakViewHandle<Workspace>,
|
workspace: WeakViewHandle<Workspace>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -152,8 +151,8 @@ impl ContactsPanel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.subscribe(&app_state.user_store, {
|
cx.subscribe(&user_store, {
|
||||||
let user_store = app_state.user_store.downgrade();
|
let user_store = user_store.downgrade();
|
||||||
move |_, _, event, cx| {
|
move |_, _, event, cx| {
|
||||||
if let Some((workspace, user_store)) =
|
if let Some((workspace, user_store)) =
|
||||||
workspace.upgrade(cx).zip(user_store.upgrade(cx))
|
workspace.upgrade(cx).zip(user_store.upgrade(cx))
|
||||||
@ -187,7 +186,6 @@ impl ContactsPanel {
|
|||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
list_state: ListState::new(0, Orientation::Top, 1000., {
|
list_state: ListState::new(0, Orientation::Top, 1000., {
|
||||||
let this = cx.weak_handle();
|
let this = cx.weak_handle();
|
||||||
let app_state = app_state.clone();
|
|
||||||
move |ix, cx| {
|
move |ix, cx| {
|
||||||
let this = this.upgrade(cx).unwrap();
|
let this = this.upgrade(cx).unwrap();
|
||||||
let this = this.read(cx);
|
let this = this.read(cx);
|
||||||
@ -234,7 +232,6 @@ impl ContactsPanel {
|
|||||||
contact.clone(),
|
contact.clone(),
|
||||||
current_user_id,
|
current_user_id,
|
||||||
*project_ix,
|
*project_ix,
|
||||||
app_state.clone(),
|
|
||||||
theme,
|
theme,
|
||||||
is_last_project_for_contact,
|
is_last_project_for_contact,
|
||||||
is_selected,
|
is_selected,
|
||||||
@ -249,10 +246,8 @@ impl ContactsPanel {
|
|||||||
entries: Default::default(),
|
entries: Default::default(),
|
||||||
match_candidates: Default::default(),
|
match_candidates: Default::default(),
|
||||||
filter_editor,
|
filter_editor,
|
||||||
_maintain_contacts: cx
|
_maintain_contacts: cx.observe(&user_store, |this, _, cx| this.update_entries(cx)),
|
||||||
.observe(&app_state.user_store, |this, _, cx| this.update_entries(cx)),
|
user_store,
|
||||||
user_store: app_state.user_store.clone(),
|
|
||||||
app_state,
|
|
||||||
};
|
};
|
||||||
this.update_entries(cx);
|
this.update_entries(cx);
|
||||||
this
|
this
|
||||||
@ -351,7 +346,6 @@ impl ContactsPanel {
|
|||||||
contact: Arc<Contact>,
|
contact: Arc<Contact>,
|
||||||
current_user_id: Option<u64>,
|
current_user_id: Option<u64>,
|
||||||
project_index: usize,
|
project_index: usize,
|
||||||
app_state: Arc<AppState>,
|
|
||||||
theme: &theme::ContactsPanel,
|
theme: &theme::ContactsPanel,
|
||||||
is_last_project: bool,
|
is_last_project: bool,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
@ -456,7 +450,6 @@ impl ContactsPanel {
|
|||||||
cx.dispatch_global_action(JoinProject {
|
cx.dispatch_global_action(JoinProject {
|
||||||
contact: contact.clone(),
|
contact: contact.clone(),
|
||||||
project_index,
|
project_index,
|
||||||
app_state: app_state.clone(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -782,7 +775,6 @@ impl ContactsPanel {
|
|||||||
.dispatch_global_action(JoinProject {
|
.dispatch_global_action(JoinProject {
|
||||||
contact: contact.clone(),
|
contact: contact.clone(),
|
||||||
project_index: *project_index,
|
project_index: *project_index,
|
||||||
app_state: self.app_state.clone(),
|
|
||||||
}),
|
}),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -987,19 +979,20 @@ impl PartialEq for ContactEntry {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use client::{proto, test::FakeServer, ChannelList, Client};
|
use client::{proto, test::FakeServer, Client};
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
|
use project::Project;
|
||||||
use theme::ThemeRegistry;
|
use theme::ThemeRegistry;
|
||||||
use workspace::WorkspaceParams;
|
use workspace::AppState;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_contact_panel(cx: &mut TestAppContext) {
|
async fn test_contact_panel(cx: &mut TestAppContext) {
|
||||||
let (app_state, server) = init(cx).await;
|
let (app_state, server) = init(cx).await;
|
||||||
let workspace_params = cx.update(WorkspaceParams::test);
|
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||||
let workspace = cx.add_view(0, |cx| Workspace::new(&workspace_params, cx));
|
let workspace = cx.add_view(0, |cx| Workspace::new(project, cx));
|
||||||
let panel = cx.add_view(0, |cx| {
|
let panel = cx.add_view(0, |cx| {
|
||||||
ContactsPanel::new(app_state.clone(), workspace.downgrade(), cx)
|
ContactsPanel::new(app_state.user_store.clone(), workspace.downgrade(), cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let get_users_request = server.receive::<proto::GetUsers>().await.unwrap();
|
let get_users_request = server.receive::<proto::GetUsers>().await.unwrap();
|
||||||
@ -1181,13 +1174,6 @@ mod tests {
|
|||||||
let mut client = Client::new(http_client.clone());
|
let mut client = Client::new(http_client.clone());
|
||||||
let server = FakeServer::for_client(100, &mut client, &cx).await;
|
let server = FakeServer::for_client(100, &mut client, &cx).await;
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||||
let channel_list =
|
|
||||||
cx.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx));
|
|
||||||
|
|
||||||
let get_channels = server.receive::<proto::GetChannels>().await.unwrap();
|
|
||||||
server
|
|
||||||
.respond(get_channels.receipt(), Default::default())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Arc::new(AppState {
|
Arc::new(AppState {
|
||||||
@ -1196,9 +1182,8 @@ mod tests {
|
|||||||
client,
|
client,
|
||||||
user_store: user_store.clone(),
|
user_store: user_store.clone(),
|
||||||
fs,
|
fs,
|
||||||
channel_list,
|
build_window_options: || Default::default(),
|
||||||
build_window_options: || unimplemented!(),
|
initialize_workspace: |_, _, _| {},
|
||||||
build_workspace: |_, _, _| unimplemented!(),
|
|
||||||
}),
|
}),
|
||||||
server,
|
server,
|
||||||
)
|
)
|
||||||
|
@ -707,49 +707,42 @@ mod tests {
|
|||||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
|
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
use workspace::WorkspaceParams;
|
use workspace::AppState;
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_diagnostics(cx: &mut TestAppContext) {
|
async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let app_state = cx.update(AppState::test);
|
||||||
let project = params.project.clone();
|
app_state
|
||||||
let workspace = cx.add_view(0, |cx| Workspace::new(¶ms, cx));
|
|
||||||
|
|
||||||
params
|
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
.insert_tree(
|
.insert_tree(
|
||||||
"/test",
|
"/test",
|
||||||
json!({
|
json!({
|
||||||
"consts.rs": "
|
"consts.rs": "
|
||||||
const a: i32 = 'a';
|
const a: i32 = 'a';
|
||||||
const b: i32 = c;
|
const b: i32 = c;
|
||||||
"
|
"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
|
|
||||||
"main.rs": "
|
"main.rs": "
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = vec![];
|
let x = vec![];
|
||||||
let y = vec![];
|
let y = vec![];
|
||||||
a(x);
|
a(x);
|
||||||
b(y);
|
b(y);
|
||||||
// comment 1
|
// comment 1
|
||||||
// comment 2
|
// comment 2
|
||||||
c(y);
|
c(y);
|
||||||
d(x);
|
d(x);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
project
|
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
|
||||||
.update(cx, |project, cx| {
|
let workspace = cx.add_view(0, |cx| Workspace::new(project.clone(), cx));
|
||||||
project.find_or_create_local_worktree("/test", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Create some diagnostics
|
// Create some diagnostics
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
|
@ -862,7 +862,14 @@ impl Editor {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
|
||||||
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
Self::new(EditorMode::SingleLine, buffer, None, field_editor_style, cx)
|
Self::new(
|
||||||
|
EditorMode::SingleLine,
|
||||||
|
buffer,
|
||||||
|
None,
|
||||||
|
field_editor_style,
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auto_height(
|
pub fn auto_height(
|
||||||
@ -877,6 +884,7 @@ impl Editor {
|
|||||||
buffer,
|
buffer,
|
||||||
None,
|
None,
|
||||||
field_editor_style,
|
field_editor_style,
|
||||||
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -887,7 +895,7 @@ impl Editor {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
Self::new(EditorMode::Full, buffer, project, None, cx)
|
Self::new(EditorMode::Full, buffer, project, None, None, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_multibuffer(
|
pub fn for_multibuffer(
|
||||||
@ -895,7 +903,7 @@ impl Editor {
|
|||||||
project: Option<ModelHandle<Project>>,
|
project: Option<ModelHandle<Project>>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new(EditorMode::Full, buffer, project, None, cx)
|
Self::new(EditorMode::Full, buffer, project, None, None, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
|
pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
|
||||||
@ -904,6 +912,7 @@ impl Editor {
|
|||||||
self.buffer.clone(),
|
self.buffer.clone(),
|
||||||
self.project.clone(),
|
self.project.clone(),
|
||||||
self.get_field_editor_theme,
|
self.get_field_editor_theme,
|
||||||
|
Some(self.selections.clone()),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
clone.scroll_position = self.scroll_position;
|
clone.scroll_position = self.scroll_position;
|
||||||
@ -917,6 +926,7 @@ impl Editor {
|
|||||||
buffer: ModelHandle<MultiBuffer>,
|
buffer: ModelHandle<MultiBuffer>,
|
||||||
project: Option<ModelHandle<Project>>,
|
project: Option<ModelHandle<Project>>,
|
||||||
get_field_editor_theme: Option<GetFieldEditorTheme>,
|
get_field_editor_theme: Option<GetFieldEditorTheme>,
|
||||||
|
selections: Option<SelectionsCollection>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let display_map = cx.add_model(|cx| {
|
let display_map = cx.add_model(|cx| {
|
||||||
@ -937,7 +947,8 @@ impl Editor {
|
|||||||
cx.observe(&display_map, Self::on_display_map_changed)
|
cx.observe(&display_map, Self::on_display_map_changed)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
|
let selections = selections
|
||||||
|
.unwrap_or_else(|| SelectionsCollection::new(display_map.clone(), buffer.clone()));
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
handle: cx.weak_handle(),
|
handle: cx.weak_handle(),
|
||||||
@ -6025,7 +6036,10 @@ mod tests {
|
|||||||
use std::{cell::RefCell, rc::Rc, time::Instant};
|
use std::{cell::RefCell, rc::Rc, time::Instant};
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
use util::test::{marked_text_by, marked_text_ranges, marked_text_ranges_by, sample_text};
|
use util::{
|
||||||
|
assert_set_eq,
|
||||||
|
test::{marked_text_by, marked_text_ranges, marked_text_ranges_by, sample_text},
|
||||||
|
};
|
||||||
use workspace::{FollowableItem, ItemHandle};
|
use workspace::{FollowableItem, ItemHandle};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@ -6304,6 +6318,26 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_clone_with_selections(cx: &mut gpui::MutableAppContext) {
|
||||||
|
let (text, selection_ranges) = marked_text_ranges(indoc! {"
|
||||||
|
The qu[ick brown
|
||||||
|
fox jum]ps over
|
||||||
|
the lazy dog
|
||||||
|
"});
|
||||||
|
cx.set_global(Settings::test(cx));
|
||||||
|
let buffer = MultiBuffer::build_simple(&text, cx);
|
||||||
|
|
||||||
|
let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
|
||||||
|
|
||||||
|
let cloned_editor = view.update(cx, |view, cx| {
|
||||||
|
view.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
|
||||||
|
view.clone(cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_set_eq!(cloned_editor.selections.ranges(cx), selection_ranges);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
||||||
cx.set_global(Settings::test(cx));
|
cx.set_global(Settings::test(cx));
|
||||||
@ -8815,7 +8849,7 @@ mod tests {
|
|||||||
let fs = FakeFs::new(cx.background().clone());
|
let fs = FakeFs::new(cx.background().clone());
|
||||||
fs.insert_file("/file.rs", Default::default()).await;
|
fs.insert_file("/file.rs", Default::default()).await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/file.rs"], cx).await;
|
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
||||||
@ -8937,7 +8971,7 @@ mod tests {
|
|||||||
let fs = FakeFs::new(cx.background().clone());
|
let fs = FakeFs::new(cx.background().clone());
|
||||||
fs.insert_file("/file.rs", text).await;
|
fs.insert_file("/file.rs", text).await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/file.rs"], cx).await;
|
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
|
||||||
@ -9765,7 +9799,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_editor(buffer: ModelHandle<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
|
fn build_editor(buffer: ModelHandle<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
|
||||||
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
Editor::new(EditorMode::Full, buffer, None, None, None, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_selection_ranges(
|
fn assert_selection_ranges(
|
||||||
|
@ -1517,7 +1517,7 @@ mod tests {
|
|||||||
cx.set_global(Settings::test(cx));
|
cx.set_global(Settings::test(cx));
|
||||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
|
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
|
||||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||||
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
Editor::new(EditorMode::Full, buffer, None, None, None, cx)
|
||||||
});
|
});
|
||||||
let element = EditorElement::new(
|
let element = EditorElement::new(
|
||||||
editor.downgrade(),
|
editor.downgrade(),
|
||||||
@ -1539,7 +1539,7 @@ mod tests {
|
|||||||
cx.set_global(Settings::test(cx));
|
cx.set_global(Settings::test(cx));
|
||||||
let buffer = MultiBuffer::build_simple("", cx);
|
let buffer = MultiBuffer::build_simple("", cx);
|
||||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||||
Editor::new(EditorMode::Full, buffer, None, None, cx)
|
Editor::new(EditorMode::Full, buffer, None, None, None, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
|
@ -2479,6 +2479,7 @@ impl History {
|
|||||||
self.undo_stack.pop();
|
self.undo_stack.pop();
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
|
self.redo_stack.clear();
|
||||||
let transaction = self.undo_stack.last_mut().unwrap();
|
let transaction = self.undo_stack.last_mut().unwrap();
|
||||||
transaction.last_edit_at = now;
|
transaction.last_edit_at = now;
|
||||||
for (buffer_id, transaction_id) in buffer_transactions {
|
for (buffer_id, transaction_id) in buffer_transactions {
|
||||||
@ -2511,6 +2512,7 @@ impl History {
|
|||||||
};
|
};
|
||||||
if !transaction.buffer_transactions.is_empty() {
|
if !transaction.buffer_transactions.is_empty() {
|
||||||
self.undo_stack.push(transaction);
|
self.undo_stack.push(transaction);
|
||||||
|
self.redo_stack.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3935,6 +3937,16 @@ mod tests {
|
|||||||
buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
|
buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx));
|
||||||
assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
|
assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
|
||||||
|
|
||||||
|
// Redo stack gets cleared after an edit.
|
||||||
|
now += 2 * group_interval;
|
||||||
|
multibuffer.start_transaction_at(now, cx);
|
||||||
|
multibuffer.edit([(0..0, "X")], cx);
|
||||||
|
multibuffer.end_transaction_at(now, cx);
|
||||||
|
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
|
||||||
|
multibuffer.redo(cx);
|
||||||
|
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
|
||||||
|
multibuffer.undo(cx);
|
||||||
|
assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
|
||||||
multibuffer.undo(cx);
|
multibuffer.undo(cx);
|
||||||
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ pub struct PendingSelection {
|
|||||||
pub mode: SelectMode,
|
pub mode: SelectMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct SelectionsCollection {
|
pub struct SelectionsCollection {
|
||||||
display_map: ModelHandle<DisplayMap>,
|
display_map: ModelHandle<DisplayMap>,
|
||||||
buffer: ModelHandle<MultiBuffer>,
|
buffer: ModelHandle<MultiBuffer>,
|
||||||
|
@ -258,9 +258,10 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use editor::{Editor, Input};
|
use editor::{Editor, Input};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::path::PathBuf;
|
use workspace::{
|
||||||
use workspace::menu::{Confirm, SelectNext};
|
menu::{Confirm, SelectNext},
|
||||||
use workspace::{Workspace, WorkspaceParams};
|
AppState, Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
@ -271,13 +272,13 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_matching_paths(cx: &mut gpui::TestAppContext) {
|
async fn test_matching_paths(cx: &mut gpui::TestAppContext) {
|
||||||
cx.update(|cx| {
|
let app_state = cx.update(|cx| {
|
||||||
super::init(cx);
|
super::init(cx);
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
|
AppState::test(cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let params = cx.update(WorkspaceParams::test);
|
app_state
|
||||||
params
|
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
.insert_tree(
|
.insert_tree(
|
||||||
@ -291,16 +292,8 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||||
params
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
.project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree("/root", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
|
||||||
.await;
|
|
||||||
cx.dispatch_action(window_id, Toggle);
|
cx.dispatch_action(window_id, Toggle);
|
||||||
|
|
||||||
let finder = cx.read(|cx| {
|
let finder = cx.read(|cx| {
|
||||||
@ -341,32 +334,26 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_matching_cancellation(cx: &mut gpui::TestAppContext) {
|
async fn test_matching_cancellation(cx: &mut gpui::TestAppContext) {
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let app_state = cx.update(AppState::test);
|
||||||
let fs = params.fs.as_fake();
|
app_state
|
||||||
fs.insert_tree(
|
.fs
|
||||||
"/dir",
|
.as_fake()
|
||||||
json!({
|
.insert_tree(
|
||||||
"hello": "",
|
"/dir",
|
||||||
"goodbye": "",
|
json!({
|
||||||
"halogen-light": "",
|
"hello": "",
|
||||||
"happiness": "",
|
"goodbye": "",
|
||||||
"height": "",
|
"halogen-light": "",
|
||||||
"hi": "",
|
"happiness": "",
|
||||||
"hiccup": "",
|
"height": "",
|
||||||
}),
|
"hi": "",
|
||||||
)
|
"hiccup": "",
|
||||||
.await;
|
}),
|
||||||
|
)
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
|
||||||
params
|
|
||||||
.project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree("/dir", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
|
let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
let (_, finder) =
|
let (_, finder) =
|
||||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
|
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
|
||||||
|
|
||||||
@ -406,23 +393,20 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_single_file_worktrees(cx: &mut gpui::TestAppContext) {
|
async fn test_single_file_worktrees(cx: &mut gpui::TestAppContext) {
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let app_state = cx.update(AppState::test);
|
||||||
params
|
app_state
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
.insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
|
.insert_tree("/root", json!({ "the-parent-dir": { "the-file": "" } }))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(
|
||||||
params
|
app_state.fs.clone(),
|
||||||
.project
|
["/root/the-parent-dir/the-file".as_ref()],
|
||||||
.update(cx, |project, cx| {
|
cx,
|
||||||
project.find_or_create_local_worktree("/root/the-parent-dir/the-file", true, cx)
|
)
|
||||||
})
|
.await;
|
||||||
.await
|
let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
.unwrap();
|
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
|
||||||
.await;
|
|
||||||
let (_, finder) =
|
let (_, finder) =
|
||||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
|
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
|
||||||
|
|
||||||
@ -451,10 +435,12 @@ mod tests {
|
|||||||
finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 0));
|
finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test(retries = 5)]
|
#[gpui::test]
|
||||||
async fn test_multiple_matches_with_same_relative_path(cx: &mut gpui::TestAppContext) {
|
async fn test_multiple_matches_with_same_relative_path(cx: &mut gpui::TestAppContext) {
|
||||||
let params = cx.update(WorkspaceParams::test);
|
cx.foreground().forbid_parking();
|
||||||
params
|
|
||||||
|
let app_state = cx.update(AppState::test);
|
||||||
|
app_state
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
.insert_tree(
|
.insert_tree(
|
||||||
@ -466,19 +452,13 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(
|
||||||
|
app_state.fs.clone(),
|
||||||
workspace
|
["/root/dir1".as_ref(), "/root/dir2".as_ref()],
|
||||||
.update(cx, |workspace, cx| {
|
cx,
|
||||||
workspace.open_paths(
|
)
|
||||||
vec![PathBuf::from("/root/dir1"), PathBuf::from("/root/dir2")],
|
.await;
|
||||||
cx,
|
let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
)
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let (_, finder) =
|
let (_, finder) =
|
||||||
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
|
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use std::{
|
|||||||
ffi::{c_void, CStr, OsStr},
|
ffi::{c_void, CStr, OsStr},
|
||||||
os::unix::ffi::OsStrExt,
|
os::unix::ffi::OsStrExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
slice,
|
ptr, slice,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@ -21,12 +21,28 @@ pub struct Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventStream {
|
pub struct EventStream {
|
||||||
stream: fs::FSEventStreamRef,
|
lifecycle: Arc<Mutex<Lifecycle>>,
|
||||||
state: Arc<Mutex<Lifecycle>>,
|
state: Box<State>,
|
||||||
callback: Box<Option<RunCallback>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunCallback = Box<dyn FnMut(Vec<Event>) -> bool>;
|
struct State {
|
||||||
|
latency: Duration,
|
||||||
|
paths: cf::CFMutableArrayRef,
|
||||||
|
callback: Option<Box<dyn FnMut(Vec<Event>) -> bool>>,
|
||||||
|
last_valid_event_id: Option<fs::FSEventStreamEventId>,
|
||||||
|
stream: fs::FSEventStreamRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for State {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
cf::CFRelease(self.paths);
|
||||||
|
fs::FSEventStreamStop(self.stream);
|
||||||
|
fs::FSEventStreamInvalidate(self.stream);
|
||||||
|
fs::FSEventStreamRelease(self.stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum Lifecycle {
|
enum Lifecycle {
|
||||||
New,
|
New,
|
||||||
@ -42,15 +58,6 @@ unsafe impl Send for Lifecycle {}
|
|||||||
impl EventStream {
|
impl EventStream {
|
||||||
pub fn new(paths: &[&Path], latency: Duration) -> (Self, Handle) {
|
pub fn new(paths: &[&Path], latency: Duration) -> (Self, Handle) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let callback = Box::new(None);
|
|
||||||
let stream_context = fs::FSEventStreamContext {
|
|
||||||
version: 0,
|
|
||||||
info: callback.as_ref() as *const _ as *mut c_void,
|
|
||||||
retain: None,
|
|
||||||
release: None,
|
|
||||||
copy_description: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let cf_paths =
|
let cf_paths =
|
||||||
cf::CFArrayCreateMutable(cf::kCFAllocatorDefault, 0, &cf::kCFTypeArrayCallBacks);
|
cf::CFArrayCreateMutable(cf::kCFAllocatorDefault, 0, &cf::kCFTypeArrayCallBacks);
|
||||||
assert!(!cf_paths.is_null());
|
assert!(!cf_paths.is_null());
|
||||||
@ -69,6 +76,20 @@ impl EventStream {
|
|||||||
cf::CFRelease(cf_url);
|
cf::CFRelease(cf_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut state = Box::new(State {
|
||||||
|
latency,
|
||||||
|
paths: cf_paths,
|
||||||
|
callback: None,
|
||||||
|
last_valid_event_id: None,
|
||||||
|
stream: ptr::null_mut(),
|
||||||
|
});
|
||||||
|
let stream_context = fs::FSEventStreamContext {
|
||||||
|
version: 0,
|
||||||
|
info: state.as_ref() as *const _ as *mut c_void,
|
||||||
|
retain: None,
|
||||||
|
release: None,
|
||||||
|
copy_description: None,
|
||||||
|
};
|
||||||
let stream = fs::FSEventStreamCreate(
|
let stream = fs::FSEventStreamCreate(
|
||||||
cf::kCFAllocatorDefault,
|
cf::kCFAllocatorDefault,
|
||||||
Self::trampoline,
|
Self::trampoline,
|
||||||
@ -80,17 +101,15 @@ impl EventStream {
|
|||||||
| fs::kFSEventStreamCreateFlagNoDefer
|
| fs::kFSEventStreamCreateFlagNoDefer
|
||||||
| fs::kFSEventStreamCreateFlagWatchRoot,
|
| fs::kFSEventStreamCreateFlagWatchRoot,
|
||||||
);
|
);
|
||||||
cf::CFRelease(cf_paths);
|
state.stream = stream;
|
||||||
|
|
||||||
let state = Arc::new(Mutex::new(Lifecycle::New));
|
|
||||||
|
|
||||||
|
let lifecycle = Arc::new(Mutex::new(Lifecycle::New));
|
||||||
(
|
(
|
||||||
EventStream {
|
EventStream {
|
||||||
stream,
|
lifecycle: lifecycle.clone(),
|
||||||
state: state.clone(),
|
state,
|
||||||
callback,
|
|
||||||
},
|
},
|
||||||
Handle(state),
|
Handle(lifecycle),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,21 +118,24 @@ impl EventStream {
|
|||||||
where
|
where
|
||||||
F: FnMut(Vec<Event>) -> bool + 'static,
|
F: FnMut(Vec<Event>) -> bool + 'static,
|
||||||
{
|
{
|
||||||
*self.callback = Some(Box::new(f));
|
self.state.callback = Some(Box::new(f));
|
||||||
unsafe {
|
unsafe {
|
||||||
let run_loop = cf::CFRunLoopGetCurrent();
|
let run_loop = cf::CFRunLoopGetCurrent();
|
||||||
{
|
{
|
||||||
let mut state = self.state.lock();
|
let mut state = self.lifecycle.lock();
|
||||||
match *state {
|
match *state {
|
||||||
Lifecycle::New => *state = Lifecycle::Running(run_loop),
|
Lifecycle::New => *state = Lifecycle::Running(run_loop),
|
||||||
Lifecycle::Running(_) => unreachable!(),
|
Lifecycle::Running(_) => unreachable!(),
|
||||||
Lifecycle::Stopped => return,
|
Lifecycle::Stopped => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs::FSEventStreamScheduleWithRunLoop(self.stream, run_loop, cf::kCFRunLoopDefaultMode);
|
fs::FSEventStreamScheduleWithRunLoop(
|
||||||
fs::FSEventStreamStart(self.stream);
|
self.state.stream,
|
||||||
|
run_loop,
|
||||||
|
cf::kCFRunLoopDefaultMode,
|
||||||
|
);
|
||||||
|
fs::FSEventStreamStart(self.state.stream);
|
||||||
cf::CFRunLoopRun();
|
cf::CFRunLoopRun();
|
||||||
fs::FSEventStreamRelease(self.stream);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,8 +151,8 @@ impl EventStream {
|
|||||||
let event_paths = event_paths as *const *const ::std::os::raw::c_char;
|
let event_paths = event_paths as *const *const ::std::os::raw::c_char;
|
||||||
let e_ptr = event_flags as *mut u32;
|
let e_ptr = event_flags as *mut u32;
|
||||||
let i_ptr = event_ids as *mut u64;
|
let i_ptr = event_ids as *mut u64;
|
||||||
let callback_ptr = (info as *mut Option<RunCallback>).as_mut().unwrap();
|
let state = (info as *mut State).as_mut().unwrap();
|
||||||
let callback = if let Some(callback) = callback_ptr.as_mut() {
|
let callback = if let Some(callback) = state.callback.as_mut() {
|
||||||
callback
|
callback
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
@ -139,30 +161,83 @@ impl EventStream {
|
|||||||
let paths = slice::from_raw_parts(event_paths, num);
|
let paths = slice::from_raw_parts(event_paths, num);
|
||||||
let flags = slice::from_raw_parts_mut(e_ptr, num);
|
let flags = slice::from_raw_parts_mut(e_ptr, num);
|
||||||
let ids = slice::from_raw_parts_mut(i_ptr, num);
|
let ids = slice::from_raw_parts_mut(i_ptr, num);
|
||||||
|
let mut stream_restarted = false;
|
||||||
|
|
||||||
let mut events = Vec::with_capacity(num);
|
// Sometimes FSEvents reports a "dropped" event, an indication that either the kernel
|
||||||
for p in 0..num {
|
// or our code couldn't keep up with the sheer volume of file-system events that were
|
||||||
let path_c_str = CStr::from_ptr(paths[p]);
|
// generated. If we observed a valid event before this happens, we'll try to read the
|
||||||
let path = PathBuf::from(OsStr::from_bytes(path_c_str.to_bytes()));
|
// file-system journal by stopping the current stream and creating a new one starting at
|
||||||
if let Some(flag) = StreamFlags::from_bits(flags[p]) {
|
// such event. Otherwise, we'll let invoke the callback with the dropped event, which
|
||||||
if flag.contains(StreamFlags::HISTORY_DONE) {
|
// will likely perform a re-scan of one of the root directories.
|
||||||
events.clear();
|
if flags
|
||||||
} else {
|
.iter()
|
||||||
events.push(Event {
|
.copied()
|
||||||
event_id: ids[p],
|
.filter_map(StreamFlags::from_bits)
|
||||||
flags: flag,
|
.any(|flags| {
|
||||||
path,
|
flags.contains(StreamFlags::USER_DROPPED)
|
||||||
});
|
|| flags.contains(StreamFlags::KERNEL_DROPPED)
|
||||||
}
|
})
|
||||||
} else {
|
{
|
||||||
debug_assert!(false, "unknown flag set for fs event: {}", flags[p]);
|
if let Some(last_valid_event_id) = state.last_valid_event_id.take() {
|
||||||
|
fs::FSEventStreamStop(state.stream);
|
||||||
|
fs::FSEventStreamInvalidate(state.stream);
|
||||||
|
fs::FSEventStreamRelease(state.stream);
|
||||||
|
|
||||||
|
let stream_context = fs::FSEventStreamContext {
|
||||||
|
version: 0,
|
||||||
|
info,
|
||||||
|
retain: None,
|
||||||
|
release: None,
|
||||||
|
copy_description: None,
|
||||||
|
};
|
||||||
|
let stream = fs::FSEventStreamCreate(
|
||||||
|
cf::kCFAllocatorDefault,
|
||||||
|
Self::trampoline,
|
||||||
|
&stream_context,
|
||||||
|
state.paths,
|
||||||
|
last_valid_event_id,
|
||||||
|
state.latency.as_secs_f64(),
|
||||||
|
fs::kFSEventStreamCreateFlagFileEvents
|
||||||
|
| fs::kFSEventStreamCreateFlagNoDefer
|
||||||
|
| fs::kFSEventStreamCreateFlagWatchRoot,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.stream = stream;
|
||||||
|
fs::FSEventStreamScheduleWithRunLoop(
|
||||||
|
state.stream,
|
||||||
|
cf::CFRunLoopGetCurrent(),
|
||||||
|
cf::kCFRunLoopDefaultMode,
|
||||||
|
);
|
||||||
|
fs::FSEventStreamStart(state.stream);
|
||||||
|
stream_restarted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !events.is_empty() {
|
if !stream_restarted {
|
||||||
if !callback(events) {
|
let mut events = Vec::with_capacity(num);
|
||||||
fs::FSEventStreamStop(stream_ref);
|
for p in 0..num {
|
||||||
cf::CFRunLoopStop(cf::CFRunLoopGetCurrent());
|
if let Some(flag) = StreamFlags::from_bits(flags[p]) {
|
||||||
|
if !flag.contains(StreamFlags::HISTORY_DONE) {
|
||||||
|
let path_c_str = CStr::from_ptr(paths[p]);
|
||||||
|
let path = PathBuf::from(OsStr::from_bytes(path_c_str.to_bytes()));
|
||||||
|
let event = Event {
|
||||||
|
event_id: ids[p],
|
||||||
|
flags: flag,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
state.last_valid_event_id = Some(event.event_id);
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug_assert!(false, "unknown flag set for fs event: {}", flags[p]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !events.is_empty() {
|
||||||
|
if !callback(events) {
|
||||||
|
fs::FSEventStreamStop(stream_ref);
|
||||||
|
cf::CFRunLoopStop(cf::CFRunLoopGetCurrent());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,6 @@ pub struct Menu<'a> {
|
|||||||
pub enum MenuItem<'a> {
|
pub enum MenuItem<'a> {
|
||||||
Action {
|
Action {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
keystroke: Option<&'a str>,
|
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
},
|
},
|
||||||
Separator,
|
Separator,
|
||||||
@ -193,6 +192,20 @@ impl App {
|
|||||||
cx.borrow_mut().quit();
|
cx.borrow_mut().quit();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
foreground_platform.on_will_open_menu(Box::new({
|
||||||
|
let cx = app.0.clone();
|
||||||
|
move || {
|
||||||
|
let mut cx = cx.borrow_mut();
|
||||||
|
cx.keystroke_matcher.clear_pending();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
foreground_platform.on_validate_menu_command(Box::new({
|
||||||
|
let cx = app.0.clone();
|
||||||
|
move |action| {
|
||||||
|
let cx = cx.borrow_mut();
|
||||||
|
!cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action)
|
||||||
|
}
|
||||||
|
}));
|
||||||
foreground_platform.on_menu_command(Box::new({
|
foreground_platform.on_menu_command(Box::new({
|
||||||
let cx = app.0.clone();
|
let cx = app.0.clone();
|
||||||
move |action| {
|
move |action| {
|
||||||
@ -1070,7 +1083,8 @@ impl MutableAppContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_menus(&mut self, menus: Vec<Menu>) {
|
pub fn set_menus(&mut self, menus: Vec<Menu>) {
|
||||||
self.foreground_platform.set_menus(menus);
|
self.foreground_platform
|
||||||
|
.set_menus(menus, &self.keystroke_matcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(
|
fn prompt(
|
||||||
@ -1364,6 +1378,26 @@ impl MutableAppContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_action_available(&self, action: &dyn Action) -> bool {
|
||||||
|
let action_type = action.as_any().type_id();
|
||||||
|
if let Some(window_id) = self.cx.platform.key_window_id() {
|
||||||
|
if let Some((presenter, _)) = self.presenters_and_platform_windows.get(&window_id) {
|
||||||
|
let dispatch_path = presenter.borrow().dispatch_path(&self.cx);
|
||||||
|
for view_id in dispatch_path {
|
||||||
|
if let Some(view) = self.views.get(&(window_id, view_id)) {
|
||||||
|
let view_type = view.as_any().type_id();
|
||||||
|
if let Some(actions) = self.actions.get(&view_type) {
|
||||||
|
if actions.contains_key(&action_type) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.global_actions.contains_key(&action_type)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: &dyn Action) {
|
pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: &dyn Action) {
|
||||||
let presenter = self
|
let presenter = self
|
||||||
.presenters_and_platform_windows
|
.presenters_and_platform_windows
|
||||||
|
@ -215,12 +215,12 @@ where
|
|||||||
self.autoscroll(scroll_max, size.y(), item_height);
|
self.autoscroll(scroll_max, size.y(), item_height);
|
||||||
|
|
||||||
let start = cmp::min(
|
let start = cmp::min(
|
||||||
((self.scroll_top() - self.padding_top) / item_height) as usize,
|
((self.scroll_top() - self.padding_top) / item_height.max(1.)) as usize,
|
||||||
self.item_count,
|
self.item_count,
|
||||||
);
|
);
|
||||||
let end = cmp::min(
|
let end = cmp::min(
|
||||||
self.item_count,
|
self.item_count,
|
||||||
start + (size.y() / item_height).ceil() as usize + 1,
|
start + (size.y() / item_height.max(1.)).ceil() as usize + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (start..end).contains(&sample_item_ix) {
|
if (start..end).contains(&sample_item_ix) {
|
||||||
|
@ -123,6 +123,10 @@ impl Matcher {
|
|||||||
self.pending.clear();
|
self.pending.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_pending_keystrokes(&self) -> bool {
|
||||||
|
!self.pending.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push_keystroke(
|
pub fn push_keystroke(
|
||||||
&mut self,
|
&mut self,
|
||||||
keystroke: Keystroke,
|
keystroke: Keystroke,
|
||||||
|
@ -14,6 +14,7 @@ use crate::{
|
|||||||
rect::{RectF, RectI},
|
rect::{RectF, RectI},
|
||||||
vector::Vector2F,
|
vector::Vector2F,
|
||||||
},
|
},
|
||||||
|
keymap,
|
||||||
text_layout::{LineLayout, RunStyle},
|
text_layout::{LineLayout, RunStyle},
|
||||||
Action, ClipboardItem, Menu, Scene,
|
Action, ClipboardItem, Menu, Scene,
|
||||||
};
|
};
|
||||||
@ -72,7 +73,9 @@ pub(crate) trait ForegroundPlatform {
|
|||||||
fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>);
|
fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>);
|
||||||
|
|
||||||
fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
|
fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
|
||||||
fn set_menus(&self, menus: Vec<Menu>);
|
fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
|
||||||
|
fn on_will_open_menu(&self, callback: Box<dyn FnMut()>);
|
||||||
|
fn set_menus(&self, menus: Vec<Menu>, matcher: &keymap::Matcher);
|
||||||
fn prompt_for_paths(
|
fn prompt_for_paths(
|
||||||
&self,
|
&self,
|
||||||
options: PathPromptOptions,
|
options: PathPromptOptions,
|
||||||
|
@ -8,7 +8,35 @@ use cocoa::{
|
|||||||
base::{id, nil, YES},
|
base::{id, nil, YES},
|
||||||
foundation::NSString as _,
|
foundation::NSString as _,
|
||||||
};
|
};
|
||||||
use std::{ffi::CStr, os::raw::c_char};
|
use std::{borrow::Cow, ffi::CStr, os::raw::c_char};
|
||||||
|
|
||||||
|
pub fn key_to_native(key: &str) -> Cow<str> {
|
||||||
|
use cocoa::appkit::*;
|
||||||
|
let code = match key {
|
||||||
|
"backspace" => 0x7F,
|
||||||
|
"up" => NSUpArrowFunctionKey,
|
||||||
|
"down" => NSDownArrowFunctionKey,
|
||||||
|
"left" => NSLeftArrowFunctionKey,
|
||||||
|
"right" => NSRightArrowFunctionKey,
|
||||||
|
"pageup" => NSPageUpFunctionKey,
|
||||||
|
"pagedown" => NSPageDownFunctionKey,
|
||||||
|
"delete" => NSDeleteFunctionKey,
|
||||||
|
"f1" => NSF1FunctionKey,
|
||||||
|
"f2" => NSF2FunctionKey,
|
||||||
|
"f3" => NSF3FunctionKey,
|
||||||
|
"f4" => NSF4FunctionKey,
|
||||||
|
"f5" => NSF5FunctionKey,
|
||||||
|
"f6" => NSF6FunctionKey,
|
||||||
|
"f7" => NSF7FunctionKey,
|
||||||
|
"f8" => NSF8FunctionKey,
|
||||||
|
"f9" => NSF9FunctionKey,
|
||||||
|
"f10" => NSF10FunctionKey,
|
||||||
|
"f11" => NSF11FunctionKey,
|
||||||
|
"f12" => NSF12FunctionKey,
|
||||||
|
_ => return Cow::Borrowed(key),
|
||||||
|
};
|
||||||
|
Cow::Owned(String::from_utf16(&[code]).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
|
pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use super::{BoolExt as _, Dispatcher, FontSystem, Window};
|
use super::{event::key_to_native, BoolExt as _, Dispatcher, FontSystem, Window};
|
||||||
use crate::{
|
use crate::{
|
||||||
executor,
|
executor, keymap,
|
||||||
keymap::Keystroke,
|
|
||||||
platform::{self, CursorStyle},
|
platform::{self, CursorStyle},
|
||||||
Action, ClipboardItem, Event, Menu, MenuItem,
|
Action, ClipboardItem, Event, Menu, MenuItem,
|
||||||
};
|
};
|
||||||
@ -90,6 +89,14 @@ unsafe fn build_classes() {
|
|||||||
sel!(handleGPUIMenuItem:),
|
sel!(handleGPUIMenuItem:),
|
||||||
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
|
||||||
);
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(validateMenuItem:),
|
||||||
|
validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(menuWillOpen:),
|
||||||
|
menu_will_open as extern "C" fn(&mut Object, Sel, id),
|
||||||
|
);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(application:openURLs:),
|
sel!(application:openURLs:),
|
||||||
open_urls as extern "C" fn(&mut Object, Sel, id, id),
|
open_urls as extern "C" fn(&mut Object, Sel, id, id),
|
||||||
@ -108,14 +115,22 @@ pub struct MacForegroundPlatformState {
|
|||||||
quit: Option<Box<dyn FnMut()>>,
|
quit: Option<Box<dyn FnMut()>>,
|
||||||
event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
|
event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
|
||||||
menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
|
menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||||
|
validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||||
|
will_open_menu: Option<Box<dyn FnMut()>>,
|
||||||
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
||||||
finish_launching: Option<Box<dyn FnOnce() -> ()>>,
|
finish_launching: Option<Box<dyn FnOnce() -> ()>>,
|
||||||
menu_actions: Vec<Box<dyn Action>>,
|
menu_actions: Vec<Box<dyn Action>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacForegroundPlatform {
|
impl MacForegroundPlatform {
|
||||||
unsafe fn create_menu_bar(&self, menus: Vec<Menu>) -> id {
|
unsafe fn create_menu_bar(
|
||||||
|
&self,
|
||||||
|
menus: Vec<Menu>,
|
||||||
|
delegate: id,
|
||||||
|
keystroke_matcher: &keymap::Matcher,
|
||||||
|
) -> id {
|
||||||
let menu_bar = NSMenu::new(nil).autorelease();
|
let menu_bar = NSMenu::new(nil).autorelease();
|
||||||
|
menu_bar.setDelegate_(delegate);
|
||||||
let mut state = self.0.borrow_mut();
|
let mut state = self.0.borrow_mut();
|
||||||
|
|
||||||
state.menu_actions.clear();
|
state.menu_actions.clear();
|
||||||
@ -126,6 +141,7 @@ impl MacForegroundPlatform {
|
|||||||
let menu_name = menu_config.name;
|
let menu_name = menu_config.name;
|
||||||
|
|
||||||
menu.setTitle_(ns_string(menu_name));
|
menu.setTitle_(ns_string(menu_name));
|
||||||
|
menu.setDelegate_(delegate);
|
||||||
|
|
||||||
for item_config in menu_config.items {
|
for item_config in menu_config.items {
|
||||||
let item;
|
let item;
|
||||||
@ -134,19 +150,18 @@ impl MacForegroundPlatform {
|
|||||||
MenuItem::Separator => {
|
MenuItem::Separator => {
|
||||||
item = NSMenuItem::separatorItem(nil);
|
item = NSMenuItem::separatorItem(nil);
|
||||||
}
|
}
|
||||||
MenuItem::Action {
|
MenuItem::Action { name, action } => {
|
||||||
name,
|
let mut keystroke = None;
|
||||||
keystroke,
|
if let Some(binding) = keystroke_matcher
|
||||||
action,
|
.bindings_for_action_type(action.as_any().type_id())
|
||||||
} => {
|
.next()
|
||||||
if let Some(keystroke) = keystroke {
|
{
|
||||||
let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| {
|
if binding.keystrokes().len() == 1 {
|
||||||
panic!(
|
keystroke = binding.keystrokes().first()
|
||||||
"Invalid keystroke for menu item {}:{} - {:?}",
|
}
|
||||||
menu_name, name, err
|
}
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
|
if let Some(keystroke) = keystroke {
|
||||||
let mut mask = NSEventModifierFlags::empty();
|
let mut mask = NSEventModifierFlags::empty();
|
||||||
for (modifier, flag) in &[
|
for (modifier, flag) in &[
|
||||||
(keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
|
(keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
|
||||||
@ -162,7 +177,7 @@ impl MacForegroundPlatform {
|
|||||||
.initWithTitle_action_keyEquivalent_(
|
.initWithTitle_action_keyEquivalent_(
|
||||||
ns_string(name),
|
ns_string(name),
|
||||||
selector("handleGPUIMenuItem:"),
|
selector("handleGPUIMenuItem:"),
|
||||||
ns_string(&keystroke.key),
|
ns_string(key_to_native(&keystroke.key).as_ref()),
|
||||||
)
|
)
|
||||||
.autorelease();
|
.autorelease();
|
||||||
item.setKeyEquivalentModifierMask_(mask);
|
item.setKeyEquivalentModifierMask_(mask);
|
||||||
@ -239,10 +254,18 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
|
|||||||
self.0.borrow_mut().menu_command = Some(callback);
|
self.0.borrow_mut().menu_command = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_menus(&self, menus: Vec<Menu>) {
|
fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
|
||||||
|
self.0.borrow_mut().will_open_menu = Some(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
|
||||||
|
self.0.borrow_mut().validate_menu_command = Some(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &keymap::Matcher) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||||
app.setMainMenu_(self.create_menu_bar(menus));
|
app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), keystroke_matcher));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -740,6 +763,34 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
|
||||||
|
unsafe {
|
||||||
|
let mut result = false;
|
||||||
|
let platform = get_foreground_platform(this);
|
||||||
|
let mut platform = platform.0.borrow_mut();
|
||||||
|
if let Some(mut callback) = platform.validate_menu_command.take() {
|
||||||
|
let tag: NSInteger = msg_send![item, tag];
|
||||||
|
let index = tag as usize;
|
||||||
|
if let Some(action) = platform.menu_actions.get(index) {
|
||||||
|
result = callback(action.as_ref());
|
||||||
|
}
|
||||||
|
platform.validate_menu_command = Some(callback);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
|
||||||
|
unsafe {
|
||||||
|
let platform = get_foreground_platform(this);
|
||||||
|
let mut platform = platform.0.borrow_mut();
|
||||||
|
if let Some(mut callback) = platform.will_open_menu.take() {
|
||||||
|
callback();
|
||||||
|
platform.will_open_menu = Some(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn ns_string(string: &str) -> id {
|
unsafe fn ns_string(string: &str) -> id {
|
||||||
NSString::alloc(nil).init_str(string).autorelease()
|
NSString::alloc(nil).init_str(string).autorelease()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::{AppVersion, CursorStyle, WindowBounds};
|
use super::{AppVersion, CursorStyle, WindowBounds};
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::vector::{vec2f, Vector2F},
|
geometry::vector::{vec2f, Vector2F},
|
||||||
Action, ClipboardItem,
|
keymap, Action, ClipboardItem,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@ -73,8 +73,9 @@ impl super::ForegroundPlatform for ForegroundPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_menu_command(&self, _: Box<dyn FnMut(&dyn Action)>) {}
|
fn on_menu_command(&self, _: Box<dyn FnMut(&dyn Action)>) {}
|
||||||
|
fn on_validate_menu_command(&self, _: Box<dyn FnMut(&dyn Action) -> bool>) {}
|
||||||
fn set_menus(&self, _: Vec<crate::Menu>) {}
|
fn on_will_open_menu(&self, _: Box<dyn FnMut()>) {}
|
||||||
|
fn set_menus(&self, _: Vec<crate::Menu>, _: &keymap::Matcher) {}
|
||||||
|
|
||||||
fn prompt_for_paths(
|
fn prompt_for_paths(
|
||||||
&self,
|
&self,
|
||||||
|
@ -497,7 +497,7 @@ impl Project {
|
|||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub async fn test(
|
pub async fn test(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
root_paths: impl IntoIterator<Item = impl AsRef<Path>>,
|
root_paths: impl IntoIterator<Item = &Path>,
|
||||||
cx: &mut gpui::TestAppContext,
|
cx: &mut gpui::TestAppContext,
|
||||||
) -> ModelHandle<Project> {
|
) -> ModelHandle<Project> {
|
||||||
let languages = Arc::new(LanguageRegistry::test());
|
let languages = Arc::new(LanguageRegistry::test());
|
||||||
@ -528,6 +528,14 @@ impl Project {
|
|||||||
&self.languages
|
&self.languages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn client(&self) -> Arc<Client> {
|
||||||
|
self.client.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user_store(&self) -> ModelHandle<UserStore> {
|
||||||
|
self.user_store.clone()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub fn check_invariants(&self, cx: &AppContext) {
|
pub fn check_invariants(&self, cx: &AppContext) {
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
@ -5294,7 +5302,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let project = Project::test(Arc::new(RealFs), [root_link_path], cx).await;
|
let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
|
||||||
|
|
||||||
project.read_with(cx, |project, cx| {
|
project.read_with(cx, |project, cx| {
|
||||||
let tree = project.worktrees(cx).next().unwrap().read(cx);
|
let tree = project.worktrees(cx).next().unwrap().read(cx);
|
||||||
@ -5378,7 +5386,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/the-root"], cx).await;
|
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| {
|
project.update(cx, |project, _| {
|
||||||
project.languages.add(Arc::new(rust_language));
|
project.languages.add(Arc::new(rust_language));
|
||||||
project.languages.add(Arc::new(json_language));
|
project.languages.add(Arc::new(json_language));
|
||||||
@ -5714,7 +5722,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir/a.rs", "/dir/b.rs"], cx).await;
|
let project = Project::test(fs, ["/dir/a.rs".as_ref(), "/dir/b.rs".as_ref()], cx).await;
|
||||||
|
|
||||||
let buffer_a = project
|
let buffer_a = project
|
||||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||||
@ -5825,7 +5833,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir"], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
let worktree_id =
|
let worktree_id =
|
||||||
project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
|
project.read_with(cx, |p, cx| p.worktrees(cx).next().unwrap().read(cx).id());
|
||||||
@ -5947,7 +5955,7 @@ mod tests {
|
|||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
|
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir"], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
@ -6016,7 +6024,7 @@ mod tests {
|
|||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
|
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir"], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
@ -6285,7 +6293,7 @@ mod tests {
|
|||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
|
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir"], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||||
.await
|
.await
|
||||||
@ -6376,7 +6384,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir"], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||||
@ -6530,7 +6538,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir"], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||||
.await
|
.await
|
||||||
@ -6686,7 +6694,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir/b.rs"], cx).await;
|
let project = Project::test(fs, ["/dir/b.rs".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
@ -6780,7 +6788,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir"], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
|
||||||
@ -6838,7 +6846,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, ["/dir"], cx).await;
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx))
|
||||||
@ -6944,7 +6952,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
||||||
.await
|
.await
|
||||||
@ -6973,7 +6981,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir/file1"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir/file1".as_ref()], cx).await;
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
||||||
.await
|
.await
|
||||||
@ -6995,7 +7003,7 @@ mod tests {
|
|||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree("/dir", json!({})).await;
|
fs.insert_tree("/dir", json!({})).await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let buffer = project.update(cx, |project, cx| {
|
let buffer = project.update(cx, |project, cx| {
|
||||||
project.create_buffer("", None, cx).unwrap()
|
project.create_buffer("", None, cx).unwrap()
|
||||||
});
|
});
|
||||||
@ -7182,7 +7190,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
|
|
||||||
// Spawn multiple tasks to open paths, repeating some paths.
|
// Spawn multiple tasks to open paths, repeating some paths.
|
||||||
let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
|
let (buffer_a_1, buffer_b, buffer_a_2) = project.update(cx, |p, cx| {
|
||||||
@ -7227,7 +7235,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
|
|
||||||
let buffer1 = project
|
let buffer1 = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/dir/file1", cx))
|
||||||
@ -7359,7 +7367,7 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/dir/the-file", cx))
|
||||||
.await
|
.await
|
||||||
@ -7444,7 +7452,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/the-dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/the-dir".as_ref()], cx).await;
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx))
|
.update(cx, |p, cx| p.open_local_buffer("/the-dir/a.rs", cx))
|
||||||
.await
|
.await
|
||||||
@ -7708,7 +7716,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
@ -7827,7 +7835,7 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
search(&project, SearchQuery::text("TWO", false, true), cx)
|
search(&project, SearchQuery::text("TWO", false, true), cx)
|
||||||
.await
|
.await
|
||||||
|
@ -913,11 +913,14 @@ mod tests {
|
|||||||
use project::FakeFs;
|
use project::FakeFs;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{collections::HashSet, path::Path};
|
use std::{collections::HashSet, path::Path};
|
||||||
use workspace::WorkspaceParams;
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_visible_list(cx: &mut gpui::TestAppContext) {
|
async fn test_visible_list(cx: &mut gpui::TestAppContext) {
|
||||||
cx.foreground().forbid_parking();
|
cx.foreground().forbid_parking();
|
||||||
|
cx.update(|cx| {
|
||||||
|
let settings = Settings::test(cx);
|
||||||
|
cx.set_global(settings);
|
||||||
|
});
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
@ -956,9 +959,8 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/root1", "/root2"], cx).await;
|
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
|
||||||
let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
|
let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
visible_entries_as_strings(&panel, 0..50, cx),
|
visible_entries_as_strings(&panel, 0..50, cx),
|
||||||
@ -1005,6 +1007,10 @@ mod tests {
|
|||||||
#[gpui::test(iterations = 30)]
|
#[gpui::test(iterations = 30)]
|
||||||
async fn test_editing_files(cx: &mut gpui::TestAppContext) {
|
async fn test_editing_files(cx: &mut gpui::TestAppContext) {
|
||||||
cx.foreground().forbid_parking();
|
cx.foreground().forbid_parking();
|
||||||
|
cx.update(|cx| {
|
||||||
|
let settings = Settings::test(cx);
|
||||||
|
cx.set_global(settings);
|
||||||
|
});
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
@ -1043,9 +1049,8 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/root1", "/root2"], cx).await;
|
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
|
||||||
let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
|
let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
|
||||||
|
|
||||||
select_path(&panel, "root1", cx);
|
select_path(&panel, "root1", cx);
|
||||||
|
@ -295,7 +295,7 @@ mod tests {
|
|||||||
let fs = FakeFs::new(cx.background());
|
let fs = FakeFs::new(cx.background());
|
||||||
fs.insert_tree("/dir", json!({ "test.rs": "" })).await;
|
fs.insert_tree("/dir", json!({ "test.rs": "" })).await;
|
||||||
|
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||||
|
|
||||||
let _buffer = project
|
let _buffer = project
|
||||||
|
@ -848,7 +848,7 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let project = Project::test(fs.clone(), ["/dir"], cx).await;
|
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||||
let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
|
let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
|
||||||
let search_view = cx.add_view(Default::default(), |cx| {
|
let search_view = cx.add_view(Default::default(), |cx| {
|
||||||
ProjectSearchView::new(search.clone(), cx)
|
ProjectSearchView::new(search.clone(), cx)
|
||||||
|
@ -529,6 +529,16 @@ fn test_history() {
|
|||||||
assert!(buffer.end_transaction_at(now).is_none());
|
assert!(buffer.end_transaction_at(now).is_none());
|
||||||
buffer.undo();
|
buffer.undo();
|
||||||
assert_eq!(buffer.text(), "12cde6");
|
assert_eq!(buffer.text(), "12cde6");
|
||||||
|
|
||||||
|
// Redo stack gets cleared after performing an edit.
|
||||||
|
buffer.edit([(0..0, "X")]);
|
||||||
|
assert_eq!(buffer.text(), "X12cde6");
|
||||||
|
buffer.redo();
|
||||||
|
assert_eq!(buffer.text(), "X12cde6");
|
||||||
|
buffer.undo();
|
||||||
|
assert_eq!(buffer.text(), "12cde6");
|
||||||
|
buffer.undo();
|
||||||
|
assert_eq!(buffer.text(), "123456");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -216,6 +216,7 @@ impl History {
|
|||||||
self.undo_stack.pop();
|
self.undo_stack.pop();
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
self.redo_stack.clear();
|
||||||
let entry = self.undo_stack.last_mut().unwrap();
|
let entry = self.undo_stack.last_mut().unwrap();
|
||||||
entry.last_edit_at = now;
|
entry.last_edit_at = now;
|
||||||
Some(entry)
|
Some(entry)
|
||||||
@ -276,6 +277,7 @@ impl History {
|
|||||||
last_edit_at: now,
|
last_edit_at: now,
|
||||||
suppress_grouping: false,
|
suppress_grouping: false,
|
||||||
});
|
});
|
||||||
|
self.redo_stack.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_undo(&mut self, op_id: clock::Local) {
|
fn push_undo(&mut self, op_id: clock::Local) {
|
||||||
|
@ -7,7 +7,7 @@ use picker::{Picker, PickerDelegate};
|
|||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::{Theme, ThemeRegistry};
|
use theme::{Theme, ThemeRegistry};
|
||||||
use workspace::Workspace;
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
pub struct ThemeSelector {
|
pub struct ThemeSelector {
|
||||||
registry: Arc<ThemeRegistry>,
|
registry: Arc<ThemeRegistry>,
|
||||||
@ -21,10 +21,14 @@ pub struct ThemeSelector {
|
|||||||
|
|
||||||
actions!(theme_selector, [Toggle, Reload]);
|
actions!(theme_selector, [Toggle, Reload]);
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
cx.add_action(ThemeSelector::toggle);
|
|
||||||
cx.add_action(ThemeSelector::reload);
|
|
||||||
Picker::<ThemeSelector>::init(cx);
|
Picker::<ThemeSelector>::init(cx);
|
||||||
|
cx.add_action({
|
||||||
|
let theme_registry = app_state.themes.clone();
|
||||||
|
move |workspace, _: &Toggle, cx| {
|
||||||
|
ThemeSelector::toggle(workspace, theme_registry.clone(), cx)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
@ -64,8 +68,11 @@ impl ThemeSelector {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
fn toggle(
|
||||||
let themes = workspace.themes();
|
workspace: &mut Workspace,
|
||||||
|
themes: Arc<ThemeRegistry>,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
workspace.toggle_modal(cx, |_, cx| {
|
workspace.toggle_modal(cx, |_, cx| {
|
||||||
let this = cx.add_view(|cx| Self::new(themes, cx));
|
let this = cx.add_view(|cx| Self::new(themes, cx));
|
||||||
cx.subscribe(&this, Self::on_event).detach();
|
cx.subscribe(&this, Self::on_event).detach();
|
||||||
@ -73,9 +80,9 @@ impl ThemeSelector {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload(workspace: &mut Workspace, _: &Reload, cx: &mut ViewContext<Workspace>) {
|
#[cfg(debug_assertions)]
|
||||||
|
pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut MutableAppContext) {
|
||||||
let current_theme_name = cx.global::<Settings>().theme.name.clone();
|
let current_theme_name = cx.global::<Settings>().theme.name.clone();
|
||||||
let themes = workspace.themes();
|
|
||||||
themes.clear();
|
themes.clear();
|
||||||
match themes.get(¤t_theme_name) {
|
match themes.get(¤t_theme_name) {
|
||||||
Ok(theme) => {
|
Ok(theme) => {
|
||||||
|
@ -7,11 +7,12 @@ use editor::{display_map::ToDisplayPoint, Autoscroll};
|
|||||||
use gpui::{json::json, keymap::Keystroke, ViewHandle};
|
use gpui::{json::json, keymap::Keystroke, ViewHandle};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::Selection;
|
use language::Selection;
|
||||||
|
use project::Project;
|
||||||
use util::{
|
use util::{
|
||||||
set_eq,
|
set_eq,
|
||||||
test::{marked_text, marked_text_ranges_by, SetEqError},
|
test::{marked_text, marked_text_ranges_by, SetEqError},
|
||||||
};
|
};
|
||||||
use workspace::{WorkspaceHandle, WorkspaceParams};
|
use workspace::{AppState, WorkspaceHandle};
|
||||||
|
|
||||||
use crate::{state::Operator, *};
|
use crate::{state::Operator, *};
|
||||||
|
|
||||||
@ -30,7 +31,8 @@ impl<'a> VimTestContext<'a> {
|
|||||||
settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
|
settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let params = cx.update(AppState::test);
|
||||||
|
let project = Project::test(params.fs.clone(), [], cx).await;
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
cx.update_global(|settings: &mut Settings, _| {
|
cx.update_global(|settings: &mut Settings, _| {
|
||||||
@ -44,9 +46,8 @@ impl<'a> VimTestContext<'a> {
|
|||||||
.insert_tree("/root", json!({ "dir": { "test.txt": "" } }))
|
.insert_tree("/root", json!({ "dir": { "test.txt": "" } }))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
|
||||||
params
|
project
|
||||||
.project
|
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.find_or_create_local_worktree("/root", true, cx)
|
project.find_or_create_local_worktree("/root", true, cx)
|
||||||
})
|
})
|
||||||
|
@ -920,7 +920,7 @@ impl NavHistory {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::WorkspaceParams;
|
use crate::AppState;
|
||||||
use gpui::{ModelHandle, TestAppContext, ViewContext};
|
use gpui::{ModelHandle, TestAppContext, ViewContext};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
@ -929,8 +929,9 @@ mod tests {
|
|||||||
async fn test_close_items(cx: &mut TestAppContext) {
|
async fn test_close_items(cx: &mut TestAppContext) {
|
||||||
cx.foreground().forbid_parking();
|
cx.foreground().forbid_parking();
|
||||||
|
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let app_state = cx.update(AppState::test);
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(app_state.fs.clone(), None, cx).await;
|
||||||
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
let item1 = cx.add_view(window_id, |_| {
|
let item1 = cx.add_view(window_id, |_| {
|
||||||
let mut item = TestItem::new();
|
let mut item = TestItem::new();
|
||||||
item.is_dirty = true;
|
item.is_dirty = true;
|
||||||
@ -1019,8 +1020,9 @@ mod tests {
|
|||||||
async fn test_prompting_only_on_last_item_for_entry(cx: &mut TestAppContext) {
|
async fn test_prompting_only_on_last_item_for_entry(cx: &mut TestAppContext) {
|
||||||
cx.foreground().forbid_parking();
|
cx.foreground().forbid_parking();
|
||||||
|
|
||||||
let params = cx.update(WorkspaceParams::test);
|
let app_state = cx.update(AppState::test);
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||||
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
let item = cx.add_view(window_id, |_| {
|
let item = cx.add_view(window_id, |_| {
|
||||||
let mut item = TestItem::new();
|
let mut item = TestItem::new();
|
||||||
item.is_dirty = true;
|
item.is_dirty = true;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
sidebar::{Side, ToggleSidebarItem},
|
sidebar::{Side, ToggleSidebarItem},
|
||||||
AppState, ToggleFollow,
|
AppState, ToggleFollow, Workspace,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::{proto, Client, Contact};
|
use client::{proto, Client, Contact};
|
||||||
@ -77,86 +77,87 @@ impl WaitingRoom {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let project_id = contact.projects[project_index].id;
|
let project_id = contact.projects[project_index].id;
|
||||||
let client = app_state.client.clone();
|
let client = app_state.client.clone();
|
||||||
let _join_task = cx.spawn_weak({
|
let _join_task =
|
||||||
let contact = contact.clone();
|
cx.spawn_weak({
|
||||||
|this, mut cx| async move {
|
let contact = contact.clone();
|
||||||
let project = Project::remote(
|
|this, mut cx| async move {
|
||||||
project_id,
|
let project = Project::remote(
|
||||||
app_state.client.clone(),
|
project_id,
|
||||||
app_state.user_store.clone(),
|
app_state.client.clone(),
|
||||||
app_state.languages.clone(),
|
app_state.user_store.clone(),
|
||||||
app_state.fs.clone(),
|
app_state.languages.clone(),
|
||||||
&mut cx,
|
app_state.fs.clone(),
|
||||||
)
|
&mut cx,
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if let Some(this) = this.upgrade(&cx) {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.waiting = false;
|
this.waiting = false;
|
||||||
match project {
|
match project {
|
||||||
Ok(project) => {
|
Ok(project) => {
|
||||||
cx.replace_root_view(|cx| {
|
cx.replace_root_view(|cx| {
|
||||||
let mut workspace = (app_state.build_workspace)(
|
let mut workspace = Workspace::new(project, cx);
|
||||||
project.clone(),
|
(app_state.initialize_workspace)(
|
||||||
&app_state,
|
&mut workspace,
|
||||||
cx,
|
&app_state,
|
||||||
);
|
cx,
|
||||||
workspace.toggle_sidebar_item(
|
);
|
||||||
&ToggleSidebarItem {
|
workspace.toggle_sidebar_item(
|
||||||
side: Side::Left,
|
&ToggleSidebarItem {
|
||||||
item_index: 0,
|
side: Side::Left,
|
||||||
},
|
item_index: 0,
|
||||||
cx,
|
},
|
||||||
);
|
cx,
|
||||||
if let Some((host_peer_id, _)) = project
|
);
|
||||||
.read(cx)
|
if let Some((host_peer_id, _)) =
|
||||||
.collaborators()
|
workspace.project.read(cx).collaborators().iter().find(
|
||||||
.iter()
|
|(_, collaborator)| collaborator.replica_id == 0,
|
||||||
.find(|(_, collaborator)| collaborator.replica_id == 0)
|
|
||||||
{
|
|
||||||
if let Some(follow) = workspace
|
|
||||||
.toggle_follow(&ToggleFollow(*host_peer_id), cx)
|
|
||||||
{
|
|
||||||
follow.detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workspace
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(error @ _) => {
|
|
||||||
let login = &contact.user.github_login;
|
|
||||||
let message = match error {
|
|
||||||
project::JoinProjectError::HostDeclined => {
|
|
||||||
format!("@{} declined your request.", login)
|
|
||||||
}
|
|
||||||
project::JoinProjectError::HostClosedProject => {
|
|
||||||
format!(
|
|
||||||
"@{} closed their copy of {}.",
|
|
||||||
login,
|
|
||||||
humanize_list(
|
|
||||||
&contact.projects[project_index]
|
|
||||||
.worktree_root_names
|
|
||||||
)
|
)
|
||||||
)
|
{
|
||||||
}
|
if let Some(follow) = workspace
|
||||||
project::JoinProjectError::HostWentOffline => {
|
.toggle_follow(&ToggleFollow(*host_peer_id), cx)
|
||||||
format!("@{} went offline.", login)
|
{
|
||||||
}
|
follow.detach_and_log_err(cx);
|
||||||
project::JoinProjectError::Other(error) => {
|
}
|
||||||
log::error!("error joining project: {}", error);
|
}
|
||||||
"An error occurred.".to_string()
|
workspace
|
||||||
}
|
});
|
||||||
};
|
}
|
||||||
this.message = message;
|
Err(error @ _) => {
|
||||||
cx.notify();
|
let login = &contact.user.github_login;
|
||||||
|
let message = match error {
|
||||||
|
project::JoinProjectError::HostDeclined => {
|
||||||
|
format!("@{} declined your request.", login)
|
||||||
|
}
|
||||||
|
project::JoinProjectError::HostClosedProject => {
|
||||||
|
format!(
|
||||||
|
"@{} closed their copy of {}.",
|
||||||
|
login,
|
||||||
|
humanize_list(
|
||||||
|
&contact.projects[project_index]
|
||||||
|
.worktree_root_names
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
project::JoinProjectError::HostWentOffline => {
|
||||||
|
format!("@{} went offline.", login)
|
||||||
|
}
|
||||||
|
project::JoinProjectError::Other(error) => {
|
||||||
|
log::error!("error joining project: {}", error);
|
||||||
|
"An error occurred.".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.message = message;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
project_id,
|
project_id,
|
||||||
|
@ -9,8 +9,7 @@ mod waiting_room;
|
|||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use client::{
|
use client::{
|
||||||
proto, Authenticate, ChannelList, Client, Contact, PeerId, Subscription, TypedEnvelope, User,
|
proto, Authenticate, Client, Contact, PeerId, Subscription, TypedEnvelope, User, UserStore,
|
||||||
UserStore,
|
|
||||||
};
|
};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
@ -75,6 +74,8 @@ type FollowableItemBuilders = HashMap<
|
|||||||
actions!(
|
actions!(
|
||||||
workspace,
|
workspace,
|
||||||
[
|
[
|
||||||
|
Open,
|
||||||
|
OpenNew,
|
||||||
Unfollow,
|
Unfollow,
|
||||||
Save,
|
Save,
|
||||||
ActivatePreviousPane,
|
ActivatePreviousPane,
|
||||||
@ -83,16 +84,9 @@ actions!(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Open(pub Arc<AppState>);
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct OpenNew(pub Arc<AppState>);
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OpenPaths {
|
pub struct OpenPaths {
|
||||||
pub paths: Vec<PathBuf>,
|
pub paths: Vec<PathBuf>,
|
||||||
pub app_state: Arc<AppState>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -102,31 +96,37 @@ pub struct ToggleFollow(pub PeerId);
|
|||||||
pub struct JoinProject {
|
pub struct JoinProject {
|
||||||
pub contact: Arc<Contact>,
|
pub contact: Arc<Contact>,
|
||||||
pub project_index: usize,
|
pub project_index: usize,
|
||||||
pub app_state: Arc<AppState>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_internal_actions!(
|
impl_internal_actions!(workspace, [OpenPaths, ToggleFollow, JoinProject]);
|
||||||
workspace,
|
|
||||||
[Open, OpenNew, OpenPaths, ToggleFollow, JoinProject]
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
|
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
pane::init(cx);
|
pane::init(cx);
|
||||||
|
|
||||||
cx.add_global_action(open);
|
cx.add_global_action(open);
|
||||||
cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| {
|
cx.add_global_action({
|
||||||
open_paths(&action.paths, &action.app_state, cx).detach();
|
let app_state = Arc::downgrade(&app_state);
|
||||||
|
move |action: &OpenPaths, cx: &mut MutableAppContext| {
|
||||||
|
if let Some(app_state) = app_state.upgrade() {
|
||||||
|
open_paths(&action.paths, &app_state, cx).detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
cx.add_global_action(move |action: &OpenNew, cx: &mut MutableAppContext| {
|
cx.add_global_action({
|
||||||
open_new(&action.0, cx)
|
let app_state = Arc::downgrade(&app_state);
|
||||||
|
move |_: &OpenNew, cx: &mut MutableAppContext| {
|
||||||
|
if let Some(app_state) = app_state.upgrade() {
|
||||||
|
open_new(&app_state, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
cx.add_global_action(move |action: &JoinProject, cx: &mut MutableAppContext| {
|
cx.add_global_action({
|
||||||
join_project(
|
let app_state = Arc::downgrade(&app_state);
|
||||||
action.contact.clone(),
|
move |action: &JoinProject, cx: &mut MutableAppContext| {
|
||||||
action.project_index,
|
if let Some(app_state) = app_state.upgrade() {
|
||||||
&action.app_state,
|
join_project(action.contact.clone(), action.project_index, &app_state, cx);
|
||||||
cx,
|
}
|
||||||
);
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.add_async_action(Workspace::toggle_follow);
|
cx.add_async_action(Workspace::toggle_follow);
|
||||||
@ -151,6 +151,7 @@ pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
|
|||||||
workspace.activate_next_pane(cx)
|
workspace.activate_next_pane(cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let client = &app_state.client;
|
||||||
client.add_view_request_handler(Workspace::handle_follow);
|
client.add_view_request_handler(Workspace::handle_follow);
|
||||||
client.add_view_message_handler(Workspace::handle_unfollow);
|
client.add_view_message_handler(Workspace::handle_unfollow);
|
||||||
client.add_view_message_handler(Workspace::handle_update_followers);
|
client.add_view_message_handler(Workspace::handle_update_followers);
|
||||||
@ -188,10 +189,8 @@ pub struct AppState {
|
|||||||
pub client: Arc<client::Client>,
|
pub client: Arc<client::Client>,
|
||||||
pub user_store: ModelHandle<client::UserStore>,
|
pub user_store: ModelHandle<client::UserStore>,
|
||||||
pub fs: Arc<dyn fs::Fs>,
|
pub fs: Arc<dyn fs::Fs>,
|
||||||
pub channel_list: ModelHandle<client::ChannelList>,
|
|
||||||
pub build_window_options: fn() -> WindowOptions<'static>,
|
pub build_window_options: fn() -> WindowOptions<'static>,
|
||||||
pub build_workspace:
|
pub initialize_workspace: fn(&mut Workspace, &Arc<AppState>, &mut ViewContext<Workspace>),
|
||||||
fn(ModelHandle<Project>, &Arc<AppState>, &mut ViewContext<Workspace>) -> Workspace,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Item: View {
|
pub trait Item: View {
|
||||||
@ -636,20 +635,9 @@ impl Into<AnyViewHandle> for &dyn NotificationHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
impl AppState {
|
||||||
pub struct WorkspaceParams {
|
|
||||||
pub project: ModelHandle<Project>,
|
|
||||||
pub client: Arc<Client>,
|
|
||||||
pub fs: Arc<dyn Fs>,
|
|
||||||
pub languages: Arc<LanguageRegistry>,
|
|
||||||
pub themes: Arc<ThemeRegistry>,
|
|
||||||
pub user_store: ModelHandle<UserStore>,
|
|
||||||
pub channel_list: ModelHandle<ChannelList>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WorkspaceParams {
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub fn test(cx: &mut MutableAppContext) -> Self {
|
pub fn test(cx: &mut MutableAppContext) -> Arc<Self> {
|
||||||
let settings = Settings::test(cx);
|
let settings = Settings::test(cx);
|
||||||
cx.set_global(settings);
|
cx.set_global(settings);
|
||||||
|
|
||||||
@ -658,42 +646,16 @@ impl WorkspaceParams {
|
|||||||
let http_client = client::test::FakeHttpClient::with_404_response();
|
let http_client = client::test::FakeHttpClient::with_404_response();
|
||||||
let client = Client::new(http_client.clone());
|
let client = Client::new(http_client.clone());
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||||
let project = Project::local(
|
let themes = ThemeRegistry::new((), cx.font_cache().clone());
|
||||||
client.clone(),
|
Arc::new(Self {
|
||||||
user_store.clone(),
|
|
||||||
languages.clone(),
|
|
||||||
fs.clone(),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
Self {
|
|
||||||
project,
|
|
||||||
channel_list: cx
|
|
||||||
.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)),
|
|
||||||
client,
|
client,
|
||||||
themes: ThemeRegistry::new((), cx.font_cache().clone()),
|
themes,
|
||||||
fs,
|
fs,
|
||||||
languages,
|
languages,
|
||||||
user_store,
|
user_store,
|
||||||
}
|
initialize_workspace: |_, _, _| {},
|
||||||
}
|
build_window_options: || Default::default(),
|
||||||
|
})
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
pub fn local(app_state: &Arc<AppState>, cx: &mut MutableAppContext) -> Self {
|
|
||||||
Self {
|
|
||||||
project: Project::local(
|
|
||||||
app_state.client.clone(),
|
|
||||||
app_state.user_store.clone(),
|
|
||||||
app_state.languages.clone(),
|
|
||||||
app_state.fs.clone(),
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
client: app_state.client.clone(),
|
|
||||||
fs: app_state.fs.clone(),
|
|
||||||
themes: app_state.themes.clone(),
|
|
||||||
languages: app_state.languages.clone(),
|
|
||||||
user_store: app_state.user_store.clone(),
|
|
||||||
channel_list: app_state.channel_list.clone(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,7 +670,6 @@ pub struct Workspace {
|
|||||||
user_store: ModelHandle<client::UserStore>,
|
user_store: ModelHandle<client::UserStore>,
|
||||||
remote_entity_subscription: Option<Subscription>,
|
remote_entity_subscription: Option<Subscription>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
themes: Arc<ThemeRegistry>,
|
|
||||||
modal: Option<AnyViewHandle>,
|
modal: Option<AnyViewHandle>,
|
||||||
center: PaneGroup,
|
center: PaneGroup,
|
||||||
left_sidebar: ViewHandle<Sidebar>,
|
left_sidebar: ViewHandle<Sidebar>,
|
||||||
@ -744,8 +705,8 @@ enum FollowerItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
cx.observe(¶ms.project, |_, project, cx| {
|
cx.observe(&project, |_, project, cx| {
|
||||||
if project.read(cx).is_read_only() {
|
if project.read(cx).is_read_only() {
|
||||||
cx.blur();
|
cx.blur();
|
||||||
}
|
}
|
||||||
@ -753,7 +714,7 @@ impl Workspace {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.subscribe(¶ms.project, move |this, project, event, cx| {
|
cx.subscribe(&project, move |this, project, event, cx| {
|
||||||
match event {
|
match event {
|
||||||
project::Event::RemoteIdChanged(remote_id) => {
|
project::Event::RemoteIdChanged(remote_id) => {
|
||||||
this.project_remote_id_changed(*remote_id, cx);
|
this.project_remote_id_changed(*remote_id, cx);
|
||||||
@ -785,8 +746,11 @@ impl Workspace {
|
|||||||
cx.focus(&pane);
|
cx.focus(&pane);
|
||||||
cx.emit(Event::PaneAdded(pane.clone()));
|
cx.emit(Event::PaneAdded(pane.clone()));
|
||||||
|
|
||||||
let mut current_user = params.user_store.read(cx).watch_current_user().clone();
|
let fs = project.read(cx).fs().clone();
|
||||||
let mut connection_status = params.client.status().clone();
|
let user_store = project.read(cx).user_store();
|
||||||
|
let client = project.read(cx).client();
|
||||||
|
let mut current_user = user_store.read(cx).watch_current_user().clone();
|
||||||
|
let mut connection_status = client.status().clone();
|
||||||
let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
|
let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
|
||||||
current_user.recv().await;
|
current_user.recv().await;
|
||||||
connection_status.recv().await;
|
connection_status.recv().await;
|
||||||
@ -826,14 +790,13 @@ impl Workspace {
|
|||||||
active_pane: pane.clone(),
|
active_pane: pane.clone(),
|
||||||
status_bar,
|
status_bar,
|
||||||
notifications: Default::default(),
|
notifications: Default::default(),
|
||||||
client: params.client.clone(),
|
client,
|
||||||
remote_entity_subscription: None,
|
remote_entity_subscription: None,
|
||||||
user_store: params.user_store.clone(),
|
user_store,
|
||||||
fs: params.fs.clone(),
|
fs,
|
||||||
themes: params.themes.clone(),
|
|
||||||
left_sidebar,
|
left_sidebar,
|
||||||
right_sidebar,
|
right_sidebar,
|
||||||
project: params.project.clone(),
|
project,
|
||||||
leader_state: Default::default(),
|
leader_state: Default::default(),
|
||||||
follower_states_by_leader: Default::default(),
|
follower_states_by_leader: Default::default(),
|
||||||
last_leaders_by_pane: Default::default(),
|
last_leaders_by_pane: Default::default(),
|
||||||
@ -867,10 +830,6 @@ impl Workspace {
|
|||||||
&self.project
|
&self.project
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn themes(&self) -> Arc<ThemeRegistry> {
|
|
||||||
self.themes.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn worktrees<'a>(
|
pub fn worktrees<'a>(
|
||||||
&self,
|
&self,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
@ -2203,8 +2162,7 @@ impl std::fmt::Debug for OpenPaths {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(action: &Open, cx: &mut MutableAppContext) {
|
fn open(_: &Open, cx: &mut MutableAppContext) {
|
||||||
let app_state = action.0.clone();
|
|
||||||
let mut paths = cx.prompt_for_paths(PathPromptOptions {
|
let mut paths = cx.prompt_for_paths(PathPromptOptions {
|
||||||
files: true,
|
files: true,
|
||||||
directories: true,
|
directories: true,
|
||||||
@ -2212,7 +2170,7 @@ fn open(action: &Open, cx: &mut MutableAppContext) {
|
|||||||
});
|
});
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
if let Some(paths) = paths.recv().await.flatten() {
|
if let Some(paths) = paths.recv().await.flatten() {
|
||||||
cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths, app_state }));
|
cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
@ -2260,14 +2218,17 @@ pub fn open_paths(
|
|||||||
.contains(&false);
|
.contains(&false);
|
||||||
|
|
||||||
cx.add_window((app_state.build_window_options)(), |cx| {
|
cx.add_window((app_state.build_window_options)(), |cx| {
|
||||||
let project = Project::local(
|
let mut workspace = Workspace::new(
|
||||||
app_state.client.clone(),
|
Project::local(
|
||||||
app_state.user_store.clone(),
|
app_state.client.clone(),
|
||||||
app_state.languages.clone(),
|
app_state.user_store.clone(),
|
||||||
app_state.fs.clone(),
|
app_state.languages.clone(),
|
||||||
|
app_state.fs.clone(),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
let mut workspace = (app_state.build_workspace)(project, &app_state, cx);
|
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
|
||||||
if contains_directory {
|
if contains_directory {
|
||||||
workspace.toggle_sidebar_item(
|
workspace.toggle_sidebar_item(
|
||||||
&ToggleSidebarItem {
|
&ToggleSidebarItem {
|
||||||
@ -2313,14 +2274,18 @@ pub fn join_project(
|
|||||||
|
|
||||||
fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||||
let project = Project::local(
|
let mut workspace = Workspace::new(
|
||||||
app_state.client.clone(),
|
Project::local(
|
||||||
app_state.user_store.clone(),
|
app_state.client.clone(),
|
||||||
app_state.languages.clone(),
|
app_state.user_store.clone(),
|
||||||
app_state.fs.clone(),
|
app_state.languages.clone(),
|
||||||
|
app_state.fs.clone(),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
(app_state.build_workspace)(project, &app_state, cx)
|
(app_state.initialize_workspace)(&mut workspace, app_state, cx);
|
||||||
|
workspace
|
||||||
});
|
});
|
||||||
cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
|
cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,31 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.14");
|
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.14");
|
||||||
|
|
||||||
|
let output = Command::new("npm")
|
||||||
|
.current_dir("../../styles")
|
||||||
|
.args(["ci"])
|
||||||
|
.output()
|
||||||
|
.expect("failed to run npm");
|
||||||
|
if !output.status.success() {
|
||||||
|
panic!(
|
||||||
|
"failed to install theme dependencies {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("npm")
|
||||||
|
.current_dir("../../styles")
|
||||||
|
.args(["run", "build-themes"])
|
||||||
|
.output()
|
||||||
|
.expect("failed to run npm");
|
||||||
|
if !output.status.success() {
|
||||||
|
panic!(
|
||||||
|
"build-themes script failed {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=../../styles");
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ use cli::{
|
|||||||
use client::{
|
use client::{
|
||||||
self,
|
self,
|
||||||
http::{self, HttpClient},
|
http::{self, HttpClient},
|
||||||
ChannelList, UserStore, ZED_SECRET_CLIENT_TOKEN,
|
UserStore, ZED_SECRET_CLIENT_TOKEN,
|
||||||
};
|
};
|
||||||
use fs::OpenOptions;
|
use fs::OpenOptions;
|
||||||
use futures::{
|
use futures::{
|
||||||
@ -40,9 +40,9 @@ use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
|
|||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
use workspace::{self, AppState, OpenNew, OpenPaths};
|
use workspace::{self, AppState, OpenNew, OpenPaths};
|
||||||
use zed::{
|
use zed::{
|
||||||
self, build_window_options, build_workspace,
|
self, build_window_options,
|
||||||
fs::RealFs,
|
fs::RealFs,
|
||||||
languages, menus,
|
initialize_workspace, languages, menus,
|
||||||
settings_file::{settings_from_files, watch_keymap_file, WatchedJsonFile},
|
settings_file::{settings_from_files, watch_keymap_file, WatchedJsonFile},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,15 +133,12 @@ fn main() {
|
|||||||
let client = client::Client::new(http.clone());
|
let client = client::Client::new(http.clone());
|
||||||
let mut languages = languages::build_language_registry(login_shell_env_loaded);
|
let mut languages = languages::build_language_registry(login_shell_env_loaded);
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
|
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
|
||||||
let channel_list =
|
|
||||||
cx.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx));
|
|
||||||
|
|
||||||
auto_update::init(http, client::ZED_SERVER_URL.clone(), cx);
|
auto_update::init(http, client::ZED_SERVER_URL.clone(), cx);
|
||||||
project::Project::init(&client);
|
project::Project::init(&client);
|
||||||
client::Channel::init(&client);
|
client::Channel::init(&client);
|
||||||
client::init(client.clone(), cx);
|
client::init(client.clone(), cx);
|
||||||
command_palette::init(cx);
|
command_palette::init(cx);
|
||||||
workspace::init(&client, cx);
|
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
go_to_line::init(cx);
|
go_to_line::init(cx);
|
||||||
file_finder::init(cx);
|
file_finder::init(cx);
|
||||||
@ -162,6 +159,8 @@ fn main() {
|
|||||||
cx.font_cache().clone(),
|
cx.font_cache().clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
|
||||||
|
.detach();
|
||||||
cx.spawn(|cx| watch_keymap_file(keymap_file, cx)).detach();
|
cx.spawn(|cx| watch_keymap_file(keymap_file, cx)).detach();
|
||||||
|
|
||||||
let settings = cx.background().block(settings_rx.next()).unwrap();
|
let settings = cx.background().block(settings_rx.next()).unwrap();
|
||||||
@ -190,33 +189,33 @@ fn main() {
|
|||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
languages,
|
languages,
|
||||||
themes,
|
themes,
|
||||||
channel_list,
|
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
user_store,
|
user_store,
|
||||||
fs,
|
fs,
|
||||||
build_window_options,
|
build_window_options,
|
||||||
build_workspace,
|
initialize_workspace,
|
||||||
});
|
});
|
||||||
|
workspace::init(app_state.clone(), cx);
|
||||||
journal::init(app_state.clone(), cx);
|
journal::init(app_state.clone(), cx);
|
||||||
theme_selector::init(cx);
|
theme_selector::init(app_state.clone(), cx);
|
||||||
zed::init(&app_state, cx);
|
zed::init(&app_state, cx);
|
||||||
|
|
||||||
cx.set_menus(menus::menus(&app_state.clone()));
|
cx.set_menus(menus::menus());
|
||||||
|
|
||||||
if stdout_is_a_pty() {
|
if stdout_is_a_pty() {
|
||||||
cx.platform().activate(true);
|
cx.platform().activate(true);
|
||||||
let paths = collect_path_args();
|
let paths = collect_path_args();
|
||||||
if paths.is_empty() {
|
if paths.is_empty() {
|
||||||
cx.dispatch_global_action(OpenNew(app_state.clone()));
|
cx.dispatch_global_action(OpenNew);
|
||||||
} else {
|
} else {
|
||||||
cx.dispatch_global_action(OpenPaths { paths, app_state });
|
cx.dispatch_global_action(OpenPaths { paths });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Ok(Some(connection)) = cli_connections_rx.try_next() {
|
if let Ok(Some(connection)) = cli_connections_rx.try_next() {
|
||||||
cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx))
|
cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx))
|
||||||
.detach();
|
.detach();
|
||||||
} else {
|
} else {
|
||||||
cx.dispatch_global_action(OpenNew(app_state.clone()));
|
cx.dispatch_global_action(OpenNew);
|
||||||
}
|
}
|
||||||
cx.spawn(|cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
while let Some(connection) = cli_connections_rx.next().await {
|
while let Some(connection) = cli_connections_rx.next().await {
|
||||||
@ -440,6 +439,43 @@ fn load_embedded_fonts(app: &App) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
async fn watch_themes(
|
||||||
|
fs: Arc<dyn Fs>,
|
||||||
|
themes: Arc<ThemeRegistry>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Option<()> {
|
||||||
|
let mut events = fs
|
||||||
|
.watch("styles/src".as_ref(), Duration::from_millis(100))
|
||||||
|
.await;
|
||||||
|
while let Some(_) = events.next().await {
|
||||||
|
let output = Command::new("npm")
|
||||||
|
.current_dir("styles")
|
||||||
|
.args(["run", "build-themes"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.log_err()?;
|
||||||
|
if output.status.success() {
|
||||||
|
cx.update(|cx| theme_selector::ThemeSelector::reload(themes.clone(), cx))
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"build-themes script failed {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
async fn watch_themes(
|
||||||
|
_fs: Arc<dyn Fs>,
|
||||||
|
_themes: Arc<ThemeRegistry>,
|
||||||
|
_cx: AsyncAppContext,
|
||||||
|
) -> Option<()> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn load_config_files(
|
fn load_config_files(
|
||||||
app: &App,
|
app: &App,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -1,33 +1,27 @@
|
|||||||
use crate::AppState;
|
|
||||||
use gpui::{Menu, MenuItem};
|
use gpui::{Menu, MenuItem};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
pub fn menus() -> Vec<Menu<'static>> {
|
||||||
vec![
|
vec![
|
||||||
Menu {
|
Menu {
|
||||||
name: "Zed",
|
name: "Zed",
|
||||||
items: vec![
|
items: vec![
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "About Zed…",
|
name: "About Zed…",
|
||||||
keystroke: None,
|
|
||||||
action: Box::new(super::About),
|
action: Box::new(super::About),
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Check for Updates",
|
name: "Check for Updates",
|
||||||
keystroke: None,
|
|
||||||
action: Box::new(auto_update::Check),
|
action: Box::new(auto_update::Check),
|
||||||
},
|
},
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Install CLI",
|
name: "Install CLI",
|
||||||
keystroke: None,
|
|
||||||
action: Box::new(super::InstallCommandLineInterface),
|
action: Box::new(super::InstallCommandLineInterface),
|
||||||
},
|
},
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Quit",
|
name: "Quit",
|
||||||
keystroke: Some("cmd-q"),
|
|
||||||
action: Box::new(super::Quit),
|
action: Box::new(super::Quit),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -37,14 +31,20 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
|||||||
items: vec![
|
items: vec![
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "New",
|
name: "New",
|
||||||
keystroke: Some("cmd-n"),
|
action: Box::new(workspace::OpenNew),
|
||||||
action: Box::new(workspace::OpenNew(state.clone())),
|
|
||||||
},
|
},
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Open…",
|
name: "Open…",
|
||||||
keystroke: Some("cmd-o"),
|
action: Box::new(workspace::Open),
|
||||||
action: Box::new(workspace::Open(state.clone())),
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Save",
|
||||||
|
action: Box::new(workspace::Save),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Close Editor",
|
||||||
|
action: Box::new(workspace::CloseActiveItem),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -53,30 +53,160 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
|
|||||||
items: vec![
|
items: vec![
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Undo",
|
name: "Undo",
|
||||||
keystroke: Some("cmd-z"),
|
|
||||||
action: Box::new(editor::Undo),
|
action: Box::new(editor::Undo),
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Redo",
|
name: "Redo",
|
||||||
keystroke: Some("cmd-Z"),
|
|
||||||
action: Box::new(editor::Redo),
|
action: Box::new(editor::Redo),
|
||||||
},
|
},
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Cut",
|
name: "Cut",
|
||||||
keystroke: Some("cmd-x"),
|
|
||||||
action: Box::new(editor::Cut),
|
action: Box::new(editor::Cut),
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Copy",
|
name: "Copy",
|
||||||
keystroke: Some("cmd-c"),
|
|
||||||
action: Box::new(editor::Copy),
|
action: Box::new(editor::Copy),
|
||||||
},
|
},
|
||||||
MenuItem::Action {
|
MenuItem::Action {
|
||||||
name: "Paste",
|
name: "Paste",
|
||||||
keystroke: Some("cmd-v"),
|
|
||||||
action: Box::new(editor::Paste),
|
action: Box::new(editor::Paste),
|
||||||
},
|
},
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Find",
|
||||||
|
action: Box::new(search::buffer_search::Deploy { focus: true }),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Find In Project",
|
||||||
|
action: Box::new(search::project_search::Deploy),
|
||||||
|
},
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Toggle Line Comment",
|
||||||
|
action: Box::new(editor::ToggleComments),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Menu {
|
||||||
|
name: "Selection",
|
||||||
|
items: vec![
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Select All",
|
||||||
|
action: Box::new(editor::SelectAll),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Expand Selection",
|
||||||
|
action: Box::new(editor::SelectLargerSyntaxNode),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Shrink Selection",
|
||||||
|
action: Box::new(editor::SelectSmallerSyntaxNode),
|
||||||
|
},
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Add Cursor Above",
|
||||||
|
action: Box::new(editor::AddSelectionAbove),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Add Cursor Below",
|
||||||
|
action: Box::new(editor::AddSelectionBelow),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Select Next Occurrence",
|
||||||
|
action: Box::new(editor::SelectNext {
|
||||||
|
replace_newest: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Move Line Up",
|
||||||
|
action: Box::new(editor::MoveLineUp),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Move Line Down",
|
||||||
|
action: Box::new(editor::MoveLineDown),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Duplicate Selection",
|
||||||
|
action: Box::new(editor::DuplicateLine),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Menu {
|
||||||
|
name: "View",
|
||||||
|
items: vec![
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Zoom In",
|
||||||
|
action: Box::new(super::IncreaseBufferFontSize),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Zoom Out",
|
||||||
|
action: Box::new(super::DecreaseBufferFontSize),
|
||||||
|
},
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Project Browser",
|
||||||
|
action: Box::new(workspace::sidebar::ToggleSidebarItemFocus {
|
||||||
|
side: workspace::sidebar::Side::Left,
|
||||||
|
item_index: 0,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Command Palette",
|
||||||
|
action: Box::new(command_palette::Toggle),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Diagnostics",
|
||||||
|
action: Box::new(diagnostics::Deploy),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Menu {
|
||||||
|
name: "Go",
|
||||||
|
items: vec![
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Back",
|
||||||
|
action: Box::new(workspace::GoBack { pane: None }),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Forward",
|
||||||
|
action: Box::new(workspace::GoForward { pane: None }),
|
||||||
|
},
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Go to File",
|
||||||
|
action: Box::new(file_finder::Toggle),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Go to Symbol in Project",
|
||||||
|
action: Box::new(project_symbols::Toggle),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Go to Symbol in Editor",
|
||||||
|
action: Box::new(outline::Toggle),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Go to Definition",
|
||||||
|
action: Box::new(editor::GoToDefinition),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Go to References",
|
||||||
|
action: Box::new(editor::FindAllReferences),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Go to Line/Column",
|
||||||
|
action: Box::new(go_to_line::Toggle),
|
||||||
|
},
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Next Problem",
|
||||||
|
action: Box::new(editor::GoToNextDiagnostic),
|
||||||
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Previous Problem",
|
||||||
|
action: Box::new(editor::GoToPrevDiagnostic),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -1,13 +1,3 @@
|
|||||||
use crate::{build_window_options, build_workspace, AppState};
|
|
||||||
use assets::Assets;
|
|
||||||
use client::{test::FakeHttpClient, ChannelList, Client, UserStore};
|
|
||||||
use gpui::MutableAppContext;
|
|
||||||
use language::LanguageRegistry;
|
|
||||||
use project::fs::FakeFs;
|
|
||||||
use settings::Settings;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use theme::ThemeRegistry;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
@ -15,32 +5,3 @@ fn init_logger() {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {
|
|
||||||
let settings = Settings::test(cx);
|
|
||||||
editor::init(cx);
|
|
||||||
cx.set_global(settings);
|
|
||||||
let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
|
|
||||||
let http = FakeHttpClient::with_404_response();
|
|
||||||
let client = Client::new(http.clone());
|
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
|
||||||
let languages = LanguageRegistry::test();
|
|
||||||
languages.add(Arc::new(language::Language::new(
|
|
||||||
language::LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(tree_sitter_rust::language()),
|
|
||||||
)));
|
|
||||||
Arc::new(AppState {
|
|
||||||
themes,
|
|
||||||
languages: Arc::new(languages),
|
|
||||||
channel_list: cx.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)),
|
|
||||||
client,
|
|
||||||
user_store,
|
|
||||||
fs: FakeFs::new(cx.background().clone()),
|
|
||||||
build_window_options,
|
|
||||||
build_workspace,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -15,7 +15,7 @@ use gpui::{
|
|||||||
actions,
|
actions,
|
||||||
geometry::vector::vec2f,
|
geometry::vector::vec2f,
|
||||||
platform::{WindowBounds, WindowOptions},
|
platform::{WindowBounds, WindowOptions},
|
||||||
AsyncAppContext, ModelHandle, ViewContext,
|
AsyncAppContext, ViewContext,
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
pub use lsp;
|
pub use lsp;
|
||||||
@ -31,7 +31,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
pub use workspace;
|
pub use workspace;
|
||||||
use workspace::{AppState, Workspace, WorkspaceParams};
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
zed,
|
zed,
|
||||||
@ -115,13 +115,13 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
|
|||||||
settings::KeymapFileContent::load_defaults(cx);
|
settings::KeymapFileContent::load_defaults(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_workspace(
|
pub fn initialize_workspace(
|
||||||
project: ModelHandle<Project>,
|
workspace: &mut Workspace,
|
||||||
app_state: &Arc<AppState>,
|
app_state: &Arc<AppState>,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) -> Workspace {
|
) {
|
||||||
cx.subscribe(&cx.handle(), {
|
cx.subscribe(&cx.handle(), {
|
||||||
let project = project.clone();
|
let project = workspace.project().clone();
|
||||||
move |_, _, event, cx| {
|
move |_, _, event, cx| {
|
||||||
if let workspace::Event::PaneAdded(pane) = event {
|
if let workspace::Event::PaneAdded(pane) = event {
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
@ -139,22 +139,12 @@ pub fn build_workspace(
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let workspace_params = WorkspaceParams {
|
cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone()));
|
||||||
project,
|
|
||||||
client: app_state.client.clone(),
|
|
||||||
fs: app_state.fs.clone(),
|
|
||||||
languages: app_state.languages.clone(),
|
|
||||||
themes: app_state.themes.clone(),
|
|
||||||
user_store: app_state.user_store.clone(),
|
|
||||||
channel_list: app_state.channel_list.clone(),
|
|
||||||
};
|
|
||||||
let workspace = Workspace::new(&workspace_params, cx);
|
|
||||||
let project = workspace.project().clone();
|
|
||||||
|
|
||||||
let theme_names = app_state.themes.list().collect();
|
let theme_names = app_state.themes.list().collect();
|
||||||
let language_names = app_state.languages.language_names();
|
let language_names = app_state.languages.language_names();
|
||||||
|
|
||||||
project.update(cx, |project, cx| {
|
workspace.project().update(cx, |project, cx| {
|
||||||
let action_names = cx.all_action_names().collect::<Vec<_>>();
|
let action_names = cx.all_action_names().collect::<Vec<_>>();
|
||||||
project.set_language_server_settings(serde_json::json!({
|
project.set_language_server_settings(serde_json::json!({
|
||||||
"json": {
|
"json": {
|
||||||
@ -172,9 +162,10 @@ pub fn build_workspace(
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
let project_panel = ProjectPanel::new(project, cx);
|
let project_panel = ProjectPanel::new(workspace.project().clone(), cx);
|
||||||
let contact_panel =
|
let contact_panel = cx.add_view(|cx| {
|
||||||
cx.add_view(|cx| ContactsPanel::new(app_state.clone(), workspace.weak_handle(), cx));
|
ContactsPanel::new(app_state.user_store.clone(), workspace.weak_handle(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
workspace.left_sidebar().update(cx, |sidebar, cx| {
|
workspace.left_sidebar().update(cx, |sidebar, cx| {
|
||||||
sidebar.add_item("icons/folder-tree-solid-14.svg", project_panel.into(), cx)
|
sidebar.add_item("icons/folder-tree-solid-14.svg", project_panel.into(), cx)
|
||||||
@ -196,8 +187,6 @@ pub fn build_workspace(
|
|||||||
status_bar.add_right_item(cursor_position, cx);
|
status_bar.add_right_item(cursor_position, cx);
|
||||||
status_bar.add_right_item(auto_update, cx);
|
status_bar.add_right_item(auto_update, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_window_options() -> WindowOptions<'static> {
|
pub fn build_window_options() -> WindowOptions<'static> {
|
||||||
@ -287,14 +276,18 @@ fn open_config_file(
|
|||||||
workspace.open_paths(vec![path.to_path_buf()], cx)
|
workspace.open_paths(vec![path.to_path_buf()], cx)
|
||||||
} else {
|
} else {
|
||||||
let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||||
let project = Project::local(
|
let mut workspace = Workspace::new(
|
||||||
app_state.client.clone(),
|
Project::local(
|
||||||
app_state.user_store.clone(),
|
app_state.client.clone(),
|
||||||
app_state.languages.clone(),
|
app_state.user_store.clone(),
|
||||||
app_state.fs.clone(),
|
app_state.languages.clone(),
|
||||||
|
app_state.fs.clone(),
|
||||||
|
cx,
|
||||||
|
),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
(app_state.build_workspace)(project, &app_state, cx)
|
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
|
||||||
|
workspace
|
||||||
});
|
});
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
workspace.open_paths(vec![path.to_path_buf()], cx)
|
workspace.open_paths(vec![path.to_path_buf()], cx)
|
||||||
@ -313,43 +306,45 @@ mod tests {
|
|||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use editor::{Autoscroll, DisplayPoint, Editor};
|
use editor::{Autoscroll, DisplayPoint, Editor};
|
||||||
use gpui::{AssetSource, MutableAppContext, TestAppContext, ViewHandle};
|
use gpui::{AssetSource, MutableAppContext, TestAppContext, ViewHandle};
|
||||||
use project::{Fs, ProjectPath};
|
use project::ProjectPath;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use test::test_app_state;
|
|
||||||
use theme::{Theme, ThemeRegistry, DEFAULT_THEME_NAME};
|
use theme::{Theme, ThemeRegistry, DEFAULT_THEME_NAME};
|
||||||
use util::test::temp_tree;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
open_paths, pane, Item, ItemHandle, OpenNew, Pane, SplitDirection, WorkspaceHandle,
|
open_paths, pane, Item, ItemHandle, OpenNew, Pane, SplitDirection, WorkspaceHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_open_paths_action(cx: &mut TestAppContext) {
|
async fn test_open_paths_action(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(test_app_state);
|
let app_state = init(cx);
|
||||||
let dir = temp_tree(json!({
|
app_state
|
||||||
"a": {
|
.fs
|
||||||
"aa": null,
|
.as_fake()
|
||||||
"ab": null,
|
.insert_tree(
|
||||||
},
|
"/root",
|
||||||
"b": {
|
json!({
|
||||||
"ba": null,
|
"a": {
|
||||||
"bb": null,
|
"aa": null,
|
||||||
},
|
"ab": null,
|
||||||
"c": {
|
},
|
||||||
"ca": null,
|
"b": {
|
||||||
"cb": null,
|
"ba": null,
|
||||||
},
|
"bb": null,
|
||||||
}));
|
},
|
||||||
|
"c": {
|
||||||
|
"ca": null,
|
||||||
|
"cb": null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(
|
open_paths(
|
||||||
&[
|
&[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
|
||||||
dir.path().join("a").to_path_buf(),
|
|
||||||
dir.path().join("b").to_path_buf(),
|
|
||||||
],
|
|
||||||
&app_state,
|
&app_state,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@ -357,7 +352,7 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
assert_eq!(cx.window_ids().len(), 1);
|
assert_eq!(cx.window_ids().len(), 1);
|
||||||
|
|
||||||
cx.update(|cx| open_paths(&[dir.path().join("a").to_path_buf()], &app_state, cx))
|
cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, cx))
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(cx.window_ids().len(), 1);
|
assert_eq!(cx.window_ids().len(), 1);
|
||||||
let workspace_1 = cx.root_view::<Workspace>(cx.window_ids()[0]).unwrap();
|
let workspace_1 = cx.root_view::<Workspace>(cx.window_ids()[0]).unwrap();
|
||||||
@ -369,10 +364,7 @@ mod tests {
|
|||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
open_paths(
|
open_paths(
|
||||||
&[
|
&[PathBuf::from("/root/b"), PathBuf::from("/root/c")],
|
||||||
dir.path().join("b").to_path_buf(),
|
|
||||||
dir.path().join("c").to_path_buf(),
|
|
||||||
],
|
|
||||||
&app_state,
|
&app_state,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
@ -383,11 +375,8 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_new_empty_workspace(cx: &mut TestAppContext) {
|
async fn test_new_empty_workspace(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(test_app_state);
|
let app_state = init(cx);
|
||||||
cx.update(|cx| {
|
cx.dispatch_global_action(workspace::OpenNew);
|
||||||
workspace::init(&app_state.client, cx);
|
|
||||||
});
|
|
||||||
cx.dispatch_global_action(workspace::OpenNew(app_state.clone()));
|
|
||||||
let window_id = *cx.window_ids().first().unwrap();
|
let window_id = *cx.window_ids().first().unwrap();
|
||||||
let workspace = cx.root_view::<Workspace>(window_id).unwrap();
|
let workspace = cx.root_view::<Workspace>(window_id).unwrap();
|
||||||
let editor = workspace.update(cx, |workspace, cx| {
|
let editor = workspace.update(cx, |workspace, cx| {
|
||||||
@ -414,7 +403,7 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_open_entry(cx: &mut TestAppContext) {
|
async fn test_open_entry(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(test_app_state);
|
let app_state = init(cx);
|
||||||
app_state
|
app_state
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
@ -429,18 +418,10 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
|
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
|
||||||
params
|
|
||||||
.project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree("/root", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||||
.await;
|
let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
|
|
||||||
let entries = cx.read(|cx| workspace.file_project_paths(cx));
|
let entries = cx.read(|cx| workspace.file_project_paths(cx));
|
||||||
let file1 = entries[0].clone();
|
let file1 = entries[0].clone();
|
||||||
let file2 = entries[1].clone();
|
let file2 = entries[1].clone();
|
||||||
@ -535,7 +516,8 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_open_paths(cx: &mut TestAppContext) {
|
async fn test_open_paths(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(test_app_state);
|
let app_state = init(cx);
|
||||||
|
|
||||||
let fs = app_state.fs.as_fake();
|
let fs = app_state.fs.as_fake();
|
||||||
fs.insert_dir("/dir1").await;
|
fs.insert_dir("/dir1").await;
|
||||||
fs.insert_dir("/dir2").await;
|
fs.insert_dir("/dir2").await;
|
||||||
@ -544,17 +526,8 @@ mod tests {
|
|||||||
fs.insert_file("/dir2/b.txt", "".into()).await;
|
fs.insert_file("/dir2/b.txt", "".into()).await;
|
||||||
fs.insert_file("/dir3/c.txt", "".into()).await;
|
fs.insert_file("/dir3/c.txt", "".into()).await;
|
||||||
|
|
||||||
let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
|
let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await;
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
params
|
|
||||||
.project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree("/dir1", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Open a file within an existing worktree.
|
// Open a file within an existing worktree.
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
@ -655,19 +628,15 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_save_conflicting_item(cx: &mut TestAppContext) {
|
async fn test_save_conflicting_item(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(test_app_state);
|
let app_state = init(cx);
|
||||||
let fs = app_state.fs.as_fake();
|
app_state
|
||||||
fs.insert_tree("/root", json!({ "a.txt": "" })).await;
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree("/root", json!({ "a.txt": "" }))
|
||||||
|
.await;
|
||||||
|
|
||||||
let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
|
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
params
|
|
||||||
.project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree("/root", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Open a file within an existing worktree.
|
// Open a file within an existing worktree.
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
@ -687,7 +656,11 @@ mod tests {
|
|||||||
editor.handle_input(&editor::Input("x".into()), cx)
|
editor.handle_input(&editor::Input("x".into()), cx)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
fs.insert_file("/root/a.txt", "changed".to_string()).await;
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_file("/root/a.txt", "changed".to_string())
|
||||||
|
.await;
|
||||||
editor
|
editor
|
||||||
.condition(&cx, |editor, cx| editor.has_conflict(cx))
|
.condition(&cx, |editor, cx| editor.has_conflict(cx))
|
||||||
.await;
|
.await;
|
||||||
@ -704,21 +677,16 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
|
async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(test_app_state);
|
let app_state = init(cx);
|
||||||
app_state.fs.as_fake().insert_dir("/root").await;
|
app_state.fs.as_fake().insert_dir("/root").await;
|
||||||
let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
|
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||||
params
|
project.update(cx, |project, _| project.languages().add(rust_lang()));
|
||||||
.project
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree("/root", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
|
let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
|
||||||
|
|
||||||
// Create a new untitled buffer
|
// Create a new untitled buffer
|
||||||
cx.dispatch_action(window_id, OpenNew(app_state.clone()));
|
cx.dispatch_action(window_id, OpenNew);
|
||||||
let editor = workspace.read_with(cx, |workspace, cx| {
|
let editor = workspace.read_with(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
.active_item(cx)
|
.active_item(cx)
|
||||||
@ -773,18 +741,11 @@ mod tests {
|
|||||||
|
|
||||||
// Open the same newly-created file in another pane item. The new editor should reuse
|
// Open the same newly-created file in another pane item. The new editor should reuse
|
||||||
// the same buffer.
|
// the same buffer.
|
||||||
cx.dispatch_action(window_id, OpenNew(app_state.clone()));
|
cx.dispatch_action(window_id, OpenNew);
|
||||||
workspace
|
workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||||
workspace.open_path(
|
workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), true, cx)
|
||||||
ProjectPath {
|
|
||||||
worktree_id: worktree.read(cx).id(),
|
|
||||||
path: Path::new("the-new-name.rs").into(),
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -805,13 +766,15 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
|
async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(test_app_state);
|
let app_state = init(cx);
|
||||||
app_state.fs.as_fake().insert_dir("/root").await;
|
app_state.fs.as_fake().insert_dir("/root").await;
|
||||||
let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
|
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||||
|
project.update(cx, |project, _| project.languages().add(rust_lang()));
|
||||||
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
|
|
||||||
// Create a new untitled buffer
|
// Create a new untitled buffer
|
||||||
cx.dispatch_action(window_id, OpenNew(app_state.clone()));
|
cx.dispatch_action(window_id, OpenNew);
|
||||||
let editor = workspace.read_with(cx, |workspace, cx| {
|
let editor = workspace.read_with(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
.active_item(cx)
|
.active_item(cx)
|
||||||
@ -842,10 +805,9 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_pane_actions(cx: &mut TestAppContext) {
|
async fn test_pane_actions(cx: &mut TestAppContext) {
|
||||||
cx.foreground().forbid_parking();
|
init(cx);
|
||||||
|
|
||||||
cx.update(|cx| pane::init(cx));
|
let app_state = cx.update(AppState::test);
|
||||||
let app_state = cx.update(test_app_state);
|
|
||||||
app_state
|
app_state
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
@ -861,17 +823,9 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
|
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||||
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
|
||||||
params
|
|
||||||
.project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree("/root", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
|
||||||
.await;
|
|
||||||
let entries = cx.read(|cx| workspace.file_project_paths(cx));
|
let entries = cx.read(|cx| workspace.file_project_paths(cx));
|
||||||
let file1 = entries[0].clone();
|
let file1 = entries[0].clone();
|
||||||
|
|
||||||
@ -926,7 +880,7 @@ mod tests {
|
|||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_navigation(cx: &mut TestAppContext) {
|
async fn test_navigation(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(test_app_state);
|
let app_state = init(cx);
|
||||||
app_state
|
app_state
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
@ -941,17 +895,10 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
|
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
|
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||||
params
|
let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
|
||||||
.project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree("/root", true, cx)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
|
||||||
.await;
|
|
||||||
let entries = cx.read(|cx| workspace.file_project_paths(cx));
|
let entries = cx.read(|cx| workspace.file_project_paths(cx));
|
||||||
let file1 = entries[0].clone();
|
let file1 = entries[0].clone();
|
||||||
let file2 = entries[1].clone();
|
let file2 = entries[1].clone();
|
||||||
@ -990,7 +937,7 @@ mod tests {
|
|||||||
editor.newline(&Default::default(), cx);
|
editor.newline(&Default::default(), cx);
|
||||||
editor.move_down(&Default::default(), cx);
|
editor.move_down(&Default::default(), cx);
|
||||||
editor.move_down(&Default::default(), cx);
|
editor.move_down(&Default::default(), cx);
|
||||||
editor.save(params.project.clone(), cx)
|
editor.save(project.clone(), cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -1104,7 +1051,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
app_state
|
app_state
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
|
||||||
.remove_file(Path::new("/root/a/file2"), Default::default())
|
.remove_file(Path::new("/root/a/file2"), Default::default())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -1219,4 +1165,29 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert!(has_default_theme);
|
assert!(has_default_theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init(cx: &mut TestAppContext) -> Arc<AppState> {
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
cx.update(|cx| {
|
||||||
|
let mut app_state = AppState::test(cx);
|
||||||
|
let state = Arc::get_mut(&mut app_state).unwrap();
|
||||||
|
state.initialize_workspace = initialize_workspace;
|
||||||
|
state.build_window_options = build_window_options;
|
||||||
|
workspace::init(app_state.clone(), cx);
|
||||||
|
editor::init(cx);
|
||||||
|
pane::init(cx);
|
||||||
|
app_state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rust_lang() -> Arc<language::Language> {
|
||||||
|
Arc::new(language::Language::new(
|
||||||
|
language::LanguageConfig {
|
||||||
|
name: "Rust".into(),
|
||||||
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::language()),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cd styles
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"watch": [
|
|
||||||
"./**/*"
|
|
||||||
],
|
|
||||||
"ext": "ts",
|
|
||||||
"ignore": [],
|
|
||||||
"exec": "ts-node src/buildThemes.ts"
|
|
||||||
}
|
|
2005
styles/package-lock.json
generated
2005
styles/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build-themes && npm run build-tokens",
|
"build": "npm run build-themes && npm run build-tokens",
|
||||||
"build-themes": "ts-node ./src/buildThemes.ts",
|
"build-themes": "ts-node ./src/buildThemes.ts",
|
||||||
"build-tokens": "ts-node ./src/buildTokens.ts",
|
"build-tokens": "ts-node ./src/buildTokens.ts"
|
||||||
"watch": "nodemon"
|
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@ -16,7 +15,6 @@
|
|||||||
"@types/node": "^17.0.23",
|
"@types/node": "^17.0.23",
|
||||||
"case-anything": "^2.1.10",
|
"case-anything": "^2.1.10",
|
||||||
"chroma-js": "^2.4.2",
|
"chroma-js": "^2.4.2",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.7.0"
|
||||||
"nodemon": "^2.0.15"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,30 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
import { tmpdir } from 'os';
|
||||||
import app from "./styleTree/app";
|
import app from "./styleTree/app";
|
||||||
import themes from "./themes";
|
import themes from "./themes";
|
||||||
import snakeCase from "./utils/snakeCase";
|
import snakeCase from "./utils/snakeCase";
|
||||||
|
|
||||||
const themeDirectory = `${__dirname}/../../assets/themes/`;
|
const themeDirectory = `${__dirname}/../../assets/themes/`;
|
||||||
|
const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), 'build-themes'));
|
||||||
|
|
||||||
// Clear existing themes
|
// Clear existing themes
|
||||||
for (const file of fs.readdirSync(themeDirectory)) {
|
for (const file of fs.readdirSync(themeDirectory)) {
|
||||||
fs.unlinkSync(path.join(themeDirectory, file));
|
if (file.endsWith('.json')) {
|
||||||
|
const name = file.replace(/\.json$/, '');
|
||||||
|
if (!themes.find(theme => theme.name === name)) {
|
||||||
|
fs.unlinkSync(path.join(themeDirectory, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write new themes to theme directory
|
// Write new themes to theme directory
|
||||||
for (let theme of themes) {
|
for (let theme of themes) {
|
||||||
let styleTree = snakeCase(app(theme));
|
let styleTree = snakeCase(app(theme));
|
||||||
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
|
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
|
||||||
let outPath = path.resolve(
|
let tempPath = path.join(tempDirectory, `${theme.name}.json`);
|
||||||
`${__dirname}/../../assets/themes/${theme.name}.json`
|
let outPath = path.join(themeDirectory, `${theme.name}.json`);
|
||||||
);
|
fs.writeFileSync(tempPath, styleTreeJSON);
|
||||||
fs.writeFileSync(outPath, styleTreeJSON);
|
fs.renameSync(tempPath, outPath);
|
||||||
console.log(`- ${outPath} created`);
|
console.log(`- ${outPath} created`);
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,12 @@ export default themes;
|
|||||||
|
|
||||||
const themesPath = path.resolve(`${__dirname}/themes`);
|
const themesPath = path.resolve(`${__dirname}/themes`);
|
||||||
for (const fileName of fs.readdirSync(themesPath)) {
|
for (const fileName of fs.readdirSync(themesPath)) {
|
||||||
|
if (fileName == "template.ts") continue;
|
||||||
const filePath = path.join(themesPath, fileName);
|
const filePath = path.join(themesPath, fileName);
|
||||||
|
|
||||||
if (fs.statSync(filePath).isFile()) {
|
if (fs.statSync(filePath).isFile()) {
|
||||||
const theme = require(filePath);
|
const theme = require(filePath);
|
||||||
themes.push(theme.dark);
|
if (theme.dark) themes.push(theme.dark);
|
||||||
themes.push(theme.light);
|
if (theme.light) themes.push(theme.light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
styles/src/themes/andromeda.ts
Normal file
27
styles/src/themes/andromeda.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createTheme } from "./common/base16";
|
||||||
|
|
||||||
|
const name = "andromeda";
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma.scale([
|
||||||
|
"#1E2025",
|
||||||
|
"#23262E",
|
||||||
|
"#292E38",
|
||||||
|
"#2E323C",
|
||||||
|
"#ACA8AE",
|
||||||
|
"#CBC9CF",
|
||||||
|
"#E1DDE4",
|
||||||
|
"#F7F7F8",
|
||||||
|
]),
|
||||||
|
red: colorRamp(chroma("#F92672")),
|
||||||
|
orange: colorRamp(chroma("#F39C12")),
|
||||||
|
yellow: colorRamp(chroma("#FFE66D")),
|
||||||
|
green: colorRamp(chroma("#96E072")),
|
||||||
|
cyan: colorRamp(chroma("#00E8C6")),
|
||||||
|
blue: colorRamp(chroma("#0CA793")),
|
||||||
|
violet: colorRamp(chroma("#8A3FA6")),
|
||||||
|
magenta: colorRamp(chroma("#C74DED")),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dark = createTheme(`${name}`, false, ramps);
|
28
styles/src/themes/brushtrees.ts
Normal file
28
styles/src/themes/brushtrees.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createTheme } from "./common/base16";
|
||||||
|
|
||||||
|
const name = "brush-tree";
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma.scale([
|
||||||
|
"#485867",
|
||||||
|
"#5A6D7A",
|
||||||
|
"#6D828E",
|
||||||
|
"#8299A1",
|
||||||
|
"#98AFB5",
|
||||||
|
"#B0C5C8",
|
||||||
|
"#C9DBDC",
|
||||||
|
"#E3EFEF",
|
||||||
|
]),
|
||||||
|
red: colorRamp(chroma("#b38686")),
|
||||||
|
orange: colorRamp(chroma("#d8bba2")),
|
||||||
|
yellow: colorRamp(chroma("#aab386")),
|
||||||
|
green: colorRamp(chroma("#87b386")),
|
||||||
|
cyan: colorRamp(chroma("#86b3b3")),
|
||||||
|
blue: colorRamp(chroma("#868cb3")),
|
||||||
|
violet: colorRamp(chroma("#b386b2")),
|
||||||
|
magenta: colorRamp(chroma("#b39f9f")),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dark = createTheme(`${name}-dark`, false, ramps);
|
||||||
|
export const light = createTheme(`${name}-light`, true, ramps);
|
@ -4,7 +4,16 @@ import { colorRamp, createTheme } from "./common/base16";
|
|||||||
const name = "cave";
|
const name = "cave";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale(["#19171c", "#26232a", "#585260", "#655f6d", "#7e7887", "#8b8792", "#e2dfe7", "#efecf4"]),
|
neutral: chroma.scale([
|
||||||
|
"#19171c",
|
||||||
|
"#26232a",
|
||||||
|
"#585260",
|
||||||
|
"#655f6d",
|
||||||
|
"#7e7887",
|
||||||
|
"#8b8792",
|
||||||
|
"#e2dfe7",
|
||||||
|
"#efecf4",
|
||||||
|
]),
|
||||||
red: colorRamp(chroma("#be4678")),
|
red: colorRamp(chroma("#be4678")),
|
||||||
orange: colorRamp(chroma("#aa573c")),
|
orange: colorRamp(chroma("#aa573c")),
|
||||||
yellow: colorRamp(chroma("#a06e3b")),
|
yellow: colorRamp(chroma("#a06e3b")),
|
||||||
@ -13,7 +22,7 @@ const ramps = {
|
|||||||
blue: colorRamp(chroma("#576ddb")),
|
blue: colorRamp(chroma("#576ddb")),
|
||||||
violet: colorRamp(chroma("#955ae7")),
|
violet: colorRamp(chroma("#955ae7")),
|
||||||
magenta: colorRamp(chroma("#bf40bf")),
|
magenta: colorRamp(chroma("#bf40bf")),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
export const dark = createTheme(`${name}-dark`, false, ramps);
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
export const light = createTheme(`${name}-light`, true, ramps);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import chroma from "chroma-js";
|
import chroma, { Color, Scale } from "chroma-js";
|
||||||
import { Scale, Color } from "chroma-js";
|
|
||||||
import { color, ColorToken, fontWeights, NumberToken } from "../../tokens";
|
import { color, ColorToken, fontWeights, NumberToken } from "../../tokens";
|
||||||
import { withOpacity } from "../../utils/color";
|
import { withOpacity } from "../../utils/color";
|
||||||
import Theme, { buildPlayer, Syntax } from "./theme";
|
import Theme, { buildPlayer, Syntax } from "./theme";
|
||||||
@ -8,12 +7,15 @@ export function colorRamp(color: Color): Scale {
|
|||||||
let hue = color.hsl()[0];
|
let hue = color.hsl()[0];
|
||||||
let endColor = chroma.hsl(hue, 0.88, 0.96);
|
let endColor = chroma.hsl(hue, 0.88, 0.96);
|
||||||
let startColor = chroma.hsl(hue, 0.68, 0.12);
|
let startColor = chroma.hsl(hue, 0.68, 0.12);
|
||||||
return chroma
|
return chroma.scale([startColor, color, endColor]).mode("hsl");
|
||||||
.scale([startColor, color, endColor])
|
|
||||||
.mode("hsl");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTheme(name: string, isLight: boolean, ramps: { [rampName: string]: Scale }, blend?: number): Theme {
|
export function createTheme(
|
||||||
|
name: string,
|
||||||
|
isLight: boolean,
|
||||||
|
ramps: { [rampName: string]: Scale },
|
||||||
|
blend?: number
|
||||||
|
): Theme {
|
||||||
if (isLight) {
|
if (isLight) {
|
||||||
for (var rampName in ramps) {
|
for (var rampName in ramps) {
|
||||||
ramps[rampName] = ramps[rampName].domain([1, 0]);
|
ramps[rampName] = ramps[rampName].domain([1, 0]);
|
||||||
@ -62,22 +64,22 @@ export function createTheme(name: string, isLight: boolean, ramps: { [rampName:
|
|||||||
},
|
},
|
||||||
ok: {
|
ok: {
|
||||||
base: withOpacity(rampColor(ramps.green, 0.5), 0.15),
|
base: withOpacity(rampColor(ramps.green, 0.5), 0.15),
|
||||||
hovered: withOpacity(rampColor(ramps.green, 0.5), 0.20),
|
hovered: withOpacity(rampColor(ramps.green, 0.5), 0.2),
|
||||||
active: withOpacity(rampColor(ramps.green, 0.5), 0.25),
|
active: withOpacity(rampColor(ramps.green, 0.5), 0.25),
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
base: withOpacity(rampColor(ramps.red, 0.5), 0.15),
|
base: withOpacity(rampColor(ramps.red, 0.5), 0.15),
|
||||||
hovered: withOpacity(rampColor(ramps.red, 0.5), 0.20),
|
hovered: withOpacity(rampColor(ramps.red, 0.5), 0.2),
|
||||||
active: withOpacity(rampColor(ramps.red, 0.5), 0.25),
|
active: withOpacity(rampColor(ramps.red, 0.5), 0.25),
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
base: withOpacity(rampColor(ramps.yellow, 0.5), 0.15),
|
base: withOpacity(rampColor(ramps.yellow, 0.5), 0.15),
|
||||||
hovered: withOpacity(rampColor(ramps.yellow, 0.5), 0.20),
|
hovered: withOpacity(rampColor(ramps.yellow, 0.5), 0.2),
|
||||||
active: withOpacity(rampColor(ramps.yellow, 0.5), 0.25),
|
active: withOpacity(rampColor(ramps.yellow, 0.5), 0.25),
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
base: withOpacity(rampColor(ramps.blue, 0.5), 0.15),
|
base: withOpacity(rampColor(ramps.blue, 0.5), 0.15),
|
||||||
hovered: withOpacity(rampColor(ramps.blue, 0.5), 0.20),
|
hovered: withOpacity(rampColor(ramps.blue, 0.5), 0.2),
|
||||||
active: withOpacity(rampColor(ramps.blue, 0.5), 0.25),
|
active: withOpacity(rampColor(ramps.blue, 0.5), 0.25),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -242,4 +244,4 @@ export function createTheme(name: string, isLight: boolean, ramps: { [rampName:
|
|||||||
player,
|
player,
|
||||||
shadowAlpha,
|
shadowAlpha,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import { withOpacity } from "../../utils/color";
|
|||||||
export interface SyntaxHighlightStyle {
|
export interface SyntaxHighlightStyle {
|
||||||
color: ColorToken;
|
color: ColorToken;
|
||||||
weight?: FontWeightToken;
|
weight?: FontWeightToken;
|
||||||
underline?: boolean,
|
underline?: boolean;
|
||||||
italic?: boolean,
|
italic?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Player {
|
export interface Player {
|
||||||
@ -25,7 +25,7 @@ export function buildPlayer(
|
|||||||
cursorColor: withOpacity(color, cursorOpacity || 1.0),
|
cursorColor: withOpacity(color, cursorOpacity || 1.0),
|
||||||
selectionColor: withOpacity(color, selectionOpacity || 0.24),
|
selectionColor: withOpacity(color, selectionOpacity || 0.24),
|
||||||
borderColor: withOpacity(color, borderOpacity || 0.8),
|
borderColor: withOpacity(color, borderOpacity || 0.8),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackgroundColorSet {
|
export interface BackgroundColorSet {
|
||||||
@ -56,7 +56,7 @@ export interface Syntax {
|
|||||||
linkText: SyntaxHighlightStyle;
|
linkText: SyntaxHighlightStyle;
|
||||||
|
|
||||||
[key: string]: SyntaxHighlightStyle;
|
[key: string]: SyntaxHighlightStyle;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default interface Theme {
|
export default interface Theme {
|
||||||
name: string;
|
name: string;
|
||||||
@ -86,8 +86,8 @@ export default interface Theme {
|
|||||||
muted: ColorToken;
|
muted: ColorToken;
|
||||||
active: ColorToken;
|
active: ColorToken;
|
||||||
/**
|
/**
|
||||||
* Used for rendering borders on top of media like avatars, images, video, etc.
|
* Used for rendering borders on top of media like avatars, images, video, etc.
|
||||||
*/
|
*/
|
||||||
onMedia: ColorToken;
|
onMedia: ColorToken;
|
||||||
ok: ColorToken;
|
ok: ColorToken;
|
||||||
error: ColorToken;
|
error: ColorToken;
|
||||||
@ -141,7 +141,7 @@ export default interface Theme {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
syntax: Syntax,
|
syntax: Syntax;
|
||||||
|
|
||||||
player: {
|
player: {
|
||||||
1: Player;
|
1: Player;
|
||||||
|
27
styles/src/themes/rose-pine-dawn.ts
Normal file
27
styles/src/themes/rose-pine-dawn.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createTheme } from "./common/base16";
|
||||||
|
|
||||||
|
const name = "rosé-pine-dawn";
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma.scale([
|
||||||
|
"#26233a",
|
||||||
|
"#555169",
|
||||||
|
"#575279",
|
||||||
|
"#6e6a86",
|
||||||
|
"#9893a5",
|
||||||
|
"#f2e9de",
|
||||||
|
"#fffaf3",
|
||||||
|
"#faf4ed",
|
||||||
|
]),
|
||||||
|
red: colorRamp(chroma("#1f1d2e")),
|
||||||
|
orange: colorRamp(chroma("#b4637a")),
|
||||||
|
yellow: colorRamp(chroma("#ea9d34")),
|
||||||
|
green: colorRamp(chroma("#d7827e")),
|
||||||
|
cyan: colorRamp(chroma("#286983")),
|
||||||
|
blue: colorRamp(chroma("#56949f")),
|
||||||
|
violet: colorRamp(chroma("#907aa9")),
|
||||||
|
magenta: colorRamp(chroma("#c5c3ce")),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const light = createTheme(`${name}`, true, ramps);
|
27
styles/src/themes/rose-pine.ts
Normal file
27
styles/src/themes/rose-pine.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createTheme } from "./common/base16";
|
||||||
|
|
||||||
|
const name = "rosé-pine";
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma.scale([
|
||||||
|
"#191724",
|
||||||
|
"#1f1d2e",
|
||||||
|
"#26233a",
|
||||||
|
"#555169",
|
||||||
|
"#6e6a86",
|
||||||
|
"#e0def4",
|
||||||
|
"#f0f0f3",
|
||||||
|
"#c5c3ce",
|
||||||
|
]),
|
||||||
|
red: colorRamp(chroma("#e2e1e7")),
|
||||||
|
orange: colorRamp(chroma("#eb6f92")),
|
||||||
|
yellow: colorRamp(chroma("#f6c177")),
|
||||||
|
green: colorRamp(chroma("#ebbcba")),
|
||||||
|
cyan: colorRamp(chroma("#31748f")),
|
||||||
|
blue: colorRamp(chroma("#0CA793")),
|
||||||
|
violet: colorRamp(chroma("#8A3FA6")),
|
||||||
|
magenta: colorRamp(chroma("#C74DED")),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dark = createTheme(`${name}`, false, ramps);
|
27
styles/src/themes/sandcastle.ts
Normal file
27
styles/src/themes/sandcastle.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createTheme } from "./common/base16";
|
||||||
|
|
||||||
|
const name = "sandcastle";
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma.scale([
|
||||||
|
"#282c34",
|
||||||
|
"#2c323b",
|
||||||
|
"#3e4451",
|
||||||
|
"#665c54",
|
||||||
|
"#928374",
|
||||||
|
"#a89984",
|
||||||
|
"#d5c4a1",
|
||||||
|
"#fdf4c1",
|
||||||
|
]),
|
||||||
|
red: colorRamp(chroma("#83a598")),
|
||||||
|
orange: colorRamp(chroma("#a07e3b")),
|
||||||
|
yellow: colorRamp(chroma("#a07e3b")),
|
||||||
|
green: colorRamp(chroma("#528b8b")),
|
||||||
|
cyan: colorRamp(chroma("#83a598")),
|
||||||
|
blue: colorRamp(chroma("#83a598")),
|
||||||
|
violet: colorRamp(chroma("#d75f5f")),
|
||||||
|
magenta: colorRamp(chroma("#a87322")),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dark = createTheme(`${name}`, false, ramps);
|
@ -4,7 +4,16 @@ import { colorRamp, createTheme } from "./common/base16";
|
|||||||
const name = "solarized";
|
const name = "solarized";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale(["#002b36", "#073642", "#586e75", "#657b83", "#839496", "#93a1a1", "#eee8d5", "#fdf6e3"]),
|
neutral: chroma.scale([
|
||||||
|
"#002b36",
|
||||||
|
"#073642",
|
||||||
|
"#586e75",
|
||||||
|
"#657b83",
|
||||||
|
"#839496",
|
||||||
|
"#93a1a1",
|
||||||
|
"#eee8d5",
|
||||||
|
"#fdf6e3",
|
||||||
|
]),
|
||||||
red: colorRamp(chroma("#dc322f")),
|
red: colorRamp(chroma("#dc322f")),
|
||||||
orange: colorRamp(chroma("#cb4b16")),
|
orange: colorRamp(chroma("#cb4b16")),
|
||||||
yellow: colorRamp(chroma("#b58900")),
|
yellow: colorRamp(chroma("#b58900")),
|
||||||
@ -13,7 +22,7 @@ const ramps = {
|
|||||||
blue: colorRamp(chroma("#268bd2")),
|
blue: colorRamp(chroma("#268bd2")),
|
||||||
violet: colorRamp(chroma("#6c71c4")),
|
violet: colorRamp(chroma("#6c71c4")),
|
||||||
magenta: colorRamp(chroma("#d33682")),
|
magenta: colorRamp(chroma("#d33682")),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
export const dark = createTheme(`${name}-dark`, false, ramps);
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
export const light = createTheme(`${name}-light`, true, ramps);
|
||||||
|
@ -4,7 +4,16 @@ import { colorRamp, createTheme } from "./common/base16";
|
|||||||
const name = "sulphurpool";
|
const name = "sulphurpool";
|
||||||
|
|
||||||
const ramps = {
|
const ramps = {
|
||||||
neutral: chroma.scale(["#202746", "#293256", "#5e6687", "#6b7394", "#898ea4", "#979db4", "#dfe2f1", "#f5f7ff"]),
|
neutral: chroma.scale([
|
||||||
|
"#202746",
|
||||||
|
"#293256",
|
||||||
|
"#5e6687",
|
||||||
|
"#6b7394",
|
||||||
|
"#898ea4",
|
||||||
|
"#979db4",
|
||||||
|
"#dfe2f1",
|
||||||
|
"#f5f7ff",
|
||||||
|
]),
|
||||||
red: colorRamp(chroma("#c94922")),
|
red: colorRamp(chroma("#c94922")),
|
||||||
orange: colorRamp(chroma("#c76b29")),
|
orange: colorRamp(chroma("#c76b29")),
|
||||||
yellow: colorRamp(chroma("#c08b30")),
|
yellow: colorRamp(chroma("#c08b30")),
|
||||||
@ -13,7 +22,7 @@ const ramps = {
|
|||||||
blue: colorRamp(chroma("#3d8fd1")),
|
blue: colorRamp(chroma("#3d8fd1")),
|
||||||
violet: colorRamp(chroma("#6679cc")),
|
violet: colorRamp(chroma("#6679cc")),
|
||||||
magenta: colorRamp(chroma("#9c637a")),
|
magenta: colorRamp(chroma("#9c637a")),
|
||||||
}
|
};
|
||||||
|
|
||||||
export const dark = createTheme(`${name}-dark`, false, ramps);
|
export const dark = createTheme(`${name}-dark`, false, ramps);
|
||||||
export const light = createTheme(`${name}-light`, true, ramps);
|
export const light = createTheme(`${name}-light`, true, ramps);
|
||||||
|
27
styles/src/themes/summercamp.ts
Normal file
27
styles/src/themes/summercamp.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createTheme } from "./common/base16";
|
||||||
|
|
||||||
|
const name = "summercamp";
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma.scale([
|
||||||
|
"#1c1810",
|
||||||
|
"#2a261c",
|
||||||
|
"#3a3527",
|
||||||
|
"#3a3527",
|
||||||
|
"#5f5b45",
|
||||||
|
"#736e55",
|
||||||
|
"#bab696",
|
||||||
|
"#f8f5de",
|
||||||
|
]),
|
||||||
|
red: colorRamp(chroma("#e35142")),
|
||||||
|
orange: colorRamp(chroma("#fba11b")),
|
||||||
|
yellow: colorRamp(chroma("#f2ff27")),
|
||||||
|
green: colorRamp(chroma("#5ceb5a")),
|
||||||
|
cyan: colorRamp(chroma("#5aebbc")),
|
||||||
|
blue: colorRamp(chroma("#489bf0")),
|
||||||
|
violet: colorRamp(chroma("#FF8080")),
|
||||||
|
magenta: colorRamp(chroma("#F69BE7")),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dark = createTheme(`${name}`, false, ramps);
|
28
styles/src/themes/summerfruit.ts
Normal file
28
styles/src/themes/summerfruit.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createTheme } from "./common/base16";
|
||||||
|
|
||||||
|
const name = "summerfruit";
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma.scale([
|
||||||
|
"#151515",
|
||||||
|
"#202020",
|
||||||
|
"#303030",
|
||||||
|
"#505050",
|
||||||
|
"#B0B0B0",
|
||||||
|
"#D0D0D0",
|
||||||
|
"#E0E0E0",
|
||||||
|
"#FFFFFF",
|
||||||
|
]),
|
||||||
|
red: colorRamp(chroma("#FF0086")),
|
||||||
|
orange: colorRamp(chroma("#FD8900")),
|
||||||
|
yellow: colorRamp(chroma("#ABA800")),
|
||||||
|
green: colorRamp(chroma("#00C918")),
|
||||||
|
cyan: colorRamp(chroma("#1FAAAA")),
|
||||||
|
blue: colorRamp(chroma("#3777E6")),
|
||||||
|
violet: colorRamp(chroma("#AD00A1")),
|
||||||
|
magenta: colorRamp(chroma("#CC6633")),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dark = createTheme(`${name}-dark`, false, ramps);
|
||||||
|
export const light = createTheme(`${name}-light`, true, ramps);
|
69
styles/src/themes/template.ts
Normal file
69
styles/src/themes/template.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* To create a new theme duplicate this file and move into templates
|
||||||
|
**/
|
||||||
|
|
||||||
|
import chroma from "chroma-js";
|
||||||
|
import { colorRamp, createTheme } from "./common/base16";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme Name
|
||||||
|
*
|
||||||
|
* What the theme will be called in the UI
|
||||||
|
* Also used to generate filenames, etc
|
||||||
|
**/
|
||||||
|
|
||||||
|
const name = "themeName";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme Colors
|
||||||
|
*
|
||||||
|
* Zed themes are based on [base16](https://github.com/chriskempson/base16)
|
||||||
|
* The first 8 colors ("Neutrals") are used to construct the UI background, panels, etc.
|
||||||
|
* The latter 8 colors ("Accents") are used for syntax themes, semantic colors, and UI states.
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color Ramps
|
||||||
|
*
|
||||||
|
* We use (chroma-js)[https://gka.github.io/chroma.js/] to minipulate color in themes and to build color ramps.
|
||||||
|
*
|
||||||
|
* You can use chroma-js operations on the ramps here.
|
||||||
|
* For example, you could use chroma.scale(...).correctLightness if your color ramps seem washed out near the ends.
|
||||||
|
**/
|
||||||
|
|
||||||
|
// TODO: Express accents without refering to them directly by color name.
|
||||||
|
// See common/base16.ts for where color tokens are used.
|
||||||
|
|
||||||
|
const ramps = {
|
||||||
|
neutral: chroma.scale([
|
||||||
|
"#19171c", // Dark: darkest backgrounds, inputs | Light: Lightest text, active states
|
||||||
|
"#26232a",
|
||||||
|
"#585260",
|
||||||
|
"#655f6d",
|
||||||
|
"#7e7887",
|
||||||
|
"#8b8792",
|
||||||
|
"#e2dfe7",
|
||||||
|
"#efecf4", // Light: darkest backgrounds, inputs | Dark: Lightest text, active states
|
||||||
|
]),
|
||||||
|
red: colorRamp(chroma("#be4678")), // Errors
|
||||||
|
orange: colorRamp(chroma("#aa573c")),
|
||||||
|
yellow: colorRamp(chroma("#a06e3b")), // Warnings
|
||||||
|
green: colorRamp(chroma("#2a9292")), // Positive
|
||||||
|
cyan: colorRamp(chroma("#398bc6")), // Player 1 (Host)
|
||||||
|
blue: colorRamp(chroma("#576ddb")), // Info
|
||||||
|
violet: colorRamp(chroma("#955ae7")),
|
||||||
|
magenta: colorRamp(chroma("#bf40bf")),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme Variants
|
||||||
|
*
|
||||||
|
* Currently we only support (and require) dark and light themes
|
||||||
|
* Eventually you will be able to have only a light or dark theme,
|
||||||
|
* and define other variants here.
|
||||||
|
*
|
||||||
|
* createTheme([name], [isLight], [arrayOfRamps])
|
||||||
|
**/
|
||||||
|
|
||||||
|
export const dark = createTheme(`${name}-dark`, false, ramps);
|
||||||
|
export const light = createTheme(`${name}-light`, true, ramps);
|
Loading…
Reference in New Issue
Block a user