Merge branch 'main' into following-tests

This commit is contained in:
Conrad Irwin 2024-01-05 16:14:12 -07:00
commit 204ef451d0
224 changed files with 12664 additions and 23906 deletions

20
Cargo.lock generated
View File

@ -576,8 +576,9 @@ dependencies = [
[[package]]
name = "async-task"
version = "4.0.3"
source = "git+https://github.com/zed-industries/async-task?rev=341b57d6de98cdfd7b418567b8de2022ca993a6e#341b57d6de98cdfd7b418567b8de2022ca993a6e"
version = "4.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
[[package]]
name = "async-tls"
@ -1440,7 +1441,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.33.0"
version = "0.34.0"
dependencies = [
"anyhow",
"async-trait",
@ -1925,11 +1926,12 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.1.20"
source = "git+https://github.com/zed-industries/rust-ctor?rev=7f824cf6a7943885a649b579f33f9ac53f0d1db6#7f824cf6a7943885a649b579f33f9ac53f0d1db6"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
dependencies = [
"quote",
"syn 1.0.109",
"syn 2.0.37",
]
[[package]]
@ -3064,7 +3066,6 @@ dependencies = [
"slotmap",
"smallvec",
"smol",
"sqlez",
"sum_tree",
"taffy",
"thiserror",
@ -6845,7 +6846,6 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"smallvec",
"sqlez",
"toml 0.5.11",
"tree-sitter",
"tree-sitter-json 0.19.0",
@ -7837,6 +7837,7 @@ dependencies = [
"convert_case 0.6.0",
"gpui",
"indexmap 1.9.3",
"indoc",
"json_comments",
"log",
"palette",
@ -8488,7 +8489,7 @@ dependencies = [
[[package]]
name = "tree-sitter-nu"
version = "0.0.1"
source = "git+https://github.com/nushell/tree-sitter-nu?rev=a0b80b2e21e5e39571252dc799e19eb89f1fc912#a0b80b2e21e5e39571252dc799e19eb89f1fc912"
source = "git+https://github.com/nushell/tree-sitter-nu?rev=26bbaecda0039df4067861ab38ea8ea169f7f5aa#26bbaecda0039df4067861ab38ea8ea169f7f5aa"
dependencies = [
"cc",
"tree-sitter",
@ -9438,6 +9439,7 @@ dependencies = [
"serde_json",
"settings",
"smallvec",
"sqlez",
"terminal",
"theme",
"ui",

View File

@ -92,10 +92,7 @@ resolver = "2"
anyhow = { version = "1.0.57" }
async-trait = { version = "0.1" }
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
# TODO: Switch back to the published version of `ctor` once:
# 1. A new version of `ctor` is published with this change: https://github.com/mmastrac/rust-ctor/pull/295
# 2. We've confirmed it's fine to update to the latest version of `ctor` (we're currently on v0.1.20).
ctor = { git = "https://github.com/zed-industries/rust-ctor", rev = "7f824cf6a7943885a649b579f33f9ac53f0d1db6" }
ctor = "0.2.6"
derive_more = { version = "0.99.17" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
@ -158,13 +155,12 @@ tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-rack
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"}
tree-sitter-lua = "0.0.14"
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "a0b80b2e21e5e39571252dc799e19eb89f1fc912"}
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "26bbaecda0039df4067861ab38ea8ea169f7f5aa"}
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42"}
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" }
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
# wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" }
# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457

View File

@ -402,7 +402,7 @@
"cmd-r": "workspace::ToggleRightDock",
"cmd-j": "workspace::ToggleBottomDock",
"alt-cmd-y": "workspace::CloseAllDocks",
"cmd-shift-f": "workspace::NewSearch",
"cmd-shift-f": "workspace::DeploySearch",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-t": "project_symbols::Toggle",

View File

@ -1286,8 +1286,8 @@ impl Panel for AssistantPanel {
}
}
fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
Some(Icon::Ai)
fn icon(&self, cx: &WindowContext) -> Option<Icon> {
Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@ -2827,8 +2827,8 @@ impl InlineAssistant {
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
let is_read_only = !self.codegen.read(cx).idle();
self.prompt_editor.update(cx, |editor, _cx| {
let was_read_only = editor.read_only();
self.prompt_editor.update(cx, |editor, cx| {
let was_read_only = editor.read_only(cx);
if was_read_only != is_read_only {
if is_read_only {
editor.set_read_only(true);
@ -3063,7 +3063,7 @@ impl InlineAssistant {
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.prompt_editor.read(cx).read_only() {
color: if self.prompt_editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text

View File

@ -36,12 +36,14 @@ impl ParticipantLocation {
pub struct LocalParticipant {
pub projects: Vec<proto::ParticipantProject>,
pub active_project: Option<WeakModel<Project>>,
pub role: proto::ChannelRole,
}
#[derive(Clone, Debug)]
pub struct RemoteParticipant {
pub user: Arc<User>,
pub peer_id: proto::PeerId,
pub role: proto::ChannelRole,
pub projects: Vec<proto::ParticipantProject>,
pub location: ParticipantLocation,
pub participant_index: ParticipantIndex,

View File

@ -247,14 +247,18 @@ impl Room {
let response = client.request(proto::CreateRoom {}).await?;
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.new_model(|cx| {
Self::new(
let mut room = Self::new(
room_proto.id,
None,
response.live_kit_connection_info,
client,
user_store,
cx,
)
);
if let Some(participant) = room_proto.participants.first() {
room.local_participant.role = participant.role()
}
room
})?;
let initial_project_id = if let Some(initial_project) = initial_project {
@ -606,6 +610,16 @@ impl Room {
.find(|p| p.peer_id == peer_id)
}
pub fn role_for_user(&self, user_id: u64) -> Option<proto::ChannelRole> {
self.remote_participants
.get(&user_id)
.map(|participant| participant.role)
}
pub fn local_participant_is_admin(&self) -> bool {
self.local_participant.role == proto::ChannelRole::Admin
}
pub fn pending_participants(&self) -> &[Arc<User>] {
&self.pending_participants
}
@ -710,7 +724,20 @@ impl Room {
this.participant_user_ids.clear();
if let Some(participant) = local_participant {
let role = participant.role();
this.local_participant.projects = participant.projects;
if this.local_participant.role != role {
this.local_participant.role = role;
this.joined_projects.retain(|project| {
if let Some(project) = project.upgrade() {
project.update(cx, |project, _| project.set_role(role));
true
} else {
false
}
});
}
} else {
this.local_participant.projects.clear();
}
@ -766,6 +793,7 @@ impl Room {
});
}
let role = participant.role();
let location = ParticipantLocation::from_proto(participant.location)
.unwrap_or(ParticipantLocation::External);
if let Some(remote_participant) =
@ -774,8 +802,11 @@ impl Room {
remote_participant.peer_id = peer_id;
remote_participant.projects = participant.projects;
remote_participant.participant_index = participant_index;
if location != remote_participant.location {
if location != remote_participant.location
|| role != remote_participant.role
{
remote_participant.location = location;
remote_participant.role = role;
cx.emit(Event::ParticipantLocationChanged {
participant_id: peer_id,
});
@ -789,6 +820,7 @@ impl Room {
peer_id,
projects: participant.projects,
location,
role,
muted: true,
speaking: false,
video_tracks: Default::default(),
@ -1091,15 +1123,24 @@ impl Room {
) -> Task<Result<Model<Project>>> {
let client = self.client.clone();
let user_store = self.user_store.clone();
let role = self.local_participant.role;
cx.emit(Event::RemoteProjectJoined { project_id: id });
cx.spawn(move |this, mut cx| async move {
let project =
Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
let project = Project::remote(
id,
client,
user_store,
language_registry,
fs,
role,
cx.clone(),
)
.await?;
this.update(&mut cx, |this, cx| {
this.joined_projects.retain(|project| {
if let Some(project) = project.upgrade() {
!project.read(cx).is_read_only()
!project.read(cx).is_disconnected()
} else {
false
}
@ -1224,6 +1265,11 @@ impl Room {
.unwrap_or(false)
}
pub fn read_only(&self) -> bool {
!(self.local_participant().role == proto::ChannelRole::Member
|| self.local_participant().role == proto::ChannelRole::Admin)
}
pub fn is_speaking(&self) -> bool {
self.live_kit
.as_ref()

View File

@ -62,7 +62,12 @@ impl ChannelBuffer {
.collect::<Result<Vec<_>, _>>()?;
let buffer = cx.new_model(|_| {
language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
language::Buffer::remote(
response.buffer_id,
response.replica_id as u16,
channel.channel_buffer_capability(),
base_text,
)
})?;
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;

View File

@ -11,6 +11,7 @@ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
WeakModel,
};
use language::Capability;
use rpc::{
proto::{self, ChannelVisibility},
TypedEnvelope,
@ -74,8 +75,12 @@ impl Channel {
slug.trim_matches(|c| c == '-').to_string()
}
pub fn can_edit_notes(&self) -> bool {
self.role == proto::ChannelRole::Member || self.role == proto::ChannelRole::Admin
pub fn channel_buffer_capability(&self) -> Capability {
if self.role == proto::ChannelRole::Member || self.role == proto::ChannelRole::Admin {
Capability::ReadWrite
} else {
Capability::ReadOnly
}
}
}

View File

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.33.0"
version = "0.34.0"
publish = false
[[bin]]

View File

@ -13,6 +13,7 @@ metadata:
annotations:
service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443"
service.beta.kubernetes.io/do-loadbalancer-certificate-id: ${ZED_DO_CERTIFICATE_ID}
service.beta.kubernetes.io/do-loadbalancer-disable-lets-encrypt-dns-records: "true"
spec:
type: LoadBalancer
selector:

View File

@ -7,6 +7,7 @@ metadata:
annotations:
service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443"
service.beta.kubernetes.io/do-loadbalancer-certificate-id: ${ZED_DO_CERTIFICATE_ID}
service.beta.kubernetes.io/do-loadbalancer-disable-lets-encrypt-dns-records: "true"
spec:
type: LoadBalancer
selector:

View File

@ -161,7 +161,8 @@ CREATE TABLE "room_participants" (
"calling_user_id" INTEGER NOT NULL REFERENCES users (id),
"calling_connection_id" INTEGER NOT NULL,
"calling_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE SET NULL,
"participant_index" INTEGER
"participant_index" INTEGER,
"role" TEXT
);
CREATE UNIQUE INDEX "index_room_participants_on_user_id" ON "room_participants" ("user_id");
CREATE INDEX "index_room_participants_on_room_id" ON "room_participants" ("room_id");

View File

@ -0,0 +1 @@
ALTER TABLE room_participants ADD COLUMN role TEXT;

View File

@ -132,6 +132,14 @@ impl ChannelRole {
Admin | Member | Banned => false,
}
}
pub fn can_share_projects(&self) -> bool {
use ChannelRole::*;
match self {
Admin | Member => true,
Guest | Banned => false,
}
}
}
impl From<proto::ChannelRole> for ChannelRole {

View File

@ -132,10 +132,7 @@ impl Database {
debug_assert!(
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
);
}
}
if channel.visibility == ChannelVisibility::Public {
} else if channel.visibility == ChannelVisibility::Public {
role = Some(ChannelRole::Guest);
let channel_to_join = self
.public_ancestors_including_self(&channel, &*tx)
@ -159,21 +156,25 @@ impl Database {
.await?,
);
debug_assert!(self.channel_role_for_user(&channel, user_id, &*tx).await? == role);
debug_assert!(
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
);
}
}
if role.is_none() || role == Some(ChannelRole::Banned) {
Err(anyhow!("not allowed"))?
}
let role = role.unwrap();
let live_kit_room = format!("channel-{}", nanoid::nanoid!(30));
let room_id = self
.get_or_create_channel_room(channel_id, &live_kit_room, environment, &*tx)
.await?;
self.join_channel_room_internal(room_id, user_id, connection, &*tx)
self.join_channel_room_internal(room_id, user_id, connection, role, &*tx)
.await
.map(|jr| (jr, accept_invite_result, role.unwrap()))
.map(|jr| (jr, accept_invite_result, role))
})
.await
}

View File

@ -46,6 +46,13 @@ impl Database {
if participant.room_id != room_id {
return Err(anyhow!("shared project on unexpected room"))?;
}
if !participant
.role
.unwrap_or(ChannelRole::Member)
.can_share_projects()
{
return Err(anyhow!("guests cannot share projects"))?;
}
let project = project::ActiveModel {
room_id: ActiveValue::set(participant.room_id),

View File

@ -131,7 +131,12 @@ impl Database {
connection.owner_id as i32,
))),
participant_index: ActiveValue::set(Some(0)),
..Default::default()
role: ActiveValue::set(Some(ChannelRole::Admin)),
id: ActiveValue::NotSet,
location_kind: ActiveValue::NotSet,
location_project_id: ActiveValue::NotSet,
initial_project_id: ActiveValue::NotSet,
}
.insert(&*tx)
.await?;
@ -151,6 +156,22 @@ impl Database {
initial_project_id: Option<ProjectId>,
) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
self.room_transaction(room_id, |tx| async move {
let caller = room_participant::Entity::find()
.filter(
room_participant::Column::UserId
.eq(calling_user_id)
.and(room_participant::Column::RoomId.eq(room_id)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("user is not in the room"))?;
let called_user_role = match caller.role.unwrap_or(ChannelRole::Member) {
ChannelRole::Admin | ChannelRole::Member => ChannelRole::Member,
ChannelRole::Guest => ChannelRole::Guest,
ChannelRole::Banned => return Err(anyhow!("banned users cannot invite").into()),
};
room_participant::ActiveModel {
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(called_user_id),
@ -162,7 +183,13 @@ impl Database {
calling_connection.owner_id as i32,
))),
initial_project_id: ActiveValue::set(initial_project_id),
..Default::default()
role: ActiveValue::set(Some(called_user_role)),
id: ActiveValue::NotSet,
answering_connection_id: ActiveValue::NotSet,
answering_connection_server_id: ActiveValue::NotSet,
location_kind: ActiveValue::NotSet,
location_project_id: ActiveValue::NotSet,
}
.insert(&*tx)
.await?;
@ -384,6 +411,7 @@ impl Database {
room_id: RoomId,
user_id: UserId,
connection: ConnectionId,
role: ChannelRole,
tx: &DatabaseTransaction,
) -> Result<JoinRoom> {
let participant_index = self
@ -404,7 +432,11 @@ impl Database {
connection.owner_id as i32,
))),
participant_index: ActiveValue::Set(Some(participant_index)),
..Default::default()
role: ActiveValue::set(Some(role)),
id: ActiveValue::NotSet,
location_kind: ActiveValue::NotSet,
location_project_id: ActiveValue::NotSet,
initial_project_id: ActiveValue::NotSet,
}])
.on_conflict(
OnConflict::columns([room_participant::Column::UserId])
@ -413,6 +445,7 @@ impl Database {
room_participant::Column::AnsweringConnectionServerId,
room_participant::Column::AnsweringConnectionLost,
room_participant::Column::ParticipantIndex,
room_participant::Column::Role,
])
.to_owned(),
)
@ -1134,6 +1167,7 @@ impl Database {
projects: Default::default(),
location: Some(proto::ParticipantLocation { variant: location }),
participant_index: participant_index as u32,
role: db_participant.role.unwrap_or(ChannelRole::Member).into(),
},
);
} else {

View File

@ -1,4 +1,4 @@
use crate::db::{ProjectId, RoomId, RoomParticipantId, ServerId, UserId};
use crate::db::{ChannelRole, ProjectId, RoomId, RoomParticipantId, ServerId, UserId};
use rpc::ConnectionId;
use sea_orm::entity::prelude::*;
@ -19,6 +19,7 @@ pub struct Model {
pub calling_connection_id: i32,
pub calling_connection_server_id: Option<ServerId>,
pub participant_index: Option<i32>,
pub role: Option<ChannelRole>,
}
impl Model {

View File

@ -2,6 +2,7 @@ use call::Room;
use gpui::{Model, TestAppContext};
mod channel_buffer_tests;
mod channel_guest_tests;
mod channel_message_tests;
mod channel_tests;
mod editor_tests;

View File

@ -0,0 +1,86 @@
use crate::tests::TestServer;
use call::ActiveCall;
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use rpc::proto;
use workspace::Workspace;
#[gpui::test]
async fn test_channel_guests(
executor: BackgroundExecutor,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
let mut server = TestServer::start(executor.clone()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
let channel_id = server
.make_channel("the-channel", None, (&client_a, cx_a), &mut [])
.await;
client_a
.channel_store()
.update(cx_a, |channel_store, cx| {
channel_store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx)
})
.await
.unwrap();
client_a
.fs()
.insert_tree(
"/a",
serde_json::json!({
"a.txt": "a-contents",
}),
)
.await;
let active_call_a = cx_a.read(ActiveCall::global);
// Client A shares a project in the channel
active_call_a
.update(cx_a, |call, cx| call.join_channel(channel_id, cx))
.await
.unwrap();
let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
cx_a.executor().run_until_parked();
// Client B joins channel A as a guest
cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
.await
.unwrap();
// b should be following a in the shared project.
// B is a guest,
cx_a.executor().run_until_parked();
// todo!() the test window does not call activation handlers
// correctly yet, so this API does not work.
// let project_b = active_call_b.read_with(cx_b, |call, _| {
// call.location()
// .unwrap()
// .upgrade()
// .expect("should not be weak")
// });
let window_b = cx_b.update(|cx| cx.active_window().unwrap());
let cx_b = &mut VisualTestContext::from_window(window_b, cx_b);
let workspace_b = window_b
.downcast::<Workspace>()
.unwrap()
.root_view(cx_b)
.unwrap();
let project_b = workspace_b.update(cx_b, |workspace, _| workspace.project().clone());
assert_eq!(
project_b.read_with(cx_b, |project, _| project.remote_id()),
Some(project_id),
);
assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()))
}

View File

@ -19,6 +19,7 @@ use project::{
search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath,
};
use rand::prelude::*;
use rpc::proto::ChannelRole;
use serde_json::json;
use settings::SettingsStore;
use std::{
@ -1380,7 +1381,7 @@ async fn test_unshare_project(
.unwrap();
executor.run_until_parked();
assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
assert!(project_b.read_with(cx_b, |project, _| project.is_disconnected()));
// Client C opens the project.
let project_c = client_c.build_remote_project(project_id, cx_c).await;
@ -1393,7 +1394,7 @@ async fn test_unshare_project(
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
assert!(project_c.read_with(cx_c, |project, _| project.is_read_only()));
assert!(project_c.read_with(cx_c, |project, _| project.is_disconnected()));
// Client C can open the project again after client A re-shares.
let project_id = active_call_a
@ -1419,7 +1420,7 @@ async fn test_unshare_project(
project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
project_c2.read_with(cx_c, |project, _| {
assert!(project.is_read_only());
assert!(project.is_disconnected());
assert!(project.collaborators().is_empty());
});
}
@ -1551,7 +1552,7 @@ async fn test_project_reconnect(
});
project_b1.read_with(cx_b, |project, _| {
assert!(!project.is_read_only());
assert!(!project.is_disconnected());
assert_eq!(project.collaborators().len(), 1);
});
@ -1653,7 +1654,7 @@ async fn test_project_reconnect(
});
project_b1.read_with(cx_b, |project, cx| {
assert!(!project.is_read_only());
assert!(!project.is_disconnected());
assert_eq!(
project
.worktree_for_id(worktree1_id, cx)
@ -1687,9 +1688,9 @@ async fn test_project_reconnect(
);
});
project_b2.read_with(cx_b, |project, _| assert!(project.is_read_only()));
project_b2.read_with(cx_b, |project, _| assert!(project.is_disconnected()));
project_b3.read_with(cx_b, |project, _| assert!(!project.is_read_only()));
project_b3.read_with(cx_b, |project, _| assert!(!project.is_disconnected()));
buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
@ -1746,7 +1747,7 @@ async fn test_project_reconnect(
executor.run_until_parked();
project_b1.read_with(cx_b, |project, cx| {
assert!(!project.is_read_only());
assert!(!project.is_disconnected());
assert_eq!(
project
.worktree_for_id(worktree1_id, cx)
@ -1780,7 +1781,7 @@ async fn test_project_reconnect(
);
});
project_b3.read_with(cx_b, |project, _| assert!(project.is_read_only()));
project_b3.read_with(cx_b, |project, _| assert!(project.is_disconnected()));
buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
@ -3535,7 +3536,7 @@ async fn test_leaving_project(
});
project_b2.read_with(cx_b, |project, _| {
assert!(project.is_read_only());
assert!(project.is_disconnected());
});
project_c.read_with(cx_c, |project, _| {
@ -3550,6 +3551,7 @@ async fn test_leaving_project(
client_b.user_store().clone(),
client_b.language_registry().clone(),
FakeFs::new(cx.background_executor().clone()),
ChannelRole::Member,
cx,
)
})
@ -3568,11 +3570,11 @@ async fn test_leaving_project(
});
project_b2.read_with(cx_b, |project, _| {
assert!(project.is_read_only());
assert!(project.is_disconnected());
});
project_c.read_with(cx_c, |project, _| {
assert!(project.is_read_only());
assert!(project.is_disconnected());
});
}

View File

@ -1149,7 +1149,7 @@ impl RandomizedTest for ProjectCollaborationTest {
Some((project, cx))
});
if !guest_project.is_read_only() {
if !guest_project.is_disconnected() {
if let Some((host_project, host_cx)) = host_project {
let host_worktree_snapshots =
host_project.read_with(host_cx, |host_project, cx| {
@ -1236,7 +1236,7 @@ impl RandomizedTest for ProjectCollaborationTest {
let buffers = client.buffers().clone();
for (guest_project, guest_buffers) in &buffers {
let project_id = if guest_project.read_with(client_cx, |project, _| {
project.is_local() || project.is_read_only()
project.is_local() || project.is_disconnected()
}) {
continue;
} else {

View File

@ -518,7 +518,7 @@ impl<T: RandomizedTest> TestPlan<T> {
for project in client.remote_projects().iter() {
project.read_with(&client_cx, |project, _| {
assert!(
project.is_read_only(),
project.is_disconnected(),
"project {:?} should be read only",
project.remote_id()
)

View File

@ -138,12 +138,6 @@ impl ChannelView {
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
channel_buffer.clone(),
)));
editor.set_read_only(
!channel_buffer
.read(cx)
.channel(cx)
.is_some_and(|c| c.can_edit_notes()),
);
editor
});
let _editor_event_subscription =
@ -178,8 +172,7 @@ impl ChannelView {
cx.notify();
}),
ChannelBufferEvent::ChannelChanged => {
self.editor.update(cx, |editor, cx| {
editor.set_read_only(!self.channel(cx).is_some_and(|c| c.can_edit_notes()));
self.editor.update(cx, |_, cx| {
cx.emit(editor::EditorEvent::TitleChanged);
cx.notify()
});
@ -254,11 +247,11 @@ impl Item for ChannelView {
fn tab_content(&self, _: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
let label = if let Some(channel) = self.channel(cx) {
match (
channel.can_edit_notes(),
self.channel_buffer.read(cx).buffer().read(cx).read_only(),
self.channel_buffer.read(cx).is_connected(),
) {
(true, true) => format!("#{}", channel.name),
(false, true) => format!("#{} (read-only)", channel.name),
(false, true) => format!("#{}", channel.name),
(true, true) => format!("#{} (read-only)", channel.name),
(_, false) => format!("#{} (disconnected)", channel.name),
}
} else {

View File

@ -607,8 +607,12 @@ impl Panel for ChatPanel {
"ChatPanel"
}
fn icon(&self, _cx: &WindowContext) -> Option<ui::Icon> {
Some(ui::Icon::MessageBubbles)
fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
if !is_channels_feature_enabled(cx) {
return None;
}
Some(ui::Icon::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {

View File

@ -151,6 +151,10 @@ enum ListEntry {
peer_id: Option<PeerId>,
is_last: bool,
},
GuestCount {
count: usize,
has_visible_participants: bool,
},
IncomingRequest(Arc<User>),
OutgoingRequest(Arc<User>),
ChannelInvite(Arc<Channel>),
@ -380,10 +384,14 @@ impl CollabPanel {
if !self.collapsed_sections.contains(&Section::ActiveCall) {
let room = room.read(cx);
let mut guest_count_ix = 0;
let mut guest_count = if room.read_only() { 1 } else { 0 };
let mut non_guest_count = if room.read_only() { 0 } else { 1 };
if let Some(channel_id) = room.channel_id() {
self.entries.push(ListEntry::ChannelNotes { channel_id });
self.entries.push(ListEntry::ChannelChat { channel_id })
self.entries.push(ListEntry::ChannelChat { channel_id });
guest_count_ix = self.entries.len();
}
// Populate the active user.
@ -402,7 +410,7 @@ impl CollabPanel {
&Default::default(),
executor.clone(),
));
if !matches.is_empty() {
if !matches.is_empty() && !room.read_only() {
let user_id = user.id;
self.entries.push(ListEntry::CallParticipant {
user,
@ -430,13 +438,23 @@ impl CollabPanel {
// Populate remote participants.
self.match_candidates.clear();
self.match_candidates
.extend(room.remote_participants().iter().map(|(_, participant)| {
StringMatchCandidate {
.extend(
room.remote_participants()
.iter()
.filter_map(|(_, participant)| {
if participant.role == proto::ChannelRole::Guest {
guest_count += 1;
return None;
} else {
non_guest_count += 1;
}
Some(StringMatchCandidate {
id: participant.user.id as usize,
string: participant.user.github_login.clone(),
char_bag: participant.user.github_login.chars().collect(),
}
}));
})
}),
);
let matches = executor.block(match_strings(
&self.match_candidates,
&query,
@ -470,6 +488,15 @@ impl CollabPanel {
});
}
}
if guest_count > 0 {
self.entries.insert(
guest_count_ix,
ListEntry::GuestCount {
count: guest_count,
has_visible_participants: non_guest_count > 0,
},
);
}
// Populate pending participants.
self.match_candidates.clear();
@ -959,6 +986,41 @@ impl CollabPanel {
.tooltip(move |cx| Tooltip::text("Open Chat", cx))
}
fn render_guest_count(
&self,
count: usize,
has_visible_participants: bool,
is_selected: bool,
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
let manageable_channel_id = ActiveCall::global(cx).read(cx).room().and_then(|room| {
let room = room.read(cx);
if room.local_participant_is_admin() {
room.channel_id()
} else {
None
}
});
ListItem::new("guest_count")
.selected(is_selected)
.start_slot(
h_stack()
.gap_1()
.child(render_tree_branch(!has_visible_participants, cx))
.child(""),
)
.child(Label::new(if count == 1 {
format!("{} guest", count)
} else {
format!("{} guests", count)
}))
.when_some(manageable_channel_id, |el, channel_id| {
el.tooltip(move |cx| Tooltip::text("Manage Members", cx))
.on_click(cx.listener(move |this, _, cx| this.manage_members(channel_id, cx)))
})
}
fn has_subchannels(&self, ix: usize) -> bool {
self.entries.get(ix).map_or(false, |entry| {
if let ListEntry::Channel { has_children, .. } = entry {
@ -1180,6 +1242,18 @@ impl CollabPanel {
});
}
}
ListEntry::GuestCount { .. } => {
let Some(room) = ActiveCall::global(cx).read(cx).room() else {
return;
};
let room = room.read(cx);
let Some(channel_id) = room.channel_id() else {
return;
};
if room.local_participant_is_admin() {
self.manage_members(channel_id, cx)
}
}
ListEntry::Channel { channel, .. } => {
let is_active = maybe!({
let call_channel = ActiveCall::global(cx)
@ -1735,6 +1809,12 @@ impl CollabPanel {
ListEntry::ParticipantScreen { peer_id, is_last } => self
.render_participant_screen(*peer_id, *is_last, is_selected, cx)
.into_any_element(),
ListEntry::GuestCount {
count,
has_visible_participants,
} => self
.render_guest_count(*count, *has_visible_participants, is_selected, cx)
.into_any_element(),
ListEntry::ChannelNotes { channel_id } => self
.render_channel_notes(*channel_id, is_selected, cx)
.into_any_element(),
@ -1766,7 +1846,7 @@ impl CollabPanel {
) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if editor.read(cx).read_only() {
color: if editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text
@ -2538,6 +2618,11 @@ impl PartialEq for ListEntry {
return true;
}
}
ListEntry::GuestCount { .. } => {
if let ListEntry::GuestCount { .. } = other {
return true;
}
}
}
false
}

View File

@ -162,8 +162,7 @@ impl Render for ChannelModal {
v_stack()
.px_2()
.py_1()
.rounded_t(px(8.))
.bg(cx.theme().colors().element_background)
.gap_2()
.child(
h_stack()
.w_px()
@ -175,7 +174,9 @@ impl Render for ChannelModal {
.child(
h_stack()
.w_full()
.h(rems(22. / 16.))
.justify_between()
.line_height(rems(1.25))
.child(
h_stack()
.gap_2()
@ -190,38 +191,54 @@ impl Render for ChannelModal {
)
.on_click(cx.listener(Self::set_channel_visiblity)),
)
.child(Label::new("Public")),
.child(Label::new("Public").size(LabelSize::Small)),
)
.children(if visibility == ChannelVisibility::Public {
Some(Button::new("copy-link", "Copy Link").on_click(cx.listener(
move |this, _, cx| {
if let Some(channel) =
this.channel_store.read(cx).channel_for_id(channel_id)
.children(
Some(
Button::new("copy-link", "Copy Link")
.label_size(LabelSize::Small)
.on_click(cx.listener(move |this, _, cx| {
if let Some(channel) = this
.channel_store
.read(cx)
.channel_for_id(channel_id)
{
let item = ClipboardItem::new(channel.link());
cx.write_to_clipboard(item);
}
},
)))
} else {
None
}),
})),
)
.filter(|_| visibility == ChannelVisibility::Public),
),
)
.child(
div()
.w_full()
.flex()
.flex_row()
h_stack()
.child(
Button::new("manage-members", "Manage Members")
.selected(mode == Mode::ManageMembers)
div()
.id("manage-members")
.px_2()
.py_1()
.cursor_pointer()
.border_b_2()
.when(mode == Mode::ManageMembers, |this| {
this.border_color(cx.theme().colors().border)
})
.child(Label::new("Manage Members"))
.on_click(cx.listener(|this, _, cx| {
this.set_mode(Mode::ManageMembers, cx);
})),
)
.child(
Button::new("invite-members", "Invite Members")
.selected(mode == Mode::InviteMembers)
div()
.id("invite-members")
.px_2()
.py_1()
.cursor_pointer()
.border_b_2()
.when(mode == Mode::InviteMembers, |this| {
this.border_color(cx.theme().colors().border)
})
.child(Label::new("Invite Members"))
.on_click(cx.listener(|this, _, cx| {
this.set_mode(Mode::InviteMembers, cx);
})),

View File

@ -10,11 +10,12 @@ use gpui::{
};
use project::{Project, RepositoryEntry};
use recent_projects::RecentProjects;
use rpc::proto;
use std::sync::Arc;
use theme::{ActiveTheme, PlayerColors};
use ui::{
h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
IconButton, IconElement, Tooltip,
IconButton, IconElement, TintColor, Tooltip,
};
use util::ResultExt;
use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
@ -175,14 +176,17 @@ impl Render for CollabTitlebarItem {
let is_muted = room.is_muted(cx);
let is_deafened = room.is_deafened().unwrap_or(false);
let is_screen_sharing = room.is_screen_sharing();
let read_only = room.read_only();
this.when(is_local, |this| {
this.when(is_local && !read_only, |this| {
this.child(
Button::new(
"toggle_sharing",
if is_shared { "Unshare" } else { "Share" },
)
.style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.selected(is_shared)
.label_size(LabelSize::Small)
.on_click(cx.listener(
move |this, _, cx| {
@ -205,7 +209,8 @@ impl Render for CollabTitlebarItem {
.detach_and_log_err(cx);
}),
)
.child(
.when(!read_only, |this| {
this.child(
IconButton::new(
"mute-microphone",
if is_muted {
@ -217,8 +222,10 @@ impl Render for CollabTitlebarItem {
.style(ButtonStyle::Subtle)
.icon_size(IconSize::Small)
.selected(is_muted)
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
)
})
.child(
IconButton::new(
"mute-sound",
@ -229,23 +236,36 @@ impl Render for CollabTitlebarItem {
},
)
.style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
.icon_size(IconSize::Small)
.selected(is_deafened)
.tooltip(move |cx| {
Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx)
})
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
if !read_only {
Tooltip::with_meta(
"Deafen Audio",
None,
"Mic will be muted",
cx,
)
.child(
} else {
Tooltip::text("Deafen Audio", cx)
}
})
.on_click(move |_, cx| crate::toggle_deafen(&Default::default(), cx)),
)
.when(!read_only, |this| {
this.child(
IconButton::new("screen-share", ui::Icon::Screen)
.style(ButtonStyle::Subtle)
.icon_size(IconSize::Small)
.selected(is_screen_sharing)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.on_click(move |_, cx| {
crate::toggle_screen_sharing(&Default::default(), cx)
}),
)
})
})
.map(|el| {
let status = self.client.status();
let status = &*status.borrow();
@ -409,6 +429,10 @@ impl CollabTitlebarItem {
current_user: &Arc<User>,
cx: &ViewContext<Self>,
) -> Option<FacePile> {
if room.role_for_user(user.id) == Some(proto::ChannelRole::Guest) {
return None;
}
let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
let pile = FacePile::default()
@ -504,8 +528,7 @@ impl CollabTitlebarItem {
| client::Status::ReconnectionError { .. } => Some(
div()
.id("disconnected")
.bg(gpui::red()) // todo!() @nate
.child(IconElement::new(Icon::Disconnected))
.child(IconElement::new(Icon::Disconnected).size(IconSize::Small))
.tooltip(|cx| Tooltip::text("Disconnected", cx))
.into_any_element(),
),
@ -522,9 +545,9 @@ impl CollabTitlebarItem {
};
Some(
div()
.bg(gpui::red()) // todo!() @nate
.child(Button::new("connection-status", label).on_click(|_, cx| {
Button::new("connection-status", label)
.label_size(LabelSize::Small)
.on_click(|_, cx| {
if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
if auto_updater.read(cx).status() == AutoUpdateStatus::Updated {
workspace::restart(&Default::default(), cx);
@ -532,7 +555,7 @@ impl CollabTitlebarItem {
}
}
auto_update::check(&Default::default(), cx);
}))
})
.into_any_element(),
)
}
@ -542,7 +565,9 @@ impl CollabTitlebarItem {
pub fn render_sign_in_button(&mut self, _: &mut ViewContext<Self>) -> Button {
let client = self.client.clone();
Button::new("sign_in", "Sign in").on_click(move |_, cx| {
Button::new("sign_in", "Sign in")
.label_size(LabelSize::Small)
.on_click(move |_, cx| {
let client = client.clone();
cx.spawn(move |mut cx| async move {
client

View File

@ -151,7 +151,12 @@ impl ProjectDiagnosticsEditor {
let focus_in_subscription =
cx.on_focus_in(&focus_handle, |diagnostics, cx| diagnostics.focus_in(cx));
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).replica_id()));
let excerpts = cx.new_model(|cx| {
MultiBuffer::new(
project_handle.read(cx).replica_id(),
project_handle.read(cx).capability(),
)
});
let editor = cx.new_view(|cx| {
let mut editor =
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), cx);
@ -1572,6 +1577,7 @@ mod tests {
workspace::init_settings(cx);
Project::init_settings(cx);
crate::init(cx);
editor::init(cx);
});
}

View File

@ -54,10 +54,10 @@ use itertools::Itertools;
pub use language::{char_kind, CharKind};
use language::{
language_settings::{self, all_language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel,
Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language,
LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal,
TransactionId,
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
SelectionGoal, TransactionId,
};
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
@ -2049,8 +2049,8 @@ impl Editor {
}
}
pub fn read_only(&self) -> bool {
self.read_only
pub fn read_only(&self, cx: &AppContext) -> bool {
self.read_only || self.buffer.read(cx).read_only()
}
pub fn set_read_only(&mut self, read_only: bool) {
@ -2199,7 +2199,7 @@ impl Editor {
S: ToOffset,
T: Into<Arc<str>>,
{
if self.read_only {
if self.read_only(cx) {
return;
}
@ -2213,7 +2213,7 @@ impl Editor {
S: ToOffset,
T: Into<Arc<str>>,
{
if self.read_only {
if self.read_only(cx) {
return;
}
@ -2232,7 +2232,7 @@ impl Editor {
S: ToOffset,
T: Into<Arc<str>>,
{
if self.read_only {
if self.read_only(cx) {
return;
}
@ -2596,7 +2596,7 @@ impl Editor {
pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
let text: Arc<str> = text.into();
if self.read_only {
if self.read_only(cx) {
return;
}
@ -3049,7 +3049,7 @@ impl Editor {
autoindent_mode: Option<AutoindentMode>,
cx: &mut ViewContext<Self>,
) {
if self.read_only {
if self.read_only(cx) {
return;
}
@ -3786,7 +3786,8 @@ impl Editor {
let mut ranges_to_highlight = Vec::new();
let excerpt_buffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(replica_id).with_title(title);
let mut multibuffer =
MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
for (buffer_handle, transaction) in &entries {
let buffer = buffer_handle.read(cx);
ranges_to_highlight.extend(
@ -7491,9 +7492,10 @@ impl Editor {
locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
let mut locations = locations.into_iter().peekable();
let mut ranges_to_highlight = Vec::new();
let capability = workspace.project().read(cx).capability();
let excerpt_buffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(replica_id);
let mut multibuffer = MultiBuffer::new(replica_id, capability);
while let Some(location) = locations.next() {
let buffer = location.buffer.read(cx);
let mut ranges_for_buffer = Vec::new();
@ -8608,7 +8610,8 @@ impl Editor {
}
pub fn show_local_cursors(&self, cx: &WindowContext) -> bool {
self.blink_manager.read(cx).visible() && self.focus_handle.is_focused(cx)
(self.read_only(cx) || self.blink_manager.read(cx).visible())
&& self.focus_handle.is_focused(cx)
}
fn on_buffer_changed(&mut self, _: Model<MultiBuffer>, cx: &mut ViewContext<Self>) {

View File

@ -17,8 +17,9 @@ use gpui::{
use indoc::indoc;
use language::{
language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry,
Override, Point,
BracketPairConfig,
Capability::ReadWrite,
FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageRegistry, Override, Point,
};
use parking_lot::Mutex;
use project::project_settings::{LspSettings, ProjectSettings};
@ -2355,7 +2356,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
.with_language(rust_language, cx)
});
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts(
toml_buffer.clone(),
[ExcerptRange {
@ -6019,7 +6020,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts(
buffer.clone(),
[
@ -6103,7 +6104,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
});
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
multibuffer
});
@ -6162,7 +6163,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
let mut excerpt1_id = None;
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
excerpt1_id = multibuffer
.push_excerpts(
buffer.clone(),
@ -6247,7 +6248,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
let mut excerpt1_id = None;
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
excerpt1_id = multibuffer
.push_excerpts(
buffer.clone(),
@ -6636,7 +6637,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
let leader = pane.update(cx, |_, cx| {
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
});
@ -7425,7 +7426,7 @@ async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::T
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts(
buffer_1.clone(),
[ExcerptRange {
@ -7552,7 +7553,7 @@ async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui
.unwrap();
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts(
private_buffer.clone(),
[ExcerptRange {

View File

@ -1910,7 +1910,13 @@ impl EditorElement {
layouts.push(layout);
}
selections.push((style.local_player, layouts));
let player = if editor.read_only(cx) {
cx.theme().players().read_only()
} else {
style.local_player
};
selections.push((player, layouts));
}
if let Some(collaboration_hub) = &editor.collaboration_hub {

View File

@ -93,6 +93,7 @@ mod tests {
use crate::editor_tests::init_test;
use crate::Point;
use gpui::{Context, TestAppContext};
use language::Capability::ReadWrite;
use multi_buffer::{ExcerptRange, MultiBuffer};
use project::{FakeFs, Project};
use unindent::Unindent;
@ -183,7 +184,7 @@ mod tests {
cx.background_executor.run_until_parked();
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, ReadWrite);
multibuffer.push_excerpts(
buffer_1.clone(),
[

View File

@ -515,34 +515,28 @@ impl DiagnosticPopover {
};
struct DiagnosticColors {
pub text: Hsla,
pub background: Hsla,
pub border: Hsla,
}
let diagnostic_colors = match self.local_diagnostic.diagnostic.severity {
DiagnosticSeverity::ERROR => DiagnosticColors {
text: style.status.error,
background: style.status.error_background,
border: style.status.error_border,
},
DiagnosticSeverity::WARNING => DiagnosticColors {
text: style.status.warning,
background: style.status.warning_background,
border: style.status.warning_border,
},
DiagnosticSeverity::INFORMATION => DiagnosticColors {
text: style.status.info,
background: style.status.info_background,
border: style.status.info_border,
},
DiagnosticSeverity::HINT => DiagnosticColors {
text: style.status.hint,
background: style.status.hint_background,
border: style.status.hint_border,
},
_ => DiagnosticColors {
text: style.status.ignored,
background: style.status.ignored_background,
border: style.status.ignored_border,
},
@ -554,7 +548,7 @@ impl DiagnosticPopover {
.px_2()
.py_1()
.bg(diagnostic_colors.background)
.text_color(diagnostic_colors.text)
.text_color(style.text.color)
.border_1()
.border_color(diagnostic_colors.border)
.rounded_md()

View File

@ -1206,7 +1206,8 @@ pub mod tests {
use gpui::{Context, TestAppContext, WindowHandle};
use itertools::Itertools;
use language::{
language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
LanguageConfig,
};
use lsp::FakeLanguageServer;
use parking_lot::Mutex;
@ -2459,7 +2460,7 @@ pub mod tests {
.await
.unwrap();
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
multibuffer.push_excerpts(
buffer_1.clone(),
[
@ -2798,7 +2799,7 @@ pub mod tests {
})
.await
.unwrap();
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
let buffer_1_excerpts = multibuffer.push_excerpts(
buffer_1.clone(),

View File

@ -15,9 +15,11 @@ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
Point, SelectionGoal,
};
use project::repository::GitFileStatus;
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use workspace::item::ItemSettings;
use std::fmt::Write;
use std::{
@ -29,7 +31,7 @@ use std::{
sync::Arc,
};
use text::Selection;
use theme::{ActiveTheme, Theme};
use theme::Theme;
use ui::{h_stack, prelude::*, Label};
use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
use workspace::{
@ -101,7 +103,8 @@ impl FollowableItem for Editor {
if state.singleton && buffers.len() == 1 {
multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
} else {
multibuffer = MultiBuffer::new(replica_id);
multibuffer =
MultiBuffer::new(replica_id, project.read(cx).capability());
let mut excerpts = state.excerpts.into_iter().peekable();
while let Some(excerpt) = excerpts.peek() {
let buffer_id = excerpt.buffer_id;
@ -579,7 +582,28 @@ impl Item for Editor {
}
fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
let _theme = cx.theme();
let git_status = if ItemSettings::get_global(cx).git_status {
self.buffer()
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).project_path(cx))
.and_then(|path| self.project.as_ref()?.read(cx).entry_for_path(&path, cx))
.and_then(|entry| entry.git_status())
} else {
None
};
let label_color = match git_status {
Some(GitFileStatus::Added) => Color::Created,
Some(GitFileStatus::Modified) => Color::Modified,
Some(GitFileStatus::Conflict) => Color::Conflict,
None => {
if selected {
Color::Default
} else {
Color::Muted
}
}
};
let description = detail.and_then(|detail| {
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
@ -595,11 +619,7 @@ impl Item for Editor {
h_stack()
.gap_2()
.child(Label::new(self.title(cx).to_string()).color(if selected {
Color::Default
} else {
Color::Muted
}))
.child(Label::new(self.title(cx).to_string()).color(label_color))
.when_some(description, |this, description| {
this.child(
Label::new(description)

View File

@ -930,10 +930,7 @@ mod tests {
fn do_work() { «test»(); }
"});
// Deactivating the window dismisses the highlight
cx.update_workspace(|workspace, cx| {
workspace.on_window_activation_changed(cx);
});
cx.cx.cx.deactivate_window();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
fn test() { do_work(); }
fn do_work() { test(); }

View File

@ -461,6 +461,7 @@ mod tests {
Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
};
use gpui::{font, Context as _};
use language::Capability;
use project::Project;
use settings::SettingsStore;
use util::post_inc;
@ -766,7 +767,7 @@ mod tests {
let buffer =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abc\ndefg\nhijkl\nmn"));
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
multibuffer.push_excerpts(
buffer.clone(),
[

View File

@ -17,7 +17,7 @@ use regex::Regex;
use serde_derive::Serialize;
use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};
use util::ResultExt;
use workspace::{ModalView, Workspace};
use workspace::{ModalView, Toast, Workspace};
use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedCommunityRepo};
@ -125,6 +125,20 @@ impl FeedbackModal {
.language_for_name("Markdown");
let project = workspace.project().clone();
let is_local_project = project.read(cx).is_local();
if !is_local_project {
const TOAST_ID: usize = 0xdeadbeef;
workspace.show_toast(
Toast::new(
TOAST_ID,
"You can only submit feedback in your own project.",
),
cx,
);
return;
}
cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err();

View File

@ -18,8 +18,7 @@ collections = { path = "../collections" }
gpui_macros = { path = "../gpui_macros" }
util = { path = "../util" }
sum_tree = { path = "../sum_tree" }
sqlez = { path = "../sqlez" }
async-task = "4.0.3"
async-task = "4.7"
backtrace = { version = "0.3", optional = true }
ctor.workspace = true
linkme = "0.3"

View File

@ -1,8 +1,6 @@
use crate::{private::Sealed, AppContext, Context, Entity, ModelContext};
use anyhow::{anyhow, Result};
use collections::HashMap;
use derive_more::{Deref, DerefMut};
use lazy_static::lazy_static;
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use slotmap::{SecondaryMap, SlotMap};
use std::{
@ -18,6 +16,9 @@ use std::{
thread::panicking,
};
#[cfg(any(test, feature = "test-support"))]
use collections::HashMap;
slotmap::new_key_type! { pub struct EntityId; }
impl EntityId {
@ -600,7 +601,7 @@ impl<T> PartialEq<Model<T>> for WeakModel<T> {
}
#[cfg(any(test, feature = "test-support"))]
lazy_static! {
lazy_static::lazy_static! {
static ref LEAK_BACKTRACE: bool =
std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
}

View File

@ -1,14 +1,13 @@
use crate::{
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
InputEvent, IntoElement, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform,
PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext,
WindowHandle, WindowOptions,
BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, Size, Task,
TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext,
WindowContext, WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt};
use std::{future::Future, mem, ops::Deref, rc::Rc, sync::Arc, time::Duration};
use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
#[derive(Clone)]
pub struct TestAppContext {
@ -185,42 +184,7 @@ impl TestAppContext {
}
pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
let (mut handlers, scale_factor) = self
.app
.borrow_mut()
.update_window(window_handle, |_, cx| {
let platform_window = cx.window.platform_window.as_test().unwrap();
let scale_factor = platform_window.scale_factor();
match &mut platform_window.bounds {
WindowBounds::Fullscreen | WindowBounds::Maximized => {
platform_window.bounds = WindowBounds::Fixed(Bounds {
origin: Point::default(),
size: size.map(|pixels| f64::from(pixels).into()),
});
}
WindowBounds::Fixed(bounds) => {
bounds.size = size.map(|pixels| f64::from(pixels).into());
}
}
(
mem::take(&mut platform_window.handlers.lock().resize),
scale_factor,
)
})
.unwrap();
for handler in &mut handlers {
handler(size, scale_factor);
}
self.app
.borrow_mut()
.update_window(window_handle, |_, cx| {
let platform_window = cx.window.platform_window.as_test().unwrap();
platform_window.handlers.lock().resize = handlers;
})
.unwrap();
self.test_window(window_handle).simulate_resize(size);
}
pub fn windows(&self) -> Vec<AnyWindowHandle> {
@ -317,41 +281,22 @@ impl TestAppContext {
keystroke: Keystroke,
is_held: bool,
) {
let keystroke2 = keystroke.clone();
let handled = window
.update(self, |_, cx| {
cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
})
.is_ok_and(|handled| handled);
if handled {
return;
self.test_window(window)
.simulate_keystroke(keystroke, is_held)
}
let input_handler = self.update_test_window(window, |window| window.input_handler.clone());
let Some(input_handler) = input_handler else {
panic!(
"dispatch_keystroke {:?} failed to dispatch action or input",
&keystroke2
);
};
let text = keystroke2.ime_key.unwrap_or(keystroke2.key);
input_handler.lock().replace_text_in_range(None, &text);
}
pub fn update_test_window<R>(
&mut self,
window: AnyWindowHandle,
f: impl FnOnce(&mut TestWindow) -> R,
) -> R {
window
.update(self, |_, cx| {
f(cx.window
.platform_window
.as_any_mut()
.downcast_mut::<TestWindow>()
.unwrap())
})
pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
self.app
.borrow_mut()
.windows
.get_mut(window.id)
.unwrap()
.as_mut()
.unwrap()
.platform_window
.as_test()
.unwrap()
.clone()
}
pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
@ -567,11 +512,7 @@ impl<'a> VisualTestContext<'a> {
}
pub fn window_title(&mut self) -> Option<String> {
self.cx
.update_window(self.window, |_, cx| {
cx.window.platform_window.as_test().unwrap().title.clone()
})
.unwrap()
self.cx.test_window(self.window).0.lock().title.clone()
}
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
@ -582,37 +523,11 @@ impl<'a> VisualTestContext<'a> {
self.cx.simulate_input(self.window, input)
}
pub fn simulate_activation(&mut self) {
self.simulate_window_events(&mut |handlers| {
handlers
.active_status_change
.iter_mut()
.for_each(|f| f(true));
})
pub fn deactivate_window(&mut self) {
if Some(self.window) == self.test_platform.active_window() {
self.test_platform.set_active_window(None)
}
pub fn simulate_deactivation(&mut self) {
self.simulate_window_events(&mut |handlers| {
handlers
.active_status_change
.iter_mut()
.for_each(|f| f(false));
})
}
fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
let handlers = self
.cx
.update_window(self.window, |_, cx| {
cx.window
.platform_window
.as_test()
.unwrap()
.handlers
.clone()
})
.unwrap();
f(&mut *handlers.lock());
self.background_executor.run_until_parked();
}
}

View File

@ -339,6 +339,15 @@ impl Hsla {
}
}
pub fn grayscale(&self) -> Self {
Hsla {
h: self.h,
s: 0.,
l: self.l,
a: self.a,
}
}
/// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
/// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
pub fn fade_out(&mut self, factor: f32) {

View File

@ -44,8 +44,9 @@ pub trait IntoElement: Sized {
}
/// Convert into an element, then draw in the current window at the given origin.
/// The provided available space is provided to the layout engine to determine the size of the root element.
/// Once the element is drawn, its associated element staet is yielded to the given callback.
/// The available space argument is provided to the layout engine to determine the size of the
// root element. Once the element is drawn, its associated element state is yielded to the
// given callback.
fn draw_and_update_state<T, R>(
self,
origin: Point<Pixels>,

View File

@ -31,39 +31,6 @@ pub trait Along {
fn apply_along(&self, axis: Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self;
}
impl sqlez::bindable::StaticColumnCount for Axis {}
impl sqlez::bindable::Bind for Axis {
fn bind(
&self,
statement: &sqlez::statement::Statement,
start_index: i32,
) -> anyhow::Result<i32> {
match self {
Axis::Horizontal => "Horizontal",
Axis::Vertical => "Vertical",
}
.bind(statement, start_index)
}
}
impl sqlez::bindable::Column for Axis {
fn column(
statement: &mut sqlez::statement::Statement,
start_index: i32,
) -> anyhow::Result<(Self, i32)> {
String::column(statement, start_index).and_then(|(axis_text, next_index)| {
Ok((
match axis_text.as_str() {
"Horizontal" => Axis::Horizontal,
"Vertical" => Axis::Vertical,
_ => anyhow::bail!("Stored serialized item kind is incorrect"),
},
next_index,
))
})
}
}
/// Describes a location in a 2D cartesian coordinate space.
///
/// It holds two public fields, `x` and `y`, which represent the coordinates in the space.
@ -2296,18 +2263,6 @@ impl From<f64> for GlobalPixels {
}
}
impl sqlez::bindable::StaticColumnCount for GlobalPixels {}
impl sqlez::bindable::Bind for GlobalPixels {
fn bind(
&self,
statement: &sqlez::statement::Statement,
start_index: i32,
) -> anyhow::Result<i32> {
self.0.bind(statement, start_index)
}
}
/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [WindowContext::set_rem_size].
///
/// Rems are used for defining lengths that are scalable and consistent across different UI elements.

View File

@ -188,15 +188,13 @@ impl DispatchTree {
action: &dyn Action,
context_stack: &Vec<KeyContext>,
) -> Vec<KeyBinding> {
self.keymap
.lock()
.bindings_for_action(action.type_id())
.filter(|candidate| {
if !candidate.action.partial_eq(action) {
return false;
}
let keymap = self.keymap.lock();
keymap
.bindings_for_action(action)
.filter(|binding| {
for i in 1..context_stack.len() {
if candidate.matches_context(&context_stack[0..=i]) {
let context = &context_stack[0..i];
if keymap.binding_enabled(binding, context) {
return true;
}
}

View File

@ -1,4 +1,4 @@
use crate::{Action, KeyBindingContextPredicate, KeyContext, KeyMatch, Keystroke};
use crate::{Action, KeyBindingContextPredicate, KeyMatch, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
@ -42,21 +42,8 @@ impl KeyBinding {
})
}
pub fn matches_context(&self, contexts: &[KeyContext]) -> bool {
self.context_predicate
.as_ref()
.map(|predicate| predicate.eval(contexts))
.unwrap_or(true)
}
pub fn match_keystrokes(
&self,
pending_keystrokes: &[Keystroke],
contexts: &[KeyContext],
) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(pending_keystrokes)
&& self.matches_context(contexts)
{
pub fn match_keystrokes(&self, pending_keystrokes: &[Keystroke]) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
// If the binding is completed, push it onto the matches list
if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
KeyMatch::Some(vec![self.action.boxed_clone()])
@ -68,18 +55,6 @@ impl KeyBinding {
}
}
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
if self.action.partial_eq(action) && self.matches_context(contexts) {
Some(self.keystrokes.clone())
} else {
None
}
}
pub fn keystrokes(&self) -> &[Keystroke] {
self.keystrokes.as_slice()
}
@ -88,3 +63,13 @@ impl KeyBinding {
self.action.as_ref()
}
}
impl std::fmt::Debug for KeyBinding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KeyBinding")
.field("keystrokes", &self.keystrokes)
.field("context_predicate", &self.context_predicate)
.field("action", &self.action.name())
.finish()
}
}

View File

@ -1,4 +1,4 @@
use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke, NoAction};
use crate::{Action, KeyBinding, KeyBindingContextPredicate, KeyContext, Keystroke, NoAction};
use collections::HashSet;
use smallvec::SmallVec;
use std::{
@ -29,54 +29,22 @@ impl Keymap {
self.version
}
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ KeyBinding> {
self.binding_indices_by_action_id
.get(&action_id)
.map(SmallVec::as_slice)
.unwrap_or(&[])
.iter()
.map(|ix| &self.bindings[*ix])
.filter(|binding| !self.binding_disabled(binding))
}
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
let no_action_id = &(NoAction {}).type_id();
let mut new_bindings = Vec::new();
let mut has_new_disabled_keystrokes = false;
let no_action_id = (NoAction {}).type_id();
for binding in bindings {
if binding.action.type_id() == *no_action_id {
has_new_disabled_keystrokes |= self
.disabled_keystrokes
let action_id = binding.action().as_any().type_id();
if action_id == no_action_id {
self.disabled_keystrokes
.entry(binding.keystrokes)
.or_default()
.insert(binding.context_predicate);
} else {
new_bindings.push(binding);
}
}
if has_new_disabled_keystrokes {
self.binding_indices_by_action_id.retain(|_, indices| {
indices.retain(|ix| {
let binding = &self.bindings[*ix];
match self.disabled_keystrokes.get(&binding.keystrokes) {
Some(disabled_predicates) => {
!disabled_predicates.contains(&binding.context_predicate)
}
None => true,
}
});
!indices.is_empty()
});
}
for new_binding in new_bindings {
if !self.binding_disabled(&new_binding) {
self.binding_indices_by_action_id
.entry(new_binding.action().as_any().type_id())
.entry(action_id)
.or_default()
.push(self.bindings.len());
self.bindings.push(new_binding);
self.bindings.push(binding);
}
}
@ -90,311 +58,113 @@ impl Keymap {
self.version.0 += 1;
}
pub fn bindings(&self) -> Vec<&KeyBinding> {
self.bindings
/// Iterate over all bindings, in the order they were added.
pub fn bindings(&self) -> impl Iterator<Item = &KeyBinding> + DoubleEndedIterator {
self.bindings.iter()
}
/// Iterate over all bindings for the given action, in the order they were added.
pub fn bindings_for_action<'a>(
&'a self,
action: &'a dyn Action,
) -> impl 'a + Iterator<Item = &'a KeyBinding> + DoubleEndedIterator {
let action_id = action.type_id();
self.binding_indices_by_action_id
.get(&action_id)
.map_or(&[] as _, SmallVec::as_slice)
.iter()
.filter(|binding| !self.binding_disabled(binding))
.collect()
.map(|ix| &self.bindings[*ix])
.filter(move |binding| binding.action().partial_eq(action))
}
fn binding_disabled(&self, binding: &KeyBinding) -> bool {
match self.disabled_keystrokes.get(&binding.keystrokes) {
Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
None => false,
pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
// If binding has a context predicate, it must match the current context,
if let Some(predicate) = &binding.context_predicate {
if !predicate.eval(context) {
return false;
}
}
if let Some(disabled_predicates) = self.disabled_keystrokes.get(&binding.keystrokes) {
for disabled_predicate in disabled_predicates {
match disabled_predicate {
// The binding must not be globally disabled.
None => return false,
// The binding must not be disabled in the current context.
Some(predicate) => {
if predicate.eval(context) {
return false;
}
}
}
}
}
// #[cfg(test)]
// mod tests {
// use crate::actions;
true
}
}
// use super::*;
#[cfg(test)]
mod tests {
use super::*;
use crate as gpui;
use gpui::actions;
// actions!(
// keymap_test,
// [Present1, Present2, Present3, Duplicate, Missing]
// );
actions!(
keymap_test,
[ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
);
// #[test]
// fn regular_keymap() {
// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
// let missing = Binding::new("ctrl-r", Missing {}, None);
// let all_bindings = [
// &present_1,
// &present_2,
// &present_3,
// &keystroke_duplicate_to_1,
// &full_duplicate_to_2,
// &missing,
// ];
#[test]
fn test_keymap() {
let bindings = [
KeyBinding::new("ctrl-a", ActionAlpha {}, None),
KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
];
// let mut keymap = Keymap::default();
// assert_absent(&keymap, &all_bindings);
// assert!(keymap.bindings().is_empty());
let mut keymap = Keymap::default();
keymap.add_bindings(bindings.clone());
// keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
// assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
// assert_present(
// &keymap,
// &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
// );
// global bindings are enabled in all contexts
assert!(keymap.binding_enabled(&bindings[0], &[]));
assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]));
// keymap.add_bindings([
// keystroke_duplicate_to_1.clone(),
// full_duplicate_to_2.clone(),
// ]);
// assert_absent(&keymap, &[&missing]);
// assert!(
// !keymap.binding_disabled(&keystroke_duplicate_to_1),
// "Duplicate binding 1 was added and should not be disabled"
// );
// assert!(
// !keymap.binding_disabled(&full_duplicate_to_2),
// "Duplicate binding 2 was added and should not be disabled"
// );
// contextual bindings are enabled in contexts that match their predicate
assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]));
assert!(keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]));
// assert_eq!(
// keymap
// .bindings_for_action(keystroke_duplicate_to_1.action().id())
// .map(|binding| &binding.keystrokes)
// .flatten()
// .collect::<Vec<_>>(),
// vec![&Keystroke {
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// key: "q".to_string(),
// ime_key: None,
// }],
// "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
// );
// assert_eq!(
// keymap
// .bindings_for_action(full_duplicate_to_2.action().id())
// .map(|binding| &binding.keystrokes)
// .flatten()
// .collect::<Vec<_>>(),
// vec![
// &Keystroke {
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// key: "w".to_string(),
// ime_key: None,
// },
// &Keystroke {
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// key: "w".to_string(),
// ime_key: None,
// }
// ],
// "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
// );
assert!(!keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]));
assert!(keymap.binding_enabled(
&bindings[2],
&[KeyContext::parse("editor mode=full").unwrap()]
));
}
// let updated_bindings = keymap.bindings();
// let expected_updated_bindings = vec![
// &present_1,
// &present_2,
// &present_3,
// &keystroke_duplicate_to_1,
// &full_duplicate_to_2,
// ];
// assert_eq!(
// updated_bindings.len(),
// expected_updated_bindings.len(),
// "Unexpected updated keymap bindings {updated_bindings:?}"
// );
// for (i, expected) in expected_updated_bindings.iter().enumerate() {
// let keymap_binding = &updated_bindings[i];
// assert_eq!(
// keymap_binding.context_predicate, expected.context_predicate,
// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
// );
// assert_eq!(
// keymap_binding.keystrokes, expected.keystrokes,
// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
// );
// }
#[test]
fn test_keymap_disabled() {
let bindings = [
KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
KeyBinding::new("ctrl-b", NoAction {}, None),
];
// keymap.clear();
// assert_absent(&keymap, &all_bindings);
// assert!(keymap.bindings().is_empty());
// }
let mut keymap = Keymap::default();
keymap.add_bindings(bindings.clone());
// #[test]
// fn keymap_with_ignored() {
// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
// let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
// let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
// let ignored_3_with_other_context =
// Binding::new("ctrl-e", NoAction {}, Some("other_context"));
// binding is only enabled in a specific context
assert!(!keymap.binding_enabled(&bindings[0], &[KeyContext::parse("barf").unwrap()]));
assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("editor").unwrap()]));
// let mut keymap = Keymap::default();
// binding is disabled in a more specific context
assert!(!keymap.binding_enabled(
&bindings[0],
&[KeyContext::parse("editor mode=full").unwrap()]
));
// keymap.add_bindings([
// ignored_1.clone(),
// ignored_2.clone(),
// ignored_3_with_other_context.clone(),
// ]);
// assert_absent(&keymap, &[&present_3]);
// assert_disabled(
// &keymap,
// &[
// &present_1,
// &present_2,
// &ignored_1,
// &ignored_2,
// &ignored_3_with_other_context,
// ],
// );
// assert!(keymap.bindings().is_empty());
// keymap.clear();
// keymap.add_bindings([
// present_1.clone(),
// present_2.clone(),
// present_3.clone(),
// ignored_1.clone(),
// ignored_2.clone(),
// ignored_3_with_other_context.clone(),
// ]);
// assert_present(&keymap, &[(&present_3, "e")]);
// assert_disabled(
// &keymap,
// &[
// &present_1,
// &present_2,
// &ignored_1,
// &ignored_2,
// &ignored_3_with_other_context,
// ],
// );
// keymap.clear();
// keymap.add_bindings([
// present_1.clone(),
// present_2.clone(),
// present_3.clone(),
// ignored_1.clone(),
// ]);
// assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
// assert_disabled(&keymap, &[&present_1, &ignored_1]);
// assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
// keymap.clear();
// keymap.add_bindings([
// present_1.clone(),
// present_2.clone(),
// present_3.clone(),
// keystroke_duplicate_to_1.clone(),
// full_duplicate_to_2.clone(),
// ignored_1.clone(),
// ignored_2.clone(),
// ignored_3_with_other_context.clone(),
// ]);
// assert_present(&keymap, &[(&present_3, "e")]);
// assert_disabled(
// &keymap,
// &[
// &present_1,
// &present_2,
// &keystroke_duplicate_to_1,
// &full_duplicate_to_2,
// &ignored_1,
// &ignored_2,
// &ignored_3_with_other_context,
// ],
// );
// keymap.clear();
// }
// #[track_caller]
// fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
// let keymap_bindings = keymap.bindings();
// assert_eq!(
// expected_bindings.len(),
// keymap_bindings.len(),
// "Unexpected keymap bindings {keymap_bindings:?}"
// );
// for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
// assert!(
// !keymap.binding_disabled(expected),
// "{expected:?} should not be disabled as it was added into keymap for element {i}"
// );
// assert_eq!(
// keymap
// .bindings_for_action(expected.action().id())
// .map(|binding| &binding.keystrokes)
// .flatten()
// .collect::<Vec<_>>(),
// vec![&Keystroke {
// ctrl: true,
// alt: false,
// shift: false,
// cmd: false,
// function: false,
// key: expected_key.to_string(),
// ime_key: None,
// }],
// "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
// );
// let keymap_binding = &keymap_bindings[i];
// assert_eq!(
// keymap_binding.context_predicate, expected.context_predicate,
// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
// );
// assert_eq!(
// keymap_binding.keystrokes, expected.keystrokes,
// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
// );
// }
// }
// #[track_caller]
// fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
// for binding in bindings.iter() {
// assert!(
// !keymap.binding_disabled(binding),
// "{binding:?} should not be disabled in the keymap where was not added"
// );
// assert_eq!(
// keymap.bindings_for_action(binding.action().id()).count(),
// 0,
// "{binding:?} should have no actions in the keymap where was not added"
// );
// }
// }
// #[track_caller]
// fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
// for binding in bindings.iter() {
// assert!(
// keymap.binding_disabled(binding),
// "{binding:?} should be disabled in the keymap"
// );
// assert_eq!(
// keymap.bindings_for_action(binding.action().id()).count(),
// 0,
// "{binding:?} should have no actions in the keymap where it was disabled"
// );
// }
// }
// }
// binding is globally disabled
assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf").unwrap()]));
}
}

View File

@ -1,6 +1,5 @@
use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::sync::Arc;
pub struct KeystrokeMatcher {
@ -51,10 +50,14 @@ impl KeystrokeMatcher {
let mut pending_key = None;
let mut found_actions = Vec::new();
for binding in keymap.bindings().iter().rev() {
for binding in keymap.bindings().rev() {
if !keymap.binding_enabled(binding, context_stack) {
continue;
}
for candidate in keystroke.match_candidates() {
self.pending_keystrokes.push(candidate.clone());
match binding.match_keystrokes(&self.pending_keystrokes, context_stack) {
match binding.match_keystrokes(&self.pending_keystrokes) {
KeyMatch::Some(mut actions) => {
found_actions.append(&mut actions);
}
@ -82,19 +85,6 @@ impl KeystrokeMatcher {
KeyMatch::Pending
}
}
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap
.lock()
.bindings()
.iter()
.rev()
.find_map(|binding| binding.keystrokes_for_action(action, contexts))
}
}
#[derive(Debug)]

View File

@ -6,19 +6,17 @@ mod mac;
mod test;
use crate::{
point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
Scene, SharedString, Size, TaskLabel,
Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics,
FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap, LineLayout, Pixels,
Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString,
Size, TaskLabel,
};
use anyhow::{anyhow, bail};
use anyhow::anyhow;
use async_task::Runnable;
use futures::channel::oneshot;
use parking::Unparker;
use seahash::SeaHasher;
use serde::{Deserialize, Serialize};
use sqlez::bindable::{Bind, Column, StaticColumnCount};
use sqlez::statement::Statement;
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use std::time::Duration;
@ -396,67 +394,6 @@ pub enum WindowBounds {
Fixed(Bounds<GlobalPixels>),
}
impl StaticColumnCount for WindowBounds {
fn column_count() -> usize {
5
}
}
impl Bind for WindowBounds {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let (region, next_index) = match self {
WindowBounds::Fullscreen => {
let next_index = statement.bind(&"Fullscreen", start_index)?;
(None, next_index)
}
WindowBounds::Maximized => {
let next_index = statement.bind(&"Maximized", start_index)?;
(None, next_index)
}
WindowBounds::Fixed(region) => {
let next_index = statement.bind(&"Fixed", start_index)?;
(Some(*region), next_index)
}
};
statement.bind(
&region.map(|region| {
(
region.origin.x,
region.origin.y,
region.size.width,
region.size.height,
)
}),
next_index,
)
}
}
impl Column for WindowBounds {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (window_state, next_index) = String::column(statement, start_index)?;
let bounds = match window_state.as_str() {
"Fullscreen" => WindowBounds::Fullscreen,
"Maximized" => WindowBounds::Maximized,
"Fixed" => {
let ((x, y, width, height), _) = Column::column(statement, next_index)?;
let x: f64 = x;
let y: f64 = y;
let width: f64 = width;
let height: f64 = height;
WindowBounds::Fixed(Bounds {
origin: point(x.into(), y.into()),
size: size(width.into(), height.into()),
})
}
_ => bail!("Window State did not have a valid string"),
};
Ok((bounds, next_index + 4))
}
}
#[derive(Copy, Clone, Debug)]
pub enum WindowAppearance {
Light,

View File

@ -11,7 +11,7 @@ use objc::{
};
use parking::{Parker, Unparker};
use parking_lot::Mutex;
use std::{ffi::c_void, sync::Arc, time::Duration};
use std::{ffi::c_void, ptr::NonNull, sync::Arc, time::Duration};
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
@ -47,7 +47,7 @@ impl PlatformDispatcher for MacDispatcher {
unsafe {
dispatch_async_f(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
runnable.into_raw() as *mut c_void,
runnable.into_raw().as_ptr() as *mut c_void,
Some(trampoline),
);
}
@ -57,7 +57,7 @@ impl PlatformDispatcher for MacDispatcher {
unsafe {
dispatch_async_f(
dispatch_get_main_queue(),
runnable.into_raw() as *mut c_void,
runnable.into_raw().as_ptr() as *mut c_void,
Some(trampoline),
);
}
@ -71,7 +71,7 @@ impl PlatformDispatcher for MacDispatcher {
dispatch_after_f(
when,
queue,
runnable.into_raw() as *mut c_void,
runnable.into_raw().as_ptr() as *mut c_void,
Some(trampoline),
);
}
@ -91,6 +91,6 @@ impl PlatformDispatcher for MacDispatcher {
}
extern "C" fn trampoline(runnable: *mut c_void) {
let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
task.run();
}

View File

@ -260,8 +260,8 @@ impl MacPlatform {
os_action,
} => {
let keystrokes = keymap
.bindings_for_action(action.type_id())
.find(|binding| binding.action().partial_eq(action.as_ref()))
.bindings_for_action(action.as_ref())
.next()
.map(|binding| binding.keystrokes());
let selector = match os_action {

View File

@ -19,7 +19,7 @@ pub struct TestPlatform {
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
pub(crate) active_window: RefCell<Option<TestWindow>>,
active_display: Rc<dyn PlatformDisplay>,
active_cursor: Mutex<CursorStyle>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
@ -79,6 +79,28 @@ impl TestPlatform {
self.prompts.borrow_mut().multiple_choice.push_back(tx);
rx
}
pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
let executor = self.foreground_executor().clone();
let previous_window = self.active_window.borrow_mut().take();
*self.active_window.borrow_mut() = window.clone();
executor
.spawn(async move {
if let Some(previous_window) = previous_window {
if let Some(window) = window.as_ref() {
if Arc::ptr_eq(&previous_window.0, &window.0) {
return;
}
}
previous_window.simulate_active_status_change(false);
}
if let Some(window) = window {
window.simulate_active_status_change(true);
}
})
.detach();
}
}
// todo!("implement out what our tests needed in GPUI 1")
@ -105,7 +127,9 @@ impl Platform for TestPlatform {
unimplemented!()
}
fn activate(&self, _ignoring_other_apps: bool) {}
fn activate(&self, _ignoring_other_apps: bool) {
//
}
fn hide(&self) {
unimplemented!()
@ -128,7 +152,10 @@ impl Platform for TestPlatform {
}
fn active_window(&self) -> Option<crate::AnyWindowHandle> {
self.active_window.lock().clone()
self.active_window
.borrow()
.as_ref()
.map(|window| window.0.lock().handle)
}
fn open_window(
@ -137,12 +164,13 @@ impl Platform for TestPlatform {
options: WindowOptions,
_draw: Box<dyn FnMut() -> Result<Scene>>,
) -> Box<dyn crate::PlatformWindow> {
*self.active_window.lock() = Some(handle);
Box::new(TestWindow::new(
let window = TestWindow::new(
options,
handle,
self.weak.clone(),
self.active_display.clone(),
))
);
Box::new(window)
}
fn set_display_link_output_callback(

View File

@ -1,7 +1,7 @@
use crate::{
px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance,
WindowBounds, WindowOptions,
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent,
Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
};
use collections::HashMap;
use parking_lot::Mutex;
@ -10,51 +10,122 @@ use std::{
sync::{self, Arc},
};
#[derive(Default)]
pub(crate) struct TestWindowHandlers {
pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
pub(crate) moved: Vec<Box<dyn FnMut()>>,
pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
}
pub struct TestWindow {
pub struct TestWindowState {
pub(crate) bounds: WindowBounds,
pub(crate) handle: AnyWindowHandle,
display: Rc<dyn PlatformDisplay>,
pub(crate) title: Option<String>,
pub(crate) edited: bool,
pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
platform: Weak<TestPlatform>,
sprite_atlas: Arc<dyn PlatformAtlas>,
input_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved_callback: Option<Box<dyn FnMut()>>,
input_handler: Option<Box<dyn PlatformInputHandler>>,
}
#[derive(Clone)]
pub struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
impl TestWindow {
pub fn new(
options: WindowOptions,
handle: AnyWindowHandle,
platform: Weak<TestPlatform>,
display: Rc<dyn PlatformDisplay>,
) -> Self {
Self {
Self(Arc::new(Mutex::new(TestWindowState {
bounds: options.bounds,
display,
platform,
input_handler: None,
handle,
sprite_atlas: Arc::new(TestAtlas::new()),
handlers: Default::default(),
title: Default::default(),
edited: false,
input_callback: None,
active_status_change_callback: None,
resize_callback: None,
moved_callback: None,
input_handler: None,
})))
}
pub fn simulate_resize(&mut self, size: Size<Pixels>) {
let scale_factor = self.scale_factor();
let mut lock = self.0.lock();
let Some(mut callback) = lock.resize_callback.take() else {
return;
};
match &mut lock.bounds {
WindowBounds::Fullscreen | WindowBounds::Maximized => {
lock.bounds = WindowBounds::Fixed(Bounds {
origin: Point::default(),
size: size.map(|pixels| f64::from(pixels).into()),
});
}
WindowBounds::Fixed(bounds) => {
bounds.size = size.map(|pixels| f64::from(pixels).into());
}
}
drop(lock);
callback(size, scale_factor);
self.0.lock().resize_callback = Some(callback);
}
pub(crate) fn simulate_active_status_change(&self, active: bool) {
let mut lock = self.0.lock();
let Some(mut callback) = lock.active_status_change_callback.take() else {
return;
};
drop(lock);
callback(active);
self.0.lock().active_status_change_callback = Some(callback);
}
pub fn simulate_input(&mut self, event: InputEvent) -> bool {
let mut lock = self.0.lock();
let Some(mut callback) = lock.input_callback.take() else {
return false;
};
drop(lock);
let result = callback(event);
self.0.lock().input_callback = Some(callback);
result
}
pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) {
if self.simulate_input(InputEvent::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
})) {
return;
}
let mut lock = self.0.lock();
let Some(mut input_handler) = lock.input_handler.take() else {
panic!(
"simulate_keystroke {:?} input event was not handled and there was no active input",
&keystroke
);
};
drop(lock);
let text = keystroke.ime_key.unwrap_or(keystroke.key);
input_handler.replace_text_in_range(None, &text);
self.0.lock().input_handler = Some(input_handler);
}
}
impl PlatformWindow for TestWindow {
fn bounds(&self) -> WindowBounds {
self.bounds
self.0.lock().bounds
}
fn content_size(&self) -> Size<Pixels> {
let bounds = match self.bounds {
let bounds = match self.bounds() {
WindowBounds::Fixed(bounds) => bounds,
WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
};
@ -74,7 +145,7 @@ impl PlatformWindow for TestWindow {
}
fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
self.display.clone()
self.0.lock().display.clone()
}
fn mouse_position(&self) -> Point<Pixels> {
@ -90,11 +161,11 @@ impl PlatformWindow for TestWindow {
}
fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
self.input_handler = Some(Arc::new(Mutex::new(input_handler)));
self.0.lock().input_handler = Some(input_handler);
}
fn clear_input_handler(&mut self) {
self.input_handler = None;
self.0.lock().input_handler = None;
}
fn prompt(
@ -103,19 +174,29 @@ impl PlatformWindow for TestWindow {
_msg: &str,
_answers: &[&str],
) -> futures::channel::oneshot::Receiver<usize> {
self.platform.upgrade().expect("platform dropped").prompt()
self.0
.lock()
.platform
.upgrade()
.expect("platform dropped")
.prompt()
}
fn activate(&self) {
unimplemented!()
self.0
.lock()
.platform
.upgrade()
.unwrap()
.set_active_window(Some(self.clone()))
}
fn set_title(&mut self, title: &str) {
self.title = Some(title.to_owned());
self.0.lock().title = Some(title.to_owned());
}
fn set_edited(&mut self, edited: bool) {
self.edited = edited;
self.0.lock().edited = edited;
}
fn show_character_palette(&self) {
@ -135,15 +216,15 @@ impl PlatformWindow for TestWindow {
}
fn on_input(&self, callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
self.handlers.lock().input.push(callback)
self.0.lock().input_callback = Some(callback)
}
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.handlers.lock().active_status_change.push(callback)
self.0.lock().active_status_change_callback = Some(callback)
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.handlers.lock().resize.push(callback)
self.0.lock().resize_callback = Some(callback)
}
fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
@ -151,7 +232,7 @@ impl PlatformWindow for TestWindow {
}
fn on_moved(&self, callback: Box<dyn FnMut()>) {
self.handlers.lock().moved.push(callback)
self.0.lock().moved_callback = Some(callback)
}
fn on_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {
@ -175,7 +256,7 @@ impl PlatformWindow for TestWindow {
}
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
self.sprite_atlas.clone()
self.0.lock().sprite_atlas.clone()
}
fn as_test(&mut self) -> Option<&mut TestWindow> {

View File

@ -57,6 +57,12 @@ lazy_static! {
pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new();
}
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Capability {
ReadWrite,
ReadOnly,
}
pub struct Buffer {
text: TextBuffer,
diff_base: Option<String>,
@ -90,6 +96,7 @@ pub struct Buffer {
completion_triggers: Vec<String>,
completion_triggers_timestamp: clock::Lamport,
deferred_ops: OperationQueue<Operation>,
capability: Capability,
}
pub struct BufferSnapshot {
@ -405,19 +412,27 @@ impl Buffer {
TextBuffer::new(replica_id, id, base_text.into()),
None,
None,
Capability::ReadWrite,
)
}
pub fn remote(remote_id: u64, replica_id: ReplicaId, base_text: String) -> Self {
pub fn remote(
remote_id: u64,
replica_id: ReplicaId,
capability: Capability,
base_text: String,
) -> Self {
Self::build(
TextBuffer::new(replica_id, remote_id, base_text),
None,
None,
capability,
)
}
pub fn from_proto(
replica_id: ReplicaId,
capability: Capability,
message: proto::BufferState,
file: Option<Arc<dyn File>>,
) -> Result<Self> {
@ -426,6 +441,7 @@ impl Buffer {
buffer,
message.diff_base.map(|text| text.into_boxed_str().into()),
file,
capability,
);
this.text.set_line_ending(proto::deserialize_line_ending(
rpc::proto::LineEnding::from_i32(message.line_ending)
@ -504,10 +520,19 @@ impl Buffer {
self
}
pub fn capability(&self) -> Capability {
self.capability
}
pub fn read_only(&self) -> bool {
self.capability == Capability::ReadOnly
}
pub fn build(
buffer: TextBuffer,
diff_base: Option<String>,
file: Option<Arc<dyn File>>,
capability: Capability,
) -> Self {
let saved_mtime = if let Some(file) = file.as_ref() {
file.mtime()
@ -526,6 +551,7 @@ impl Buffer {
diff_base,
git_diff: git::diff::BufferDiff::new(),
file,
capability,
syntax_map: Mutex::new(SyntaxMap::new()),
parsing_in_background: false,
parse_count: 0,

View File

@ -1926,7 +1926,7 @@ fn test_serialization(cx: &mut gpui::AppContext) {
.background_executor()
.block(buffer1.read(cx).serialize_ops(None, cx));
let buffer2 = cx.new_model(|cx| {
let mut buffer = Buffer::from_proto(1, state, None).unwrap();
let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
buffer
.apply_ops(
ops.into_iter()
@ -1967,7 +1967,8 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
let ops = cx
.background_executor()
.block(base_buffer.read(cx).serialize_ops(None, cx));
let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap();
let mut buffer =
Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
buffer
.apply_ops(
ops.into_iter()
@ -2083,8 +2084,13 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
replica_id
);
new_buffer = Some(cx.new_model(|cx| {
let mut new_buffer =
Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap();
let mut new_buffer = Buffer::from_proto(
new_replica_id,
Capability::ReadWrite,
old_buffer_state,
None,
)
.unwrap();
new_buffer
.apply_ops(
old_buffer_ops

View File

@ -772,9 +772,10 @@ impl Render for LspLogToolbarItemView {
}),
);
if server_selected && row.logs_selected {
let selected_ix = menu.select_last();
debug_assert_eq!(
Some(ix * 3 + 1),
menu.select_last(),
selected_ix,
"Could not scroll to a just added LSP menu item"
);
}
@ -822,9 +823,10 @@ impl Render for LspLogToolbarItemView {
}),
);
if server_selected && row.rpc_trace_selected {
let selected_ix = menu.select_last();
debug_assert_eq!(
Some(ix * 3 + 2),
menu.select_last(),
selected_ix,
"Could not scroll to a just added LSP menu item"
);
}

View File

@ -62,6 +62,7 @@ impl<'a> VideoGrant<'a> {
Self {
room: Some(Cow::Borrowed(room)),
room_join: Some(true),
can_publish: Some(false),
can_subscribe: Some(true),
..Default::default()
}

View File

@ -11,7 +11,7 @@ pub use language::Completion;
use language::{
char_kind,
language_settings::{language_settings, LanguageSettings},
AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
AutoindentMode, Buffer, BufferChunks, BufferSnapshot, Capability, CharKind, Chunk, CursorShape,
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
@ -55,6 +55,7 @@ pub struct MultiBuffer {
replica_id: ReplicaId,
history: History,
title: Option<String>,
capability: Capability,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -225,13 +226,14 @@ struct ExcerptBytes<'a> {
}
impl MultiBuffer {
pub fn new(replica_id: ReplicaId) -> Self {
pub fn new(replica_id: ReplicaId, capability: Capability) -> Self {
Self {
snapshot: Default::default(),
buffers: Default::default(),
next_excerpt_id: 1,
subscriptions: Default::default(),
singleton: false,
capability,
replica_id,
history: History {
next_transaction_id: Default::default(),
@ -271,6 +273,7 @@ impl MultiBuffer {
next_excerpt_id: 1,
subscriptions: Default::default(),
singleton: self.singleton,
capability: self.capability,
replica_id: self.replica_id,
history: self.history.clone(),
title: self.title.clone(),
@ -282,8 +285,12 @@ impl MultiBuffer {
self
}
pub fn read_only(&self) -> bool {
self.capability == Capability::ReadOnly
}
pub fn singleton(buffer: Model<Buffer>, cx: &mut ModelContext<Self>) -> Self {
let mut this = Self::new(buffer.read(cx).replica_id());
let mut this = Self::new(buffer.read(cx).replica_id(), buffer.read(cx).capability());
this.singleton = true;
this.push_excerpts(
buffer,
@ -1657,7 +1664,7 @@ impl MultiBuffer {
excerpts: [(&str, Vec<Range<Point>>); COUNT],
cx: &mut gpui::AppContext,
) -> Model<Self> {
let multi = cx.new_model(|_| Self::new(0));
let multi = cx.new_model(|_| Self::new(0, Capability::ReadWrite));
for (text, ranges) in excerpts {
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange {
@ -1678,7 +1685,7 @@ impl MultiBuffer {
pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> Model<Self> {
cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
let mutation_count = rng.gen_range(1..=5);
multibuffer.randomly_edit_excerpts(rng, mutation_count, cx);
multibuffer
@ -4176,7 +4183,7 @@ mod tests {
let ops = cx
.background_executor()
.block(host_buffer.read(cx).serialize_ops(None, cx));
let mut buffer = Buffer::from_proto(1, state, None).unwrap();
let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
buffer
.apply_ops(
ops.into_iter()
@ -4205,7 +4212,7 @@ mod tests {
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a')));
let buffer_2 =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'g')));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let events = Arc::new(RwLock::new(Vec::<Event>::new()));
multibuffer.update(cx, |_, cx| {
@ -4442,8 +4449,8 @@ mod tests {
let buffer_2 =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'm')));
let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let leader_multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let follower_multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let follower_edit_event_count = Arc::new(RwLock::new(0));
follower_multibuffer.update(cx, |_, cx| {
@ -4547,7 +4554,7 @@ mod tests {
fn test_push_excerpts_with_context_lines(cx: &mut AppContext) {
let buffer =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
multibuffer.push_excerpts_with_context_lines(
buffer.clone(),
@ -4584,7 +4591,7 @@ mod tests {
async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) {
let buffer =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a')));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| {
let snapshot = buffer.read(cx);
let ranges = vec![
@ -4619,7 +4626,7 @@ mod tests {
#[gpui::test]
fn test_empty_multibuffer(cx: &mut AppContext) {
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let snapshot = multibuffer.read(cx).snapshot(cx);
assert_eq!(snapshot.text(), "");
@ -4652,7 +4659,7 @@ mod tests {
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "efghi"));
let multibuffer = cx.new_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
multibuffer.push_excerpts(
buffer_1.clone(),
[ExcerptRange {
@ -4710,7 +4717,7 @@ mod tests {
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd"));
let buffer_2 =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "ABCDEFGHIJKLMNOP"));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
// Create an insertion id in buffer 1 that doesn't exist in buffer 2.
// Add an excerpt from buffer 1 that spans this new insertion.
@ -4844,7 +4851,7 @@ mod tests {
.unwrap_or(10);
let mut buffers: Vec<Model<Buffer>> = Vec::new();
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let mut excerpt_ids = Vec::<ExcerptId>::new();
let mut expected_excerpts = Vec::<(Model<Buffer>, Range<text::Anchor>)>::new();
let mut anchors = Vec::new();
@ -5266,7 +5273,7 @@ mod tests {
let buffer_1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "1234"));
let buffer_2 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "5678"));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0));
let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
let group_interval = multibuffer.read(cx).history.group_interval;
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.push_excerpts(

View File

@ -39,11 +39,11 @@ use language::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
serialize_anchor, serialize_version, split_operations,
},
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction,
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate,
OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
ToOffset, ToPointUtf16, Transaction, Unclipped,
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16,
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
};
use log::error;
use lsp::{
@ -262,6 +262,7 @@ enum ProjectClientState {
},
Remote {
sharing_has_stopped: bool,
capability: Capability,
remote_id: u64,
replica_id: ReplicaId,
},
@ -702,6 +703,7 @@ impl Project {
user_store: Model<UserStore>,
languages: Arc<LanguageRegistry>,
fs: Arc<dyn Fs>,
role: proto::ChannelRole,
mut cx: AsyncAppContext,
) -> Result<Model<Self>> {
client.authenticate_and_connect(true, &cx).await?;
@ -756,6 +758,7 @@ impl Project {
client: client.clone(),
client_state: Some(ProjectClientState::Remote {
sharing_has_stopped: false,
capability: Capability::ReadWrite,
remote_id,
replica_id,
}),
@ -796,6 +799,7 @@ impl Project {
prettiers_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(),
};
this.set_role(role);
for worktree in worktrees {
let _ = this.add_worktree(&worktree, cx);
}
@ -1618,6 +1622,17 @@ impl Project {
cx.notify();
}
pub fn set_role(&mut self, role: proto::ChannelRole) {
if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state {
*capability = if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin
{
Capability::ReadWrite
} else {
Capability::ReadOnly
};
}
}
fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) {
if let Some(ProjectClientState::Remote {
sharing_has_stopped,
@ -1659,7 +1674,7 @@ impl Project {
cx.emit(Event::Closed);
}
pub fn is_read_only(&self) -> bool {
pub fn is_disconnected(&self) -> bool {
match &self.client_state {
Some(ProjectClientState::Remote {
sharing_has_stopped,
@ -1669,6 +1684,17 @@ impl Project {
}
}
pub fn capability(&self) -> Capability {
match &self.client_state {
Some(ProjectClientState::Remote { capability, .. }) => *capability,
Some(ProjectClientState::Local { .. }) | None => Capability::ReadWrite,
}
}
pub fn is_read_only(&self) -> bool {
self.is_disconnected() || self.capability() == Capability::ReadOnly
}
pub fn is_local(&self) -> bool {
match &self.client_state {
Some(ProjectClientState::Remote { .. }) => false,
@ -6013,7 +6039,7 @@ impl Project {
this.upgrade().context("project dropped")?;
let response = rpc.request(message).await?;
let this = this.upgrade().context("project dropped")?;
if this.update(&mut cx, |this, _| this.is_read_only())? {
if this.update(&mut cx, |this, _| this.is_disconnected())? {
Err(anyhow!("disconnected before completing request"))
} else {
request
@ -7192,7 +7218,8 @@ impl Project {
let buffer_id = state.id;
let buffer = cx.new_model(|_| {
Buffer::from_proto(this.replica_id(), state, buffer_file).unwrap()
Buffer::from_proto(this.replica_id(), this.capability(), state, buffer_file)
.unwrap()
});
this.incomplete_remote_buffers
.insert(buffer_id, Some(buffer));
@ -7940,7 +7967,7 @@ impl Project {
if let Some(buffer) = buffer {
break buffer;
} else if this.update(&mut cx, |this, _| this.is_read_only())? {
} else if this.update(&mut cx, |this, _| this.is_disconnected())? {
return Err(anyhow!("disconnected before buffer {} could be opened", id));
}

View File

@ -32,7 +32,8 @@ use language::{
deserialize_fingerprint, deserialize_version, serialize_fingerprint, serialize_line_ending,
serialize_version,
},
Buffer, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, RopeFingerprint, Unclipped,
Buffer, Capability, DiagnosticEntry, File as _, LineEnding, PointUtf16, Rope, RopeFingerprint,
Unclipped,
};
use lsp::LanguageServerId;
use parking_lot::Mutex;
@ -682,7 +683,14 @@ impl LocalWorktree {
.background_executor()
.spawn(async move { text::Buffer::new(0, id, contents) })
.await;
cx.new_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
cx.new_model(|_| {
Buffer::build(
text_buffer,
diff_base,
Some(Arc::new(file)),
Capability::ReadWrite,
)
})
})
}

View File

@ -388,8 +388,18 @@ impl ProjectPanel {
let is_dir = entry.is_dir();
let worktree_id = worktree.id();
let is_local = project.is_local();
let is_read_only = project.is_read_only();
let context_menu = ContextMenu::build(cx, |mut menu, cx| {
if is_read_only {
menu = menu.action("Copy Relative Path", Box::new(CopyRelativePath));
if is_dir {
menu = menu.action("Search Inside", Box::new(NewSearchInDirectory))
}
return menu;
}
if is_local {
menu = menu.action(
"Add Folder to Project",
@ -1473,6 +1483,7 @@ impl ProjectPanel {
impl Render for ProjectPanel {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
let has_worktree = self.visible_entries.len() != 0;
let project = self.project.read(cx);
if has_worktree {
div()
@ -1485,21 +1496,25 @@ impl Render for ProjectPanel {
.on_action(cx.listener(Self::expand_selected_entry))
.on_action(cx.listener(Self::collapse_selected_entry))
.on_action(cx.listener(Self::collapse_all_entries))
.on_action(cx.listener(Self::new_file))
.on_action(cx.listener(Self::open_file))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::copy_path))
.on_action(cx.listener(Self::copy_relative_path))
.on_action(cx.listener(Self::new_search_in_directory))
.when(!project.is_read_only(), |el| {
el.on_action(cx.listener(Self::new_file))
.on_action(cx.listener(Self::new_directory))
.on_action(cx.listener(Self::rename))
.on_action(cx.listener(Self::delete))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::open_file))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::cut))
.on_action(cx.listener(Self::copy))
.on_action(cx.listener(Self::copy_path))
.on_action(cx.listener(Self::copy_relative_path))
.on_action(cx.listener(Self::paste))
.on_action(cx.listener(Self::reveal_in_finder))
})
.when(project.is_local(), |el| {
el.on_action(cx.listener(Self::reveal_in_finder))
.on_action(cx.listener(Self::open_in_terminal))
.on_action(cx.listener(Self::new_search_in_directory))
})
.track_focus(&self.focus_handle)
.child(
uniform_list(

View File

@ -395,6 +395,7 @@ mod tests {
language::init(cx);
Project::init_settings(cx);
workspace::init_settings(cx);
editor::init(cx);
});
}

View File

@ -269,6 +269,7 @@ message Participant {
repeated ParticipantProject projects = 3;
ParticipantLocation location = 4;
uint32 participant_index = 5;
ChannelRole role = 6;
}
message PendingParticipant {

View File

@ -70,7 +70,7 @@ impl BufferSearchBar {
fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if editor.read(cx).read_only() {
color: if editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text
@ -223,6 +223,7 @@ impl Render for BufferSearchBar {
.gap_2()
.border_1()
.border_color(editor_border)
.min_w(rems(384. / 16.))
.rounded_lg()
.child(IconElement::new(Icon::MagnifyingGlass))
.child(self.render_text_input(&self.query_editor, cx))

View File

@ -38,8 +38,8 @@ use std::{
use theme::ThemeSettings;
use ui::{
h_stack, prelude::*, v_stack, Button, Icon, IconButton, IconElement, Label, LabelCommon,
LabelSize, Selectable, Tooltip,
h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize,
Selectable, ToggleButton, Tooltip,
};
use util::{paths::PathMatcher, ResultExt as _};
use workspace::{
@ -61,12 +61,12 @@ struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>);
struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>);
pub fn init(cx: &mut AppContext) {
// todo!() po
cx.set_global(ActiveSearches::default());
cx.set_global(ActiveSettings::default());
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace
.register_action(ProjectSearchView::deploy)
.register_action(ProjectSearchView::new_search)
.register_action(ProjectSearchView::deploy_search)
.register_action(ProjectSearchBar::search_in_new);
})
.detach();
@ -132,9 +132,11 @@ pub struct ProjectSearchBar {
impl ProjectSearch {
fn new(project: Model<Project>, cx: &mut ModelContext<Self>) -> Self {
let replica_id = project.read(cx).replica_id();
let capability = project.read(cx).capability();
Self {
project,
excerpts: cx.new_model(|_| MultiBuffer::new(replica_id)),
excerpts: cx.new_model(|_| MultiBuffer::new(replica_id, capability)),
pending_search: Default::default(),
match_ranges: Default::default(),
active_query: None,
@ -286,7 +288,6 @@ impl Render for ProjectSearchView {
.size_full()
.track_focus(&self.focus_handle)
.child(self.results_editor.clone())
.into_any()
} else {
let model = self.model.read(cx);
let has_no_results = model.no_results.unwrap_or(false);
@ -363,6 +364,7 @@ impl Render for ProjectSearchView {
.flex_1()
.size_full()
.justify_center()
.bg(cx.theme().colors().editor_background)
.track_focus(&self.focus_handle)
.child(
h_stack()
@ -372,7 +374,6 @@ impl Render for ProjectSearchView {
.child(v_stack().child(major_text).children(minor_text))
.child(h_stack().flex_1()),
)
.into_any()
}
}
}
@ -941,11 +942,41 @@ impl ProjectSearchView {
});
}
// Re-activate the most recently activated search or the most recent if it has been closed.
// If no search exists in the workspace, create a new one.
fn deploy_search(
workspace: &mut Workspace,
_: &workspace::DeploySearch,
cx: &mut ViewContext<Workspace>,
) {
let active_search = cx
.global::<ActiveSearches>()
.0
.get(&workspace.project().downgrade());
let existing = active_search
.and_then(|active_search| {
workspace
.items_of_type::<ProjectSearchView>(cx)
.filter(|search| &search.downgrade() == active_search)
.last()
})
.or_else(|| workspace.item_of_type::<ProjectSearchView>(cx));
Self::existing_or_new_search(workspace, existing, cx)
}
// Add another search tab to the workspace.
fn deploy(
fn new_search(
workspace: &mut Workspace,
_: &workspace::NewSearch,
cx: &mut ViewContext<Workspace>,
) {
Self::existing_or_new_search(workspace, None, cx)
}
fn existing_or_new_search(
workspace: &mut Workspace,
existing: Option<View<ProjectSearchView>>,
cx: &mut ViewContext<Workspace>,
) {
// Clean up entries for dropped projects
cx.update_global(|state: &mut ActiveSearches, _cx| {
@ -962,6 +993,10 @@ impl ProjectSearchView {
}
});
let search = if let Some(existing) = existing {
workspace.activate_item(&existing, cx);
existing
} else {
let settings = cx
.global::<ActiveSettings>()
.0
@ -974,7 +1009,11 @@ impl ProjectSearchView {
};
let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings));
let view = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings));
workspace.add_item(Box::new(view.clone()), cx);
view
};
workspace.add_item(Box::new(search.clone()), cx);
@ -1519,7 +1558,7 @@ impl ProjectSearchBar {
fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if editor.read(cx).read_only() {
color: if editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text
@ -1639,20 +1678,26 @@ impl Render for ProjectSearchBar {
let mode_column = v_stack().items_start().justify_start().child(
h_stack()
.gap_2()
.child(
h_stack()
.child(
Button::new("project-search-text-button", "Text")
ToggleButton::new("project-search-text-button", "Text")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(search.current_mode == SearchMode::Text)
.on_click(cx.listener(|this, _, cx| {
this.activate_search_mode(SearchMode::Text, cx)
}))
.tooltip(|cx| {
Tooltip::for_action("Toggle text search", &ActivateTextMode, cx)
}),
})
.first(),
)
.child(
Button::new("project-search-regex-button", "Regex")
ToggleButton::new("project-search-regex-button", "Regex")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(search.current_mode == SearchMode::Regex)
.on_click(cx.listener(|this, _, cx| {
this.activate_search_mode(SearchMode::Regex, cx)
@ -1663,11 +1708,20 @@ impl Render for ProjectSearchBar {
&ActivateRegexMode,
cx,
)
})
.map(|this| {
if semantic_is_available {
this.middle()
} else {
this.last()
}
}),
)
.when(semantic_is_available, |this| {
this.child(
Button::new("project-search-semantic-button", "Semantic")
ToggleButton::new("project-search-semantic-button", "Semantic")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(search.current_mode == SearchMode::Semantic)
.on_click(cx.listener(|this, _, cx| {
this.activate_search_mode(SearchMode::Semantic, cx)
@ -1678,7 +1732,8 @@ impl Render for ProjectSearchBar {
&ActivateSemanticMode,
cx,
)
}),
})
.last(),
)
}),
)
@ -1829,6 +1884,7 @@ impl Render for ProjectSearchBar {
.child(
h_stack()
.justify_between()
.gap_2()
.child(query_column)
.child(mode_column)
.child(replace_column)
@ -2060,7 +2116,7 @@ pub mod tests {
}
#[gpui::test]
async fn test_project_search_focus(cx: &mut TestAppContext) {
async fn test_deploy_project_search_focus(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
@ -2101,7 +2157,237 @@ pub mod tests {
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
});
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx)
})
.unwrap();
let Some(search_view) = cx.read(|cx| {
workspace
.read(cx)
.unwrap()
.active_pane()
.read(cx)
.active_item()
.and_then(|item| item.downcast::<ProjectSearchView>())
}) else {
panic!("Search view expected to appear after new search event trigger")
};
cx.spawn(|mut cx| async move {
window
.update(&mut cx, |_, cx| {
cx.dispatch_action(ToggleFocus.boxed_clone())
})
.unwrap();
})
.detach();
cx.background_executor.run_until_parked();
window
.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
assert!(
search_view.query_editor.focus_handle(cx).is_focused(cx),
"Empty search view should be focused after the toggle focus event: no results panel to focus on",
);
});
}).unwrap();
window
.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
let query_editor = &search_view.query_editor;
assert!(
query_editor.focus_handle(cx).is_focused(cx),
"Search view should be focused after the new search view is activated",
);
let query_text = query_editor.read(cx).text(cx);
assert!(
query_text.is_empty(),
"New search query should be empty but got '{query_text}'",
);
let results_text = search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx));
assert!(
results_text.is_empty(),
"Empty search view should have no results but got '{results_text}'"
);
});
})
.unwrap();
window
.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
search_view.query_editor.update(cx, |query_editor, cx| {
query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", cx)
});
search_view.search(cx);
});
})
.unwrap();
cx.background_executor.run_until_parked();
window
.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
let results_text = search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx));
assert!(
results_text.is_empty(),
"Search view for mismatching query should have no results but got '{results_text}'"
);
assert!(
search_view.query_editor.focus_handle(cx).is_focused(cx),
"Search view should be focused after mismatching query had been used in search",
);
});
}).unwrap();
cx.spawn(|mut cx| async move {
window.update(&mut cx, |_, cx| {
cx.dispatch_action(ToggleFocus.boxed_clone())
})
})
.detach();
cx.background_executor.run_until_parked();
window.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
assert!(
search_view.query_editor.focus_handle(cx).is_focused(cx),
"Search view with mismatching query should be focused after the toggle focus event: still no results panel to focus on",
);
});
}).unwrap();
window
.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
search_view
.query_editor
.update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
search_view.search(cx);
});
})
.unwrap();
cx.background_executor.run_until_parked();
window.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
assert_eq!(
search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx)),
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
"Search view results should match the query"
);
assert!(
search_view.results_editor.focus_handle(cx).is_focused(cx),
"Search view with mismatching query should be focused after search results are available",
);
});
}).unwrap();
cx.spawn(|mut cx| async move {
window
.update(&mut cx, |_, cx| {
cx.dispatch_action(ToggleFocus.boxed_clone())
})
.unwrap();
})
.detach();
cx.background_executor.run_until_parked();
window.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
assert!(
search_view.results_editor.focus_handle(cx).is_focused(cx),
"Search view with matching query should still have its results editor focused after the toggle focus event",
);
});
}).unwrap();
workspace
.update(cx, |workspace, cx| {
ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx)
})
.unwrap();
window.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row");
assert_eq!(
search_view
.results_editor
.update(cx, |editor, cx| editor.display_text(cx)),
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;",
"Results should be unchanged after search view 2nd open in a row"
);
assert!(
search_view.query_editor.focus_handle(cx).is_focused(cx),
"Focus should be moved into query editor again after search view 2nd open in a row"
);
});
}).unwrap();
cx.spawn(|mut cx| async move {
window
.update(&mut cx, |_, cx| {
cx.dispatch_action(ToggleFocus.boxed_clone())
})
.unwrap();
})
.detach();
cx.background_executor.run_until_parked();
window.update(cx, |_, cx| {
search_view.update(cx, |search_view, cx| {
assert!(
search_view.results_editor.focus_handle(cx).is_focused(cx),
"Search view with matching query should switch focus to the results editor after the toggle focus event",
);
});
}).unwrap();
}
#[gpui::test]
async fn test_new_project_search_focus(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
"/dir",
json!({
"one.rs": "const ONE: usize = 1;",
"two.rs": "const TWO: usize = one::ONE + one::ONE;",
"three.rs": "const THREE: usize = one::ONE + two::TWO;",
"four.rs": "const FOUR: usize = one::ONE + three::THREE;",
}),
)
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.clone();
let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
let active_item = cx.read(|cx| {
workspace
.read(cx)
.unwrap()
.active_pane()
.read(cx)
.active_item()
.and_then(|item| item.downcast::<ProjectSearchView>())
});
assert!(
active_item.is_none(),
"Expected no search panel to be active"
);
window
.update(cx, move |workspace, cx| {
assert_eq!(workspace.panes().len(), 1);
workspace.panes()[0].update(cx, move |pane, cx| {
pane.toolbar()
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
});
ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx)
})
.unwrap();
@ -2250,7 +2536,7 @@ pub mod tests {
workspace
.update(cx, |workspace, cx| {
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx)
})
.unwrap();
cx.background_executor.run_until_parked();
@ -2536,7 +2822,7 @@ pub mod tests {
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
});
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx)
}
})
.unwrap();

View File

@ -14,7 +14,6 @@ test-support = ["gpui/test-support", "fs/test-support"]
[dependencies]
collections = { path = "../collections" }
gpui = { path = "../gpui" }
sqlez = { path = "../sqlez" }
fs = { path = "../fs" }
feature_flags = { path = "../feature_flags" }
util = { path = "../util" }

View File

@ -53,7 +53,7 @@ pub struct TerminalPanel {
impl TerminalPanel {
fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
let terminal_panel = cx.view().clone();
let terminal_panel = cx.view().downgrade();
let pane = cx.new_view(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
@ -77,14 +77,17 @@ impl TerminalPanel {
pane.set_can_navigate(false, cx);
pane.display_nav_history_buttons(false);
pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
let terminal_panel = terminal_panel.clone();
h_stack()
.gap_2()
.child(
IconButton::new("plus", Icon::Plus)
.icon_size(IconSize::Small)
.on_click(cx.listener_for(&terminal_panel, |terminal_panel, _, cx| {
terminal_panel.add_terminal(None, cx);
}))
.on_click(move |_, cx| {
terminal_panel
.update(cx, |panel, cx| panel.add_terminal(None, cx))
.log_err();
})
.tooltip(|cx| Tooltip::text("New Terminal", cx)),
)
.child({

View File

@ -131,6 +131,15 @@ impl PlayerColors {
*self.0.last().unwrap()
}
pub fn read_only(&self) -> PlayerColor {
let local = self.local();
PlayerColor {
cursor: local.cursor.grayscale(),
background: local.background.grayscale(),
selection: local.selection.grayscale(),
}
}
pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
let len = self.0.len() - 1;
self.0[(participant_index as usize % len) + 1]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,457 +15,6 @@ pub fn one() -> UserThemeFamily {
name: "One".into(),
author: "Zed Industries".into(),
themes: vec![
UserTheme {
name: "One Light".into(),
appearance: Appearance::Light,
styles: UserThemeStylesRefinement {
colors: ThemeColorsRefinement {
border: Some(rgba(0xc9c9caff).into()),
border_variant: Some(rgba(0xdfdfe0ff).into()),
border_focused: Some(rgba(0xcbcdf6ff).into()),
border_selected: Some(rgba(0xcbcdf6ff).into()),
border_transparent: Some(rgba(0x00000000).into()),
border_disabled: Some(rgba(0xd3d3d4ff).into()),
elevated_surface_background: Some(rgba(0xebebecff).into()),
surface_background: Some(rgba(0xebebecff).into()),
background: Some(rgba(0xdcdcddff).into()),
panel_background: Some(rgba(0xebebecff).into()),
element_background: Some(rgba(0xebebecff).into()),
element_hover: Some(rgba(0xdfdfe0ff).into()),
element_active: Some(rgba(0xcacacaff).into()),
element_selected: Some(rgba(0xcacacaff).into()),
element_disabled: Some(rgba(0xebebecff).into()),
drop_target_background: Some(rgba(0x7f818880).into()),
ghost_element_background: Some(rgba(0x00000000).into()),
ghost_element_hover: Some(rgba(0xdfdfe0ff).into()),
ghost_element_active: Some(rgba(0xcacacaff).into()),
ghost_element_selected: Some(rgba(0xcacacaff).into()),
ghost_element_disabled: Some(rgba(0xebebecff).into()),
text: Some(rgba(0x383a41ff).into()),
text_muted: Some(rgba(0x7f8188ff).into()),
text_placeholder: Some(rgba(0xa1a1a3ff).into()),
text_disabled: Some(rgba(0xa1a1a3ff).into()),
text_accent: Some(rgba(0x5c79e2ff).into()),
icon: Some(rgba(0x383a41ff).into()),
icon_muted: Some(rgba(0x7f8188ff).into()),
icon_disabled: Some(rgba(0xa1a1a3ff).into()),
icon_placeholder: Some(rgba(0x7f8188ff).into()),
icon_accent: Some(rgba(0x5c79e2ff).into()),
status_bar_background: Some(rgba(0xdcdcddff).into()),
title_bar_background: Some(rgba(0xdcdcddff).into()),
toolbar_background: Some(rgba(0xfafafaff).into()),
tab_bar_background: Some(rgba(0xebebecff).into()),
tab_inactive_background: Some(rgba(0xebebecff).into()),
tab_active_background: Some(rgba(0xfafafaff).into()),
scrollbar_thumb_background: Some(rgba(0x383a414c).into()),
scrollbar_thumb_hover_background: Some(rgba(0xdfdfe0ff).into()),
scrollbar_thumb_border: Some(rgba(0xdfdfe0ff).into()),
scrollbar_track_background: Some(rgba(0x00000000).into()),
scrollbar_track_border: Some(rgba(0xeeeeeeff).into()),
editor_foreground: Some(rgba(0x383a41ff).into()),
editor_background: Some(rgba(0xfafafaff).into()),
editor_gutter_background: Some(rgba(0xfafafaff).into()),
editor_subheader_background: Some(rgba(0xebebecff).into()),
editor_active_line_background: Some(rgba(0xebebecbf).into()),
editor_highlighted_line_background: Some(rgba(0xebebecff).into()),
editor_line_number: Some(rgba(0x383a4159).into()),
editor_active_line_number: Some(rgba(0x383a41ff).into()),
editor_invisible: Some(rgba(0x7f8188ff).into()),
editor_wrap_guide: Some(rgba(0x383a410d).into()),
editor_active_wrap_guide: Some(rgba(0x383a411a).into()),
editor_document_highlight_read_background: Some(rgba(0x5c79e21a).into()),
editor_document_highlight_write_background: Some(rgba(0xa3a3a466).into()),
terminal_background: Some(rgba(0xfafafaff).into()),
terminal_ansi_bright_black: Some(rgba(0xaaaaaaff).into()),
terminal_ansi_bright_red: Some(rgba(0xf0b0a4ff).into()),
terminal_ansi_bright_green: Some(rgba(0xb2cfa9ff).into()),
terminal_ansi_bright_yellow: Some(rgba(0xf1dfc1ff).into()),
terminal_ansi_bright_blue: Some(rgba(0xb5baf2ff).into()),
terminal_ansi_bright_magenta: Some(rgba(0xcea6d3ff).into()),
terminal_ansi_bright_cyan: Some(rgba(0xa4bfdbff).into()),
terminal_ansi_bright_white: Some(rgba(0x383a41ff).into()),
terminal_ansi_black: Some(rgba(0xfafafaff).into()),
terminal_ansi_red: Some(rgba(0xd36151ff).into()),
terminal_ansi_green: Some(rgba(0x669f59ff).into()),
terminal_ansi_yellow: Some(rgba(0xdec184ff).into()),
terminal_ansi_blue: Some(rgba(0x5c79e2ff).into()),
terminal_ansi_magenta: Some(rgba(0x994fa6ff).into()),
terminal_ansi_cyan: Some(rgba(0x3b82b7ff).into()),
terminal_ansi_white: Some(rgba(0x383a41ff).into()),
link_text_hover: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
status: StatusColorsRefinement {
conflict: Some(rgba(0xdec184ff).into()),
conflict_background: Some(rgba(0xfaf2e6ff).into()),
conflict_border: Some(rgba(0xf5e8d2ff).into()),
created: Some(rgba(0x669f59ff).into()),
created_background: Some(rgba(0xe0ebdcff).into()),
created_border: Some(rgba(0xc8dcc1ff).into()),
deleted: Some(rgba(0xd36151ff).into()),
deleted_background: Some(rgba(0xfbdfd9ff).into()),
deleted_border: Some(rgba(0xf6c6bdff).into()),
error: Some(rgba(0xd36151ff).into()),
error_background: Some(rgba(0xfbdfd9ff).into()),
error_border: Some(rgba(0xf6c6bdff).into()),
hidden: Some(rgba(0xa1a1a3ff).into()),
hidden_background: Some(rgba(0xdcdcddff).into()),
hidden_border: Some(rgba(0xd3d3d4ff).into()),
hint: Some(rgba(0x9295beff).into()),
hint_background: Some(rgba(0xe2e2faff).into()),
hint_border: Some(rgba(0xcbcdf6ff).into()),
ignored: Some(rgba(0x7f8188ff).into()),
ignored_background: Some(rgba(0xdcdcddff).into()),
ignored_border: Some(rgba(0xc9c9caff).into()),
info: Some(rgba(0x5c79e2ff).into()),
info_background: Some(rgba(0xe2e2faff).into()),
info_border: Some(rgba(0xcbcdf6ff).into()),
modified: Some(rgba(0xdec184ff).into()),
modified_background: Some(rgba(0xfaf2e6ff).into()),
modified_border: Some(rgba(0xf5e8d2ff).into()),
predictive: Some(rgba(0x9c9fc7ff).into()),
predictive_background: Some(rgba(0xe0ebdcff).into()),
predictive_border: Some(rgba(0xc8dcc1ff).into()),
renamed: Some(rgba(0x5c79e2ff).into()),
renamed_background: Some(rgba(0xe2e2faff).into()),
renamed_border: Some(rgba(0xcbcdf6ff).into()),
success: Some(rgba(0x669f59ff).into()),
success_background: Some(rgba(0xe0ebdcff).into()),
success_border: Some(rgba(0xc8dcc1ff).into()),
unreachable: Some(rgba(0x7f8188ff).into()),
unreachable_background: Some(rgba(0xdcdcddff).into()),
unreachable_border: Some(rgba(0xc9c9caff).into()),
warning: Some(rgba(0xdec184ff).into()),
warning_background: Some(rgba(0xfaf2e6ff).into()),
warning_border: Some(rgba(0xf5e8d2ff).into()),
..Default::default()
},
player: Some(PlayerColors(vec![
PlayerColor {
cursor: rgba(0x5c79e2ff).into(),
background: rgba(0x5c79e2ff).into(),
selection: rgba(0x5c79e23d).into(),
},
PlayerColor {
cursor: rgba(0x994fa6ff).into(),
background: rgba(0x994fa6ff).into(),
selection: rgba(0x994fa63d).into(),
},
PlayerColor {
cursor: rgba(0xad6f27ff).into(),
background: rgba(0xad6f27ff).into(),
selection: rgba(0xad6f273d).into(),
},
PlayerColor {
cursor: rgba(0xa44aabff).into(),
background: rgba(0xa44aabff).into(),
selection: rgba(0xa44aab3d).into(),
},
PlayerColor {
cursor: rgba(0x3b82b7ff).into(),
background: rgba(0x3b82b7ff).into(),
selection: rgba(0x3b82b73d).into(),
},
PlayerColor {
cursor: rgba(0xd36151ff).into(),
background: rgba(0xd36151ff).into(),
selection: rgba(0xd361513d).into(),
},
PlayerColor {
cursor: rgba(0xdec184ff).into(),
background: rgba(0xdec184ff).into(),
selection: rgba(0xdec1843d).into(),
},
PlayerColor {
cursor: rgba(0x669f59ff).into(),
background: rgba(0x669f59ff).into(),
selection: rgba(0x669f593d).into(),
},
])),
syntax: Some(UserSyntaxTheme {
highlights: vec![
(
"attribute".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"boolean".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f26ff).into()),
..Default::default()
},
),
(
"comment".into(),
UserHighlightStyle {
color: Some(rgba(0xa2a3a7ff).into()),
..Default::default()
},
),
(
"comment.doc".into(),
UserHighlightStyle {
color: Some(rgba(0x7c7e86ff).into()),
..Default::default()
},
),
(
"constant".into(),
UserHighlightStyle {
color: Some(rgba(0x669f59ff).into()),
..Default::default()
},
),
(
"constructor".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"embedded".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"emphasis".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"emphasis.strong".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f26ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"enum".into(),
UserHighlightStyle {
color: Some(rgba(0xd36050ff).into()),
..Default::default()
},
),
(
"function".into(),
UserHighlightStyle {
color: Some(rgba(0x5b79e3ff).into()),
..Default::default()
},
),
(
"hint".into(),
UserHighlightStyle {
color: Some(rgba(0x9295beff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"keyword".into(),
UserHighlightStyle {
color: Some(rgba(0xa449abff).into()),
..Default::default()
},
),
(
"label".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"link_text".into(),
UserHighlightStyle {
color: Some(rgba(0x5b79e3ff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"link_uri".into(),
UserHighlightStyle {
color: Some(rgba(0x3982b7ff).into()),
..Default::default()
},
),
(
"number".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f26ff).into()),
..Default::default()
},
),
(
"operator".into(),
UserHighlightStyle {
color: Some(rgba(0x3982b7ff).into()),
..Default::default()
},
),
(
"predictive".into(),
UserHighlightStyle {
color: Some(rgba(0x9c9fc7ff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"preproc".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"primary".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"property".into(),
UserHighlightStyle {
color: Some(rgba(0xd36050ff).into()),
..Default::default()
},
),
(
"punctuation".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"punctuation.bracket".into(),
UserHighlightStyle {
color: Some(rgba(0x4d4f52ff).into()),
..Default::default()
},
),
(
"punctuation.delimiter".into(),
UserHighlightStyle {
color: Some(rgba(0x4d4f52ff).into()),
..Default::default()
},
),
(
"punctuation.list_marker".into(),
UserHighlightStyle {
color: Some(rgba(0xd36050ff).into()),
..Default::default()
},
),
(
"punctuation.special".into(),
UserHighlightStyle {
color: Some(rgba(0xb92c46ff).into()),
..Default::default()
},
),
(
"string".into(),
UserHighlightStyle {
color: Some(rgba(0x659f58ff).into()),
..Default::default()
},
),
(
"string.escape".into(),
UserHighlightStyle {
color: Some(rgba(0x7c7e86ff).into()),
..Default::default()
},
),
(
"string.regex".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f27ff).into()),
..Default::default()
},
),
(
"string.special".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f27ff).into()),
..Default::default()
},
),
(
"string.special.symbol".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f27ff).into()),
..Default::default()
},
),
(
"tag".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"text.literal".into(),
UserHighlightStyle {
color: Some(rgba(0x659f58ff).into()),
..Default::default()
},
),
(
"title".into(),
UserHighlightStyle {
color: Some(rgba(0xd36050ff).into()),
font_weight: Some(UserFontWeight(400.0)),
..Default::default()
},
),
(
"type".into(),
UserHighlightStyle {
color: Some(rgba(0x3982b7ff).into()),
..Default::default()
},
),
(
"variable".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"variable.special".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f26ff).into()),
..Default::default()
},
),
(
"variant".into(),
UserHighlightStyle {
color: Some(rgba(0x5b79e3ff).into()),
..Default::default()
},
),
],
}),
},
},
UserTheme {
name: "One Dark".into(),
appearance: Appearance::Dark,
@ -917,6 +466,457 @@ pub fn one() -> UserThemeFamily {
}),
},
},
UserTheme {
name: "One Light".into(),
appearance: Appearance::Light,
styles: UserThemeStylesRefinement {
colors: ThemeColorsRefinement {
border: Some(rgba(0xc9c9caff).into()),
border_variant: Some(rgba(0xdfdfe0ff).into()),
border_focused: Some(rgba(0xcbcdf6ff).into()),
border_selected: Some(rgba(0xcbcdf6ff).into()),
border_transparent: Some(rgba(0x00000000).into()),
border_disabled: Some(rgba(0xd3d3d4ff).into()),
elevated_surface_background: Some(rgba(0xebebecff).into()),
surface_background: Some(rgba(0xebebecff).into()),
background: Some(rgba(0xdcdcddff).into()),
panel_background: Some(rgba(0xebebecff).into()),
element_background: Some(rgba(0xebebecff).into()),
element_hover: Some(rgba(0xdfdfe0ff).into()),
element_active: Some(rgba(0xcacacaff).into()),
element_selected: Some(rgba(0xcacacaff).into()),
element_disabled: Some(rgba(0xebebecff).into()),
drop_target_background: Some(rgba(0x7f818880).into()),
ghost_element_background: Some(rgba(0x00000000).into()),
ghost_element_hover: Some(rgba(0xdfdfe0ff).into()),
ghost_element_active: Some(rgba(0xcacacaff).into()),
ghost_element_selected: Some(rgba(0xcacacaff).into()),
ghost_element_disabled: Some(rgba(0xebebecff).into()),
text: Some(rgba(0x383a41ff).into()),
text_muted: Some(rgba(0x7f8188ff).into()),
text_placeholder: Some(rgba(0xa1a1a3ff).into()),
text_disabled: Some(rgba(0xa1a1a3ff).into()),
text_accent: Some(rgba(0x5c79e2ff).into()),
icon: Some(rgba(0x383a41ff).into()),
icon_muted: Some(rgba(0x7f8188ff).into()),
icon_disabled: Some(rgba(0xa1a1a3ff).into()),
icon_placeholder: Some(rgba(0x7f8188ff).into()),
icon_accent: Some(rgba(0x5c79e2ff).into()),
status_bar_background: Some(rgba(0xdcdcddff).into()),
title_bar_background: Some(rgba(0xdcdcddff).into()),
toolbar_background: Some(rgba(0xfafafaff).into()),
tab_bar_background: Some(rgba(0xebebecff).into()),
tab_inactive_background: Some(rgba(0xebebecff).into()),
tab_active_background: Some(rgba(0xfafafaff).into()),
scrollbar_thumb_background: Some(rgba(0x383a414c).into()),
scrollbar_thumb_hover_background: Some(rgba(0xdfdfe0ff).into()),
scrollbar_thumb_border: Some(rgba(0xdfdfe0ff).into()),
scrollbar_track_background: Some(rgba(0x00000000).into()),
scrollbar_track_border: Some(rgba(0xeeeeeeff).into()),
editor_foreground: Some(rgba(0x383a41ff).into()),
editor_background: Some(rgba(0xfafafaff).into()),
editor_gutter_background: Some(rgba(0xfafafaff).into()),
editor_subheader_background: Some(rgba(0xebebecff).into()),
editor_active_line_background: Some(rgba(0xebebecbf).into()),
editor_highlighted_line_background: Some(rgba(0xebebecff).into()),
editor_line_number: Some(rgba(0x383a4159).into()),
editor_active_line_number: Some(rgba(0x383a41ff).into()),
editor_invisible: Some(rgba(0x7f8188ff).into()),
editor_wrap_guide: Some(rgba(0x383a410d).into()),
editor_active_wrap_guide: Some(rgba(0x383a411a).into()),
editor_document_highlight_read_background: Some(rgba(0x5c79e21a).into()),
editor_document_highlight_write_background: Some(rgba(0xa3a3a466).into()),
terminal_background: Some(rgba(0xfafafaff).into()),
terminal_ansi_bright_black: Some(rgba(0xaaaaaaff).into()),
terminal_ansi_bright_red: Some(rgba(0xf0b0a4ff).into()),
terminal_ansi_bright_green: Some(rgba(0xb2cfa9ff).into()),
terminal_ansi_bright_yellow: Some(rgba(0xf1dfc1ff).into()),
terminal_ansi_bright_blue: Some(rgba(0xb5baf2ff).into()),
terminal_ansi_bright_magenta: Some(rgba(0xcea6d3ff).into()),
terminal_ansi_bright_cyan: Some(rgba(0xa4bfdbff).into()),
terminal_ansi_bright_white: Some(rgba(0x383a41ff).into()),
terminal_ansi_black: Some(rgba(0xfafafaff).into()),
terminal_ansi_red: Some(rgba(0xd36151ff).into()),
terminal_ansi_green: Some(rgba(0x669f59ff).into()),
terminal_ansi_yellow: Some(rgba(0xdec184ff).into()),
terminal_ansi_blue: Some(rgba(0x5c79e2ff).into()),
terminal_ansi_magenta: Some(rgba(0x994fa6ff).into()),
terminal_ansi_cyan: Some(rgba(0x3b82b7ff).into()),
terminal_ansi_white: Some(rgba(0x383a41ff).into()),
link_text_hover: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
status: StatusColorsRefinement {
conflict: Some(rgba(0xdec184ff).into()),
conflict_background: Some(rgba(0xfaf2e6ff).into()),
conflict_border: Some(rgba(0xf5e8d2ff).into()),
created: Some(rgba(0x669f59ff).into()),
created_background: Some(rgba(0xe0ebdcff).into()),
created_border: Some(rgba(0xc8dcc1ff).into()),
deleted: Some(rgba(0xd36151ff).into()),
deleted_background: Some(rgba(0xfbdfd9ff).into()),
deleted_border: Some(rgba(0xf6c6bdff).into()),
error: Some(rgba(0xd36151ff).into()),
error_background: Some(rgba(0xfbdfd9ff).into()),
error_border: Some(rgba(0xf6c6bdff).into()),
hidden: Some(rgba(0xa1a1a3ff).into()),
hidden_background: Some(rgba(0xdcdcddff).into()),
hidden_border: Some(rgba(0xd3d3d4ff).into()),
hint: Some(rgba(0x9295beff).into()),
hint_background: Some(rgba(0xe2e2faff).into()),
hint_border: Some(rgba(0xcbcdf6ff).into()),
ignored: Some(rgba(0x7f8188ff).into()),
ignored_background: Some(rgba(0xdcdcddff).into()),
ignored_border: Some(rgba(0xc9c9caff).into()),
info: Some(rgba(0x5c79e2ff).into()),
info_background: Some(rgba(0xe2e2faff).into()),
info_border: Some(rgba(0xcbcdf6ff).into()),
modified: Some(rgba(0xdec184ff).into()),
modified_background: Some(rgba(0xfaf2e6ff).into()),
modified_border: Some(rgba(0xf5e8d2ff).into()),
predictive: Some(rgba(0x9c9fc7ff).into()),
predictive_background: Some(rgba(0xe0ebdcff).into()),
predictive_border: Some(rgba(0xc8dcc1ff).into()),
renamed: Some(rgba(0x5c79e2ff).into()),
renamed_background: Some(rgba(0xe2e2faff).into()),
renamed_border: Some(rgba(0xcbcdf6ff).into()),
success: Some(rgba(0x669f59ff).into()),
success_background: Some(rgba(0xe0ebdcff).into()),
success_border: Some(rgba(0xc8dcc1ff).into()),
unreachable: Some(rgba(0x7f8188ff).into()),
unreachable_background: Some(rgba(0xdcdcddff).into()),
unreachable_border: Some(rgba(0xc9c9caff).into()),
warning: Some(rgba(0xdec184ff).into()),
warning_background: Some(rgba(0xfaf2e6ff).into()),
warning_border: Some(rgba(0xf5e8d2ff).into()),
..Default::default()
},
player: Some(PlayerColors(vec![
PlayerColor {
cursor: rgba(0x5c79e2ff).into(),
background: rgba(0x5c79e2ff).into(),
selection: rgba(0x5c79e23d).into(),
},
PlayerColor {
cursor: rgba(0x994fa6ff).into(),
background: rgba(0x994fa6ff).into(),
selection: rgba(0x994fa63d).into(),
},
PlayerColor {
cursor: rgba(0xad6f27ff).into(),
background: rgba(0xad6f27ff).into(),
selection: rgba(0xad6f273d).into(),
},
PlayerColor {
cursor: rgba(0xa44aabff).into(),
background: rgba(0xa44aabff).into(),
selection: rgba(0xa44aab3d).into(),
},
PlayerColor {
cursor: rgba(0x3b82b7ff).into(),
background: rgba(0x3b82b7ff).into(),
selection: rgba(0x3b82b73d).into(),
},
PlayerColor {
cursor: rgba(0xd36151ff).into(),
background: rgba(0xd36151ff).into(),
selection: rgba(0xd361513d).into(),
},
PlayerColor {
cursor: rgba(0xdec184ff).into(),
background: rgba(0xdec184ff).into(),
selection: rgba(0xdec1843d).into(),
},
PlayerColor {
cursor: rgba(0x669f59ff).into(),
background: rgba(0x669f59ff).into(),
selection: rgba(0x669f593d).into(),
},
])),
syntax: Some(UserSyntaxTheme {
highlights: vec![
(
"attribute".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"boolean".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f26ff).into()),
..Default::default()
},
),
(
"comment".into(),
UserHighlightStyle {
color: Some(rgba(0xa2a3a7ff).into()),
..Default::default()
},
),
(
"comment.doc".into(),
UserHighlightStyle {
color: Some(rgba(0x7c7e86ff).into()),
..Default::default()
},
),
(
"constant".into(),
UserHighlightStyle {
color: Some(rgba(0x669f59ff).into()),
..Default::default()
},
),
(
"constructor".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"embedded".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"emphasis".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"emphasis.strong".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f26ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"enum".into(),
UserHighlightStyle {
color: Some(rgba(0xd36050ff).into()),
..Default::default()
},
),
(
"function".into(),
UserHighlightStyle {
color: Some(rgba(0x5b79e3ff).into()),
..Default::default()
},
),
(
"hint".into(),
UserHighlightStyle {
color: Some(rgba(0x9295beff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"keyword".into(),
UserHighlightStyle {
color: Some(rgba(0xa449abff).into()),
..Default::default()
},
),
(
"label".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"link_text".into(),
UserHighlightStyle {
color: Some(rgba(0x5b79e3ff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"link_uri".into(),
UserHighlightStyle {
color: Some(rgba(0x3982b7ff).into()),
..Default::default()
},
),
(
"number".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f26ff).into()),
..Default::default()
},
),
(
"operator".into(),
UserHighlightStyle {
color: Some(rgba(0x3982b7ff).into()),
..Default::default()
},
),
(
"predictive".into(),
UserHighlightStyle {
color: Some(rgba(0x9c9fc7ff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"preproc".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"primary".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"property".into(),
UserHighlightStyle {
color: Some(rgba(0xd36050ff).into()),
..Default::default()
},
),
(
"punctuation".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"punctuation.bracket".into(),
UserHighlightStyle {
color: Some(rgba(0x4d4f52ff).into()),
..Default::default()
},
),
(
"punctuation.delimiter".into(),
UserHighlightStyle {
color: Some(rgba(0x4d4f52ff).into()),
..Default::default()
},
),
(
"punctuation.list_marker".into(),
UserHighlightStyle {
color: Some(rgba(0xd36050ff).into()),
..Default::default()
},
),
(
"punctuation.special".into(),
UserHighlightStyle {
color: Some(rgba(0xb92c46ff).into()),
..Default::default()
},
),
(
"string".into(),
UserHighlightStyle {
color: Some(rgba(0x659f58ff).into()),
..Default::default()
},
),
(
"string.escape".into(),
UserHighlightStyle {
color: Some(rgba(0x7c7e86ff).into()),
..Default::default()
},
),
(
"string.regex".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f27ff).into()),
..Default::default()
},
),
(
"string.special".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f27ff).into()),
..Default::default()
},
),
(
"string.special.symbol".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f27ff).into()),
..Default::default()
},
),
(
"tag".into(),
UserHighlightStyle {
color: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
),
(
"text.literal".into(),
UserHighlightStyle {
color: Some(rgba(0x659f58ff).into()),
..Default::default()
},
),
(
"title".into(),
UserHighlightStyle {
color: Some(rgba(0xd36050ff).into()),
font_weight: Some(UserFontWeight(400.0)),
..Default::default()
},
),
(
"type".into(),
UserHighlightStyle {
color: Some(rgba(0x3982b7ff).into()),
..Default::default()
},
),
(
"variable".into(),
UserHighlightStyle {
color: Some(rgba(0x383a41ff).into()),
..Default::default()
},
),
(
"variable.special".into(),
UserHighlightStyle {
color: Some(rgba(0xad6f26ff).into()),
..Default::default()
},
),
(
"variant".into(),
UserHighlightStyle {
color: Some(rgba(0x5b79e3ff).into()),
..Default::default()
},
),
],
}),
},
},
],
}
}

View File

@ -15,6 +15,464 @@ pub fn rose_pine() -> UserThemeFamily {
name: "Rosé Pine".into(),
author: "Zed Industries".into(),
themes: vec![
UserTheme {
name: "Rosé Pine".into(),
appearance: Appearance::Dark,
styles: UserThemeStylesRefinement {
colors: ThemeColorsRefinement {
border: Some(rgba(0x423f55ff).into()),
border_variant: Some(rgba(0x232132ff).into()),
border_focused: Some(rgba(0x435255ff).into()),
border_selected: Some(rgba(0x435255ff).into()),
border_transparent: Some(rgba(0x00000000).into()),
border_disabled: Some(rgba(0x353347ff).into()),
elevated_surface_background: Some(rgba(0x1d1b2aff).into()),
surface_background: Some(rgba(0x1d1b2aff).into()),
background: Some(rgba(0x292739ff).into()),
panel_background: Some(rgba(0x1d1b2aff).into()),
element_background: Some(rgba(0x1d1b2aff).into()),
element_hover: Some(rgba(0x232132ff).into()),
element_active: Some(rgba(0x403e53ff).into()),
element_selected: Some(rgba(0x403e53ff).into()),
element_disabled: Some(rgba(0x1d1b2aff).into()),
drop_target_background: Some(rgba(0x75718e80).into()),
ghost_element_background: Some(rgba(0x00000000).into()),
ghost_element_hover: Some(rgba(0x232132ff).into()),
ghost_element_active: Some(rgba(0x403e53ff).into()),
ghost_element_selected: Some(rgba(0x403e53ff).into()),
ghost_element_disabled: Some(rgba(0x1d1b2aff).into()),
text: Some(rgba(0xe0def4ff).into()),
text_muted: Some(rgba(0x75718eff).into()),
text_placeholder: Some(rgba(0x2f2b43ff).into()),
text_disabled: Some(rgba(0x2f2b43ff).into()),
text_accent: Some(rgba(0x9cced7ff).into()),
icon: Some(rgba(0xe0def4ff).into()),
icon_muted: Some(rgba(0x75718eff).into()),
icon_disabled: Some(rgba(0x2f2b43ff).into()),
icon_placeholder: Some(rgba(0x75718eff).into()),
icon_accent: Some(rgba(0x9cced7ff).into()),
status_bar_background: Some(rgba(0x292739ff).into()),
title_bar_background: Some(rgba(0x292739ff).into()),
toolbar_background: Some(rgba(0x191724ff).into()),
tab_bar_background: Some(rgba(0x1d1b2aff).into()),
tab_inactive_background: Some(rgba(0x1d1b2aff).into()),
tab_active_background: Some(rgba(0x191724ff).into()),
scrollbar_thumb_background: Some(rgba(0xe0def44c).into()),
scrollbar_thumb_hover_background: Some(rgba(0x232132ff).into()),
scrollbar_thumb_border: Some(rgba(0x232132ff).into()),
scrollbar_track_background: Some(rgba(0x00000000).into()),
scrollbar_track_border: Some(rgba(0x1c1a29ff).into()),
editor_foreground: Some(rgba(0xe0def4ff).into()),
editor_background: Some(rgba(0x191724ff).into()),
editor_gutter_background: Some(rgba(0x191724ff).into()),
editor_subheader_background: Some(rgba(0x1d1b2aff).into()),
editor_active_line_background: Some(rgba(0x1d1b2abf).into()),
editor_highlighted_line_background: Some(rgba(0x1d1b2aff).into()),
editor_line_number: Some(rgba(0xe0def459).into()),
editor_active_line_number: Some(rgba(0xe0def4ff).into()),
editor_invisible: Some(rgba(0x75718eff).into()),
editor_wrap_guide: Some(rgba(0xe0def40d).into()),
editor_active_wrap_guide: Some(rgba(0xe0def41a).into()),
editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()),
editor_document_highlight_write_background: Some(rgba(0x28253c66).into()),
terminal_background: Some(rgba(0x191724ff).into()),
terminal_ansi_bright_black: Some(rgba(0x403d55ff).into()),
terminal_ansi_bright_red: Some(rgba(0x7e3647ff).into()),
terminal_ansi_bright_green: Some(rgba(0x31614fff).into()),
terminal_ansi_bright_yellow: Some(rgba(0x8a653bff).into()),
terminal_ansi_bright_blue: Some(rgba(0x566c70ff).into()),
terminal_ansi_bright_magenta: Some(rgba(0x4c3b47ff).into()),
terminal_ansi_bright_cyan: Some(rgba(0x203a46ff).into()),
terminal_ansi_bright_white: Some(rgba(0xe0def4ff).into()),
terminal_ansi_black: Some(rgba(0x191724ff).into()),
terminal_ansi_red: Some(rgba(0xea6f92ff).into()),
terminal_ansi_green: Some(rgba(0x5dc2a3ff).into()),
terminal_ansi_yellow: Some(rgba(0xf5c177ff).into()),
terminal_ansi_blue: Some(rgba(0x9cced7ff).into()),
terminal_ansi_magenta: Some(rgba(0x9d7691ff).into()),
terminal_ansi_cyan: Some(rgba(0x32748fff).into()),
terminal_ansi_white: Some(rgba(0xe0def4ff).into()),
link_text_hover: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
status: StatusColorsRefinement {
conflict: Some(rgba(0xf5c177ff).into()),
conflict_background: Some(rgba(0x50341aff).into()),
conflict_border: Some(rgba(0x6d4d2bff).into()),
created: Some(rgba(0x5dc2a3ff).into()),
created_background: Some(rgba(0x182e23ff).into()),
created_border: Some(rgba(0x254839ff).into()),
deleted: Some(rgba(0xea6f92ff).into()),
deleted_background: Some(rgba(0x431820ff).into()),
deleted_border: Some(rgba(0x612834ff).into()),
error: Some(rgba(0xea6f92ff).into()),
error_background: Some(rgba(0x431820ff).into()),
error_border: Some(rgba(0x612834ff).into()),
hidden: Some(rgba(0x2f2b43ff).into()),
hidden_background: Some(rgba(0x292739ff).into()),
hidden_border: Some(rgba(0x353347ff).into()),
hint: Some(rgba(0x5e768cff).into()),
hint_background: Some(rgba(0x2f3739ff).into()),
hint_border: Some(rgba(0x435255ff).into()),
ignored: Some(rgba(0x75718eff).into()),
ignored_background: Some(rgba(0x292739ff).into()),
ignored_border: Some(rgba(0x423f55ff).into()),
info: Some(rgba(0x9cced7ff).into()),
info_background: Some(rgba(0x2f3739ff).into()),
info_border: Some(rgba(0x435255ff).into()),
modified: Some(rgba(0xf5c177ff).into()),
modified_background: Some(rgba(0x50341aff).into()),
modified_border: Some(rgba(0x6d4d2bff).into()),
predictive: Some(rgba(0x556b81ff).into()),
predictive_background: Some(rgba(0x182e23ff).into()),
predictive_border: Some(rgba(0x254839ff).into()),
renamed: Some(rgba(0x9cced7ff).into()),
renamed_background: Some(rgba(0x2f3739ff).into()),
renamed_border: Some(rgba(0x435255ff).into()),
success: Some(rgba(0x5dc2a3ff).into()),
success_background: Some(rgba(0x182e23ff).into()),
success_border: Some(rgba(0x254839ff).into()),
unreachable: Some(rgba(0x75718eff).into()),
unreachable_background: Some(rgba(0x292739ff).into()),
unreachable_border: Some(rgba(0x423f55ff).into()),
warning: Some(rgba(0xf5c177ff).into()),
warning_background: Some(rgba(0x50341aff).into()),
warning_border: Some(rgba(0x6d4d2bff).into()),
..Default::default()
},
player: Some(PlayerColors(vec![
PlayerColor {
cursor: rgba(0x9cced7ff).into(),
background: rgba(0x9cced7ff).into(),
selection: rgba(0x9cced73d).into(),
},
PlayerColor {
cursor: rgba(0x9d7691ff).into(),
background: rgba(0x9d7691ff).into(),
selection: rgba(0x9d76913d).into(),
},
PlayerColor {
cursor: rgba(0xc4a7e6ff).into(),
background: rgba(0xc4a7e6ff).into(),
selection: rgba(0xc4a7e63d).into(),
},
PlayerColor {
cursor: rgba(0xc4a7e6ff).into(),
background: rgba(0xc4a7e6ff).into(),
selection: rgba(0xc4a7e63d).into(),
},
PlayerColor {
cursor: rgba(0x32748fff).into(),
background: rgba(0x32748fff).into(),
selection: rgba(0x32748f3d).into(),
},
PlayerColor {
cursor: rgba(0xea6f92ff).into(),
background: rgba(0xea6f92ff).into(),
selection: rgba(0xea6f923d).into(),
},
PlayerColor {
cursor: rgba(0xf5c177ff).into(),
background: rgba(0xf5c177ff).into(),
selection: rgba(0xf5c1773d).into(),
},
PlayerColor {
cursor: rgba(0x5dc2a3ff).into(),
background: rgba(0x5dc2a3ff).into(),
selection: rgba(0x5dc2a33d).into(),
},
])),
syntax: Some(UserSyntaxTheme {
highlights: vec![
(
"attribute".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"boolean".into(),
UserHighlightStyle {
color: Some(rgba(0xebbcbaff).into()),
..Default::default()
},
),
(
"comment".into(),
UserHighlightStyle {
color: Some(rgba(0x6e6a86ff).into()),
..Default::default()
},
),
(
"comment.doc".into(),
UserHighlightStyle {
color: Some(rgba(0x777390ff).into()),
..Default::default()
},
),
(
"constant".into(),
UserHighlightStyle {
color: Some(rgba(0x5dc2a3ff).into()),
..Default::default()
},
),
(
"constructor".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"embedded".into(),
UserHighlightStyle {
color: Some(rgba(0xe0def4ff).into()),
..Default::default()
},
),
(
"emphasis".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"emphasis.strong".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"enum".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"function".into(),
UserHighlightStyle {
color: Some(rgba(0xebbcbaff).into()),
..Default::default()
},
),
(
"function.method".into(),
UserHighlightStyle {
color: Some(rgba(0xebbcbaff).into()),
..Default::default()
},
),
(
"hint".into(),
UserHighlightStyle {
color: Some(rgba(0x5e768cff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"keyword".into(),
UserHighlightStyle {
color: Some(rgba(0x31748fff).into()),
..Default::default()
},
),
(
"label".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"link_text".into(),
UserHighlightStyle {
color: Some(rgba(0x9ccfd8ff).into()),
font_style: Some(UserFontStyle::Normal),
..Default::default()
},
),
(
"link_uri".into(),
UserHighlightStyle {
color: Some(rgba(0xebbcbaff).into()),
..Default::default()
},
),
(
"number".into(),
UserHighlightStyle {
color: Some(rgba(0x5dc2a3ff).into()),
..Default::default()
},
),
(
"operator".into(),
UserHighlightStyle {
color: Some(rgba(0x31748fff).into()),
..Default::default()
},
),
(
"predictive".into(),
UserHighlightStyle {
color: Some(rgba(0x556b81ff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"preproc".into(),
UserHighlightStyle {
color: Some(rgba(0xe0def4ff).into()),
..Default::default()
},
),
(
"primary".into(),
UserHighlightStyle {
color: Some(rgba(0xe0def4ff).into()),
..Default::default()
},
),
(
"property".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"punctuation".into(),
UserHighlightStyle {
color: Some(rgba(0x908caaff).into()),
..Default::default()
},
),
(
"punctuation.bracket".into(),
UserHighlightStyle {
color: Some(rgba(0x9d99b6ff).into()),
..Default::default()
},
),
(
"punctuation.delimiter".into(),
UserHighlightStyle {
color: Some(rgba(0x9d99b6ff).into()),
..Default::default()
},
),
(
"punctuation.list_marker".into(),
UserHighlightStyle {
color: Some(rgba(0x9d99b6ff).into()),
..Default::default()
},
),
(
"punctuation.special".into(),
UserHighlightStyle {
color: Some(rgba(0x9d99b6ff).into()),
..Default::default()
},
),
(
"string".into(),
UserHighlightStyle {
color: Some(rgba(0xf6c177ff).into()),
..Default::default()
},
),
(
"string.escape".into(),
UserHighlightStyle {
color: Some(rgba(0x777390ff).into()),
..Default::default()
},
),
(
"string.regex".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"string.special".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"string.special.symbol".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"tag".into(),
UserHighlightStyle {
color: Some(rgba(0x9ccfd8ff).into()),
..Default::default()
},
),
(
"text.literal".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"title".into(),
UserHighlightStyle {
color: Some(rgba(0xf6c177ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"type".into(),
UserHighlightStyle {
color: Some(rgba(0x9ccfd8ff).into()),
..Default::default()
},
),
(
"type.builtin".into(),
UserHighlightStyle {
color: Some(rgba(0x9ccfd8ff).into()),
..Default::default()
},
),
(
"variable".into(),
UserHighlightStyle {
color: Some(rgba(0xe0def4ff).into()),
..Default::default()
},
),
(
"variant".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
],
}),
},
},
UserTheme {
name: "Rosé Pine Dawn".into(),
appearance: Appearance::Light,
@ -931,464 +1389,6 @@ pub fn rose_pine() -> UserThemeFamily {
}),
},
},
UserTheme {
name: "Rosé Pine".into(),
appearance: Appearance::Dark,
styles: UserThemeStylesRefinement {
colors: ThemeColorsRefinement {
border: Some(rgba(0x423f55ff).into()),
border_variant: Some(rgba(0x232132ff).into()),
border_focused: Some(rgba(0x435255ff).into()),
border_selected: Some(rgba(0x435255ff).into()),
border_transparent: Some(rgba(0x00000000).into()),
border_disabled: Some(rgba(0x353347ff).into()),
elevated_surface_background: Some(rgba(0x1d1b2aff).into()),
surface_background: Some(rgba(0x1d1b2aff).into()),
background: Some(rgba(0x292739ff).into()),
panel_background: Some(rgba(0x1d1b2aff).into()),
element_background: Some(rgba(0x1d1b2aff).into()),
element_hover: Some(rgba(0x232132ff).into()),
element_active: Some(rgba(0x403e53ff).into()),
element_selected: Some(rgba(0x403e53ff).into()),
element_disabled: Some(rgba(0x1d1b2aff).into()),
drop_target_background: Some(rgba(0x75718e80).into()),
ghost_element_background: Some(rgba(0x00000000).into()),
ghost_element_hover: Some(rgba(0x232132ff).into()),
ghost_element_active: Some(rgba(0x403e53ff).into()),
ghost_element_selected: Some(rgba(0x403e53ff).into()),
ghost_element_disabled: Some(rgba(0x1d1b2aff).into()),
text: Some(rgba(0xe0def4ff).into()),
text_muted: Some(rgba(0x75718eff).into()),
text_placeholder: Some(rgba(0x2f2b43ff).into()),
text_disabled: Some(rgba(0x2f2b43ff).into()),
text_accent: Some(rgba(0x9cced7ff).into()),
icon: Some(rgba(0xe0def4ff).into()),
icon_muted: Some(rgba(0x75718eff).into()),
icon_disabled: Some(rgba(0x2f2b43ff).into()),
icon_placeholder: Some(rgba(0x75718eff).into()),
icon_accent: Some(rgba(0x9cced7ff).into()),
status_bar_background: Some(rgba(0x292739ff).into()),
title_bar_background: Some(rgba(0x292739ff).into()),
toolbar_background: Some(rgba(0x191724ff).into()),
tab_bar_background: Some(rgba(0x1d1b2aff).into()),
tab_inactive_background: Some(rgba(0x1d1b2aff).into()),
tab_active_background: Some(rgba(0x191724ff).into()),
scrollbar_thumb_background: Some(rgba(0xe0def44c).into()),
scrollbar_thumb_hover_background: Some(rgba(0x232132ff).into()),
scrollbar_thumb_border: Some(rgba(0x232132ff).into()),
scrollbar_track_background: Some(rgba(0x00000000).into()),
scrollbar_track_border: Some(rgba(0x1c1a29ff).into()),
editor_foreground: Some(rgba(0xe0def4ff).into()),
editor_background: Some(rgba(0x191724ff).into()),
editor_gutter_background: Some(rgba(0x191724ff).into()),
editor_subheader_background: Some(rgba(0x1d1b2aff).into()),
editor_active_line_background: Some(rgba(0x1d1b2abf).into()),
editor_highlighted_line_background: Some(rgba(0x1d1b2aff).into()),
editor_line_number: Some(rgba(0xe0def459).into()),
editor_active_line_number: Some(rgba(0xe0def4ff).into()),
editor_invisible: Some(rgba(0x75718eff).into()),
editor_wrap_guide: Some(rgba(0xe0def40d).into()),
editor_active_wrap_guide: Some(rgba(0xe0def41a).into()),
editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()),
editor_document_highlight_write_background: Some(rgba(0x28253c66).into()),
terminal_background: Some(rgba(0x191724ff).into()),
terminal_ansi_bright_black: Some(rgba(0x403d55ff).into()),
terminal_ansi_bright_red: Some(rgba(0x7e3647ff).into()),
terminal_ansi_bright_green: Some(rgba(0x31614fff).into()),
terminal_ansi_bright_yellow: Some(rgba(0x8a653bff).into()),
terminal_ansi_bright_blue: Some(rgba(0x566c70ff).into()),
terminal_ansi_bright_magenta: Some(rgba(0x4c3b47ff).into()),
terminal_ansi_bright_cyan: Some(rgba(0x203a46ff).into()),
terminal_ansi_bright_white: Some(rgba(0xe0def4ff).into()),
terminal_ansi_black: Some(rgba(0x191724ff).into()),
terminal_ansi_red: Some(rgba(0xea6f92ff).into()),
terminal_ansi_green: Some(rgba(0x5dc2a3ff).into()),
terminal_ansi_yellow: Some(rgba(0xf5c177ff).into()),
terminal_ansi_blue: Some(rgba(0x9cced7ff).into()),
terminal_ansi_magenta: Some(rgba(0x9d7691ff).into()),
terminal_ansi_cyan: Some(rgba(0x32748fff).into()),
terminal_ansi_white: Some(rgba(0xe0def4ff).into()),
link_text_hover: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
status: StatusColorsRefinement {
conflict: Some(rgba(0xf5c177ff).into()),
conflict_background: Some(rgba(0x50341aff).into()),
conflict_border: Some(rgba(0x6d4d2bff).into()),
created: Some(rgba(0x5dc2a3ff).into()),
created_background: Some(rgba(0x182e23ff).into()),
created_border: Some(rgba(0x254839ff).into()),
deleted: Some(rgba(0xea6f92ff).into()),
deleted_background: Some(rgba(0x431820ff).into()),
deleted_border: Some(rgba(0x612834ff).into()),
error: Some(rgba(0xea6f92ff).into()),
error_background: Some(rgba(0x431820ff).into()),
error_border: Some(rgba(0x612834ff).into()),
hidden: Some(rgba(0x2f2b43ff).into()),
hidden_background: Some(rgba(0x292739ff).into()),
hidden_border: Some(rgba(0x353347ff).into()),
hint: Some(rgba(0x5e768cff).into()),
hint_background: Some(rgba(0x2f3739ff).into()),
hint_border: Some(rgba(0x435255ff).into()),
ignored: Some(rgba(0x75718eff).into()),
ignored_background: Some(rgba(0x292739ff).into()),
ignored_border: Some(rgba(0x423f55ff).into()),
info: Some(rgba(0x9cced7ff).into()),
info_background: Some(rgba(0x2f3739ff).into()),
info_border: Some(rgba(0x435255ff).into()),
modified: Some(rgba(0xf5c177ff).into()),
modified_background: Some(rgba(0x50341aff).into()),
modified_border: Some(rgba(0x6d4d2bff).into()),
predictive: Some(rgba(0x556b81ff).into()),
predictive_background: Some(rgba(0x182e23ff).into()),
predictive_border: Some(rgba(0x254839ff).into()),
renamed: Some(rgba(0x9cced7ff).into()),
renamed_background: Some(rgba(0x2f3739ff).into()),
renamed_border: Some(rgba(0x435255ff).into()),
success: Some(rgba(0x5dc2a3ff).into()),
success_background: Some(rgba(0x182e23ff).into()),
success_border: Some(rgba(0x254839ff).into()),
unreachable: Some(rgba(0x75718eff).into()),
unreachable_background: Some(rgba(0x292739ff).into()),
unreachable_border: Some(rgba(0x423f55ff).into()),
warning: Some(rgba(0xf5c177ff).into()),
warning_background: Some(rgba(0x50341aff).into()),
warning_border: Some(rgba(0x6d4d2bff).into()),
..Default::default()
},
player: Some(PlayerColors(vec![
PlayerColor {
cursor: rgba(0x9cced7ff).into(),
background: rgba(0x9cced7ff).into(),
selection: rgba(0x9cced73d).into(),
},
PlayerColor {
cursor: rgba(0x9d7691ff).into(),
background: rgba(0x9d7691ff).into(),
selection: rgba(0x9d76913d).into(),
},
PlayerColor {
cursor: rgba(0xc4a7e6ff).into(),
background: rgba(0xc4a7e6ff).into(),
selection: rgba(0xc4a7e63d).into(),
},
PlayerColor {
cursor: rgba(0xc4a7e6ff).into(),
background: rgba(0xc4a7e6ff).into(),
selection: rgba(0xc4a7e63d).into(),
},
PlayerColor {
cursor: rgba(0x32748fff).into(),
background: rgba(0x32748fff).into(),
selection: rgba(0x32748f3d).into(),
},
PlayerColor {
cursor: rgba(0xea6f92ff).into(),
background: rgba(0xea6f92ff).into(),
selection: rgba(0xea6f923d).into(),
},
PlayerColor {
cursor: rgba(0xf5c177ff).into(),
background: rgba(0xf5c177ff).into(),
selection: rgba(0xf5c1773d).into(),
},
PlayerColor {
cursor: rgba(0x5dc2a3ff).into(),
background: rgba(0x5dc2a3ff).into(),
selection: rgba(0x5dc2a33d).into(),
},
])),
syntax: Some(UserSyntaxTheme {
highlights: vec![
(
"attribute".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"boolean".into(),
UserHighlightStyle {
color: Some(rgba(0xebbcbaff).into()),
..Default::default()
},
),
(
"comment".into(),
UserHighlightStyle {
color: Some(rgba(0x6e6a86ff).into()),
..Default::default()
},
),
(
"comment.doc".into(),
UserHighlightStyle {
color: Some(rgba(0x777390ff).into()),
..Default::default()
},
),
(
"constant".into(),
UserHighlightStyle {
color: Some(rgba(0x5dc2a3ff).into()),
..Default::default()
},
),
(
"constructor".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"embedded".into(),
UserHighlightStyle {
color: Some(rgba(0xe0def4ff).into()),
..Default::default()
},
),
(
"emphasis".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"emphasis.strong".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"enum".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"function".into(),
UserHighlightStyle {
color: Some(rgba(0xebbcbaff).into()),
..Default::default()
},
),
(
"function.method".into(),
UserHighlightStyle {
color: Some(rgba(0xebbcbaff).into()),
..Default::default()
},
),
(
"hint".into(),
UserHighlightStyle {
color: Some(rgba(0x5e768cff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"keyword".into(),
UserHighlightStyle {
color: Some(rgba(0x31748fff).into()),
..Default::default()
},
),
(
"label".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"link_text".into(),
UserHighlightStyle {
color: Some(rgba(0x9ccfd8ff).into()),
font_style: Some(UserFontStyle::Normal),
..Default::default()
},
),
(
"link_uri".into(),
UserHighlightStyle {
color: Some(rgba(0xebbcbaff).into()),
..Default::default()
},
),
(
"number".into(),
UserHighlightStyle {
color: Some(rgba(0x5dc2a3ff).into()),
..Default::default()
},
),
(
"operator".into(),
UserHighlightStyle {
color: Some(rgba(0x31748fff).into()),
..Default::default()
},
),
(
"predictive".into(),
UserHighlightStyle {
color: Some(rgba(0x556b81ff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"preproc".into(),
UserHighlightStyle {
color: Some(rgba(0xe0def4ff).into()),
..Default::default()
},
),
(
"primary".into(),
UserHighlightStyle {
color: Some(rgba(0xe0def4ff).into()),
..Default::default()
},
),
(
"property".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
(
"punctuation".into(),
UserHighlightStyle {
color: Some(rgba(0x908caaff).into()),
..Default::default()
},
),
(
"punctuation.bracket".into(),
UserHighlightStyle {
color: Some(rgba(0x9d99b6ff).into()),
..Default::default()
},
),
(
"punctuation.delimiter".into(),
UserHighlightStyle {
color: Some(rgba(0x9d99b6ff).into()),
..Default::default()
},
),
(
"punctuation.list_marker".into(),
UserHighlightStyle {
color: Some(rgba(0x9d99b6ff).into()),
..Default::default()
},
),
(
"punctuation.special".into(),
UserHighlightStyle {
color: Some(rgba(0x9d99b6ff).into()),
..Default::default()
},
),
(
"string".into(),
UserHighlightStyle {
color: Some(rgba(0xf6c177ff).into()),
..Default::default()
},
),
(
"string.escape".into(),
UserHighlightStyle {
color: Some(rgba(0x777390ff).into()),
..Default::default()
},
),
(
"string.regex".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"string.special".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"string.special.symbol".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"tag".into(),
UserHighlightStyle {
color: Some(rgba(0x9ccfd8ff).into()),
..Default::default()
},
),
(
"text.literal".into(),
UserHighlightStyle {
color: Some(rgba(0xc4a7e6ff).into()),
..Default::default()
},
),
(
"title".into(),
UserHighlightStyle {
color: Some(rgba(0xf6c177ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"type".into(),
UserHighlightStyle {
color: Some(rgba(0x9ccfd8ff).into()),
..Default::default()
},
),
(
"type.builtin".into(),
UserHighlightStyle {
color: Some(rgba(0x9ccfd8ff).into()),
..Default::default()
},
),
(
"variable".into(),
UserHighlightStyle {
color: Some(rgba(0xe0def4ff).into()),
..Default::default()
},
),
(
"variant".into(),
UserHighlightStyle {
color: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
),
],
}),
},
},
],
}
}

View File

@ -15,450 +15,6 @@ pub fn solarized() -> UserThemeFamily {
name: "Solarized".into(),
author: "Zed Industries".into(),
themes: vec![
UserTheme {
name: "Solarized Light".into(),
appearance: Appearance::Light,
styles: UserThemeStylesRefinement {
colors: ThemeColorsRefinement {
border: Some(rgba(0x9faaa8ff).into()),
border_variant: Some(rgba(0xdcdacbff).into()),
border_focused: Some(rgba(0xbfd3efff).into()),
border_selected: Some(rgba(0xbfd3efff).into()),
border_transparent: Some(rgba(0x00000000).into()),
border_disabled: Some(rgba(0xb7bdb6ff).into()),
elevated_surface_background: Some(rgba(0xf3eddaff).into()),
surface_background: Some(rgba(0xf3eddaff).into()),
background: Some(rgba(0xcfd0c4ff).into()),
panel_background: Some(rgba(0xf3eddaff).into()),
element_background: Some(rgba(0xf3eddaff).into()),
element_hover: Some(rgba(0xdcdacbff).into()),
element_active: Some(rgba(0xa2aca9ff).into()),
element_selected: Some(rgba(0xa2aca9ff).into()),
element_disabled: Some(rgba(0xf3eddaff).into()),
drop_target_background: Some(rgba(0x34555e80).into()),
ghost_element_background: Some(rgba(0x00000000).into()),
ghost_element_hover: Some(rgba(0xdcdacbff).into()),
ghost_element_active: Some(rgba(0xa2aca9ff).into()),
ghost_element_selected: Some(rgba(0xa2aca9ff).into()),
ghost_element_disabled: Some(rgba(0xf3eddaff).into()),
text: Some(rgba(0x002b36ff).into()),
text_muted: Some(rgba(0x34555eff).into()),
text_placeholder: Some(rgba(0x6a7f86ff).into()),
text_disabled: Some(rgba(0x6a7f86ff).into()),
text_accent: Some(rgba(0x298bd1ff).into()),
icon: Some(rgba(0x002b36ff).into()),
icon_muted: Some(rgba(0x34555eff).into()),
icon_disabled: Some(rgba(0x6a7f86ff).into()),
icon_placeholder: Some(rgba(0x34555eff).into()),
icon_accent: Some(rgba(0x298bd1ff).into()),
status_bar_background: Some(rgba(0xcfd0c4ff).into()),
title_bar_background: Some(rgba(0xcfd0c4ff).into()),
toolbar_background: Some(rgba(0xfdf6e3ff).into()),
tab_bar_background: Some(rgba(0xf3eddaff).into()),
tab_inactive_background: Some(rgba(0xf3eddaff).into()),
tab_active_background: Some(rgba(0xfdf6e3ff).into()),
scrollbar_thumb_background: Some(rgba(0x002b364c).into()),
scrollbar_thumb_hover_background: Some(rgba(0xdcdacbff).into()),
scrollbar_thumb_border: Some(rgba(0xdcdacbff).into()),
scrollbar_track_background: Some(rgba(0x00000000).into()),
scrollbar_track_border: Some(rgba(0xf5eedbff).into()),
editor_foreground: Some(rgba(0x002b36ff).into()),
editor_background: Some(rgba(0xfdf6e3ff).into()),
editor_gutter_background: Some(rgba(0xfdf6e3ff).into()),
editor_subheader_background: Some(rgba(0xf3eddaff).into()),
editor_active_line_background: Some(rgba(0xf3eddabf).into()),
editor_highlighted_line_background: Some(rgba(0xf3eddaff).into()),
editor_line_number: Some(rgba(0x002b3659).into()),
editor_active_line_number: Some(rgba(0x002b36ff).into()),
editor_invisible: Some(rgba(0x34555eff).into()),
editor_wrap_guide: Some(rgba(0x002b360d).into()),
editor_active_wrap_guide: Some(rgba(0x002b361a).into()),
editor_document_highlight_read_background: Some(rgba(0x298bd11a).into()),
editor_document_highlight_write_background: Some(rgba(0x6d828866).into()),
terminal_background: Some(rgba(0xfdf6e3ff).into()),
terminal_ansi_bright_black: Some(rgba(0x7b8e91ff).into()),
terminal_ansi_bright_red: Some(rgba(0xfaa091ff).into()),
terminal_ansi_bright_green: Some(rgba(0xc6cb8bff).into()),
terminal_ansi_bright_yellow: Some(rgba(0xe1c28aff).into()),
terminal_ansi_bright_blue: Some(rgba(0xa5c3e9ff).into()),
terminal_ansi_bright_magenta: Some(rgba(0xf0a2bfff).into()),
terminal_ansi_bright_cyan: Some(rgba(0x9fd0cbff).into()),
terminal_ansi_bright_white: Some(rgba(0x002b36ff).into()),
terminal_ansi_black: Some(rgba(0xfdf6e3ff).into()),
terminal_ansi_red: Some(rgba(0xdc3330ff).into()),
terminal_ansi_green: Some(rgba(0x859904ff).into()),
terminal_ansi_yellow: Some(rgba(0xb58904ff).into()),
terminal_ansi_blue: Some(rgba(0x298bd1ff).into()),
terminal_ansi_magenta: Some(rgba(0xd33882ff).into()),
terminal_ansi_cyan: Some(rgba(0x2ca198ff).into()),
terminal_ansi_white: Some(rgba(0x002b36ff).into()),
link_text_hover: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
status: StatusColorsRefinement {
conflict: Some(rgba(0xb58904ff).into()),
conflict_background: Some(rgba(0xf5e6d0ff).into()),
conflict_border: Some(rgba(0xebd3aaff).into()),
created: Some(rgba(0x859904ff).into()),
created_background: Some(rgba(0xe9ead0ff).into()),
created_border: Some(rgba(0xd6d9abff).into()),
deleted: Some(rgba(0xdc3330ff).into()),
deleted_background: Some(rgba(0xffd9d2ff).into()),
deleted_border: Some(rgba(0xffbbafff).into()),
error: Some(rgba(0xdc3330ff).into()),
error_background: Some(rgba(0xffd9d2ff).into()),
error_border: Some(rgba(0xffbbafff).into()),
hidden: Some(rgba(0x6a7f86ff).into()),
hidden_background: Some(rgba(0xcfd0c4ff).into()),
hidden_border: Some(rgba(0xb7bdb6ff).into()),
hint: Some(rgba(0x5889a3ff).into()),
hint_background: Some(rgba(0xdbe6f6ff).into()),
hint_border: Some(rgba(0xbfd3efff).into()),
ignored: Some(rgba(0x34555eff).into()),
ignored_background: Some(rgba(0xcfd0c4ff).into()),
ignored_border: Some(rgba(0x9faaa8ff).into()),
info: Some(rgba(0x298bd1ff).into()),
info_background: Some(rgba(0xdbe6f6ff).into()),
info_border: Some(rgba(0xbfd3efff).into()),
modified: Some(rgba(0xb58904ff).into()),
modified_background: Some(rgba(0xf5e6d0ff).into()),
modified_border: Some(rgba(0xebd3aaff).into()),
predictive: Some(rgba(0x679aafff).into()),
predictive_background: Some(rgba(0xe9ead0ff).into()),
predictive_border: Some(rgba(0xd6d9abff).into()),
renamed: Some(rgba(0x298bd1ff).into()),
renamed_background: Some(rgba(0xdbe6f6ff).into()),
renamed_border: Some(rgba(0xbfd3efff).into()),
success: Some(rgba(0x859904ff).into()),
success_background: Some(rgba(0xe9ead0ff).into()),
success_border: Some(rgba(0xd6d9abff).into()),
unreachable: Some(rgba(0x34555eff).into()),
unreachable_background: Some(rgba(0xcfd0c4ff).into()),
unreachable_border: Some(rgba(0x9faaa8ff).into()),
warning: Some(rgba(0xb58904ff).into()),
warning_background: Some(rgba(0xf5e6d0ff).into()),
warning_border: Some(rgba(0xebd3aaff).into()),
..Default::default()
},
player: Some(PlayerColors(vec![
PlayerColor {
cursor: rgba(0x298bd1ff).into(),
background: rgba(0x298bd1ff).into(),
selection: rgba(0x298bd13d).into(),
},
PlayerColor {
cursor: rgba(0xd33882ff).into(),
background: rgba(0xd33882ff).into(),
selection: rgba(0xd338823d).into(),
},
PlayerColor {
cursor: rgba(0xcb4c18ff).into(),
background: rgba(0xcb4c18ff).into(),
selection: rgba(0xcb4c183d).into(),
},
PlayerColor {
cursor: rgba(0x6d71c4ff).into(),
background: rgba(0x6d71c4ff).into(),
selection: rgba(0x6d71c43d).into(),
},
PlayerColor {
cursor: rgba(0x2ca198ff).into(),
background: rgba(0x2ca198ff).into(),
selection: rgba(0x2ca1983d).into(),
},
PlayerColor {
cursor: rgba(0xdc3330ff).into(),
background: rgba(0xdc3330ff).into(),
selection: rgba(0xdc33303d).into(),
},
PlayerColor {
cursor: rgba(0xb58904ff).into(),
background: rgba(0xb58904ff).into(),
selection: rgba(0xb589043d).into(),
},
PlayerColor {
cursor: rgba(0x859904ff).into(),
background: rgba(0x859904ff).into(),
selection: rgba(0x8599043d).into(),
},
])),
syntax: Some(UserSyntaxTheme {
highlights: vec![
(
"attribute".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"boolean".into(),
UserHighlightStyle {
color: Some(rgba(0x859904ff).into()),
..Default::default()
},
),
(
"comment".into(),
UserHighlightStyle {
color: Some(rgba(0x30525bff).into()),
..Default::default()
},
),
(
"comment.doc".into(),
UserHighlightStyle {
color: Some(rgba(0x30525bff).into()),
..Default::default()
},
),
(
"constant".into(),
UserHighlightStyle {
color: Some(rgba(0x859904ff).into()),
..Default::default()
},
),
(
"constructor".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"embedded".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
..Default::default()
},
),
(
"emphasis".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"emphasis.strong".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"enum".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"function".into(),
UserHighlightStyle {
color: Some(rgba(0xb58904ff).into()),
..Default::default()
},
),
(
"hint".into(),
UserHighlightStyle {
color: Some(rgba(0x5889a3ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"keyword".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"label".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"link_text".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"link_uri".into(),
UserHighlightStyle {
color: Some(rgba(0x859904ff).into()),
..Default::default()
},
),
(
"number".into(),
UserHighlightStyle {
color: Some(rgba(0x859904ff).into()),
..Default::default()
},
),
(
"operator".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"predictive".into(),
UserHighlightStyle {
color: Some(rgba(0x679aafff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"preproc".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
..Default::default()
},
),
(
"primary".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
..Default::default()
},
),
(
"property".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"punctuation".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"punctuation.bracket".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"punctuation.delimiter".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"punctuation.list_marker".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"punctuation.special".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"string".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"string.escape".into(),
UserHighlightStyle {
color: Some(rgba(0x30525bff).into()),
..Default::default()
},
),
(
"string.regex".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"string.special".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"string.special.symbol".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"tag".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"text.literal".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"title".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"type".into(),
UserHighlightStyle {
color: Some(rgba(0x2ca198ff).into()),
..Default::default()
},
),
(
"variable".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
..Default::default()
},
),
(
"variant".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
],
}),
},
},
UserTheme {
name: "Solarized Dark".into(),
appearance: Appearance::Dark,
@ -903,6 +459,450 @@ pub fn solarized() -> UserThemeFamily {
}),
},
},
UserTheme {
name: "Solarized Light".into(),
appearance: Appearance::Light,
styles: UserThemeStylesRefinement {
colors: ThemeColorsRefinement {
border: Some(rgba(0x9faaa8ff).into()),
border_variant: Some(rgba(0xdcdacbff).into()),
border_focused: Some(rgba(0xbfd3efff).into()),
border_selected: Some(rgba(0xbfd3efff).into()),
border_transparent: Some(rgba(0x00000000).into()),
border_disabled: Some(rgba(0xb7bdb6ff).into()),
elevated_surface_background: Some(rgba(0xf3eddaff).into()),
surface_background: Some(rgba(0xf3eddaff).into()),
background: Some(rgba(0xcfd0c4ff).into()),
panel_background: Some(rgba(0xf3eddaff).into()),
element_background: Some(rgba(0xf3eddaff).into()),
element_hover: Some(rgba(0xdcdacbff).into()),
element_active: Some(rgba(0xa2aca9ff).into()),
element_selected: Some(rgba(0xa2aca9ff).into()),
element_disabled: Some(rgba(0xf3eddaff).into()),
drop_target_background: Some(rgba(0x34555e80).into()),
ghost_element_background: Some(rgba(0x00000000).into()),
ghost_element_hover: Some(rgba(0xdcdacbff).into()),
ghost_element_active: Some(rgba(0xa2aca9ff).into()),
ghost_element_selected: Some(rgba(0xa2aca9ff).into()),
ghost_element_disabled: Some(rgba(0xf3eddaff).into()),
text: Some(rgba(0x002b36ff).into()),
text_muted: Some(rgba(0x34555eff).into()),
text_placeholder: Some(rgba(0x6a7f86ff).into()),
text_disabled: Some(rgba(0x6a7f86ff).into()),
text_accent: Some(rgba(0x298bd1ff).into()),
icon: Some(rgba(0x002b36ff).into()),
icon_muted: Some(rgba(0x34555eff).into()),
icon_disabled: Some(rgba(0x6a7f86ff).into()),
icon_placeholder: Some(rgba(0x34555eff).into()),
icon_accent: Some(rgba(0x298bd1ff).into()),
status_bar_background: Some(rgba(0xcfd0c4ff).into()),
title_bar_background: Some(rgba(0xcfd0c4ff).into()),
toolbar_background: Some(rgba(0xfdf6e3ff).into()),
tab_bar_background: Some(rgba(0xf3eddaff).into()),
tab_inactive_background: Some(rgba(0xf3eddaff).into()),
tab_active_background: Some(rgba(0xfdf6e3ff).into()),
scrollbar_thumb_background: Some(rgba(0x002b364c).into()),
scrollbar_thumb_hover_background: Some(rgba(0xdcdacbff).into()),
scrollbar_thumb_border: Some(rgba(0xdcdacbff).into()),
scrollbar_track_background: Some(rgba(0x00000000).into()),
scrollbar_track_border: Some(rgba(0xf5eedbff).into()),
editor_foreground: Some(rgba(0x002b36ff).into()),
editor_background: Some(rgba(0xfdf6e3ff).into()),
editor_gutter_background: Some(rgba(0xfdf6e3ff).into()),
editor_subheader_background: Some(rgba(0xf3eddaff).into()),
editor_active_line_background: Some(rgba(0xf3eddabf).into()),
editor_highlighted_line_background: Some(rgba(0xf3eddaff).into()),
editor_line_number: Some(rgba(0x002b3659).into()),
editor_active_line_number: Some(rgba(0x002b36ff).into()),
editor_invisible: Some(rgba(0x34555eff).into()),
editor_wrap_guide: Some(rgba(0x002b360d).into()),
editor_active_wrap_guide: Some(rgba(0x002b361a).into()),
editor_document_highlight_read_background: Some(rgba(0x298bd11a).into()),
editor_document_highlight_write_background: Some(rgba(0x6d828866).into()),
terminal_background: Some(rgba(0xfdf6e3ff).into()),
terminal_ansi_bright_black: Some(rgba(0x7b8e91ff).into()),
terminal_ansi_bright_red: Some(rgba(0xfaa091ff).into()),
terminal_ansi_bright_green: Some(rgba(0xc6cb8bff).into()),
terminal_ansi_bright_yellow: Some(rgba(0xe1c28aff).into()),
terminal_ansi_bright_blue: Some(rgba(0xa5c3e9ff).into()),
terminal_ansi_bright_magenta: Some(rgba(0xf0a2bfff).into()),
terminal_ansi_bright_cyan: Some(rgba(0x9fd0cbff).into()),
terminal_ansi_bright_white: Some(rgba(0x002b36ff).into()),
terminal_ansi_black: Some(rgba(0xfdf6e3ff).into()),
terminal_ansi_red: Some(rgba(0xdc3330ff).into()),
terminal_ansi_green: Some(rgba(0x859904ff).into()),
terminal_ansi_yellow: Some(rgba(0xb58904ff).into()),
terminal_ansi_blue: Some(rgba(0x298bd1ff).into()),
terminal_ansi_magenta: Some(rgba(0xd33882ff).into()),
terminal_ansi_cyan: Some(rgba(0x2ca198ff).into()),
terminal_ansi_white: Some(rgba(0x002b36ff).into()),
link_text_hover: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
status: StatusColorsRefinement {
conflict: Some(rgba(0xb58904ff).into()),
conflict_background: Some(rgba(0xf5e6d0ff).into()),
conflict_border: Some(rgba(0xebd3aaff).into()),
created: Some(rgba(0x859904ff).into()),
created_background: Some(rgba(0xe9ead0ff).into()),
created_border: Some(rgba(0xd6d9abff).into()),
deleted: Some(rgba(0xdc3330ff).into()),
deleted_background: Some(rgba(0xffd9d2ff).into()),
deleted_border: Some(rgba(0xffbbafff).into()),
error: Some(rgba(0xdc3330ff).into()),
error_background: Some(rgba(0xffd9d2ff).into()),
error_border: Some(rgba(0xffbbafff).into()),
hidden: Some(rgba(0x6a7f86ff).into()),
hidden_background: Some(rgba(0xcfd0c4ff).into()),
hidden_border: Some(rgba(0xb7bdb6ff).into()),
hint: Some(rgba(0x5889a3ff).into()),
hint_background: Some(rgba(0xdbe6f6ff).into()),
hint_border: Some(rgba(0xbfd3efff).into()),
ignored: Some(rgba(0x34555eff).into()),
ignored_background: Some(rgba(0xcfd0c4ff).into()),
ignored_border: Some(rgba(0x9faaa8ff).into()),
info: Some(rgba(0x298bd1ff).into()),
info_background: Some(rgba(0xdbe6f6ff).into()),
info_border: Some(rgba(0xbfd3efff).into()),
modified: Some(rgba(0xb58904ff).into()),
modified_background: Some(rgba(0xf5e6d0ff).into()),
modified_border: Some(rgba(0xebd3aaff).into()),
predictive: Some(rgba(0x679aafff).into()),
predictive_background: Some(rgba(0xe9ead0ff).into()),
predictive_border: Some(rgba(0xd6d9abff).into()),
renamed: Some(rgba(0x298bd1ff).into()),
renamed_background: Some(rgba(0xdbe6f6ff).into()),
renamed_border: Some(rgba(0xbfd3efff).into()),
success: Some(rgba(0x859904ff).into()),
success_background: Some(rgba(0xe9ead0ff).into()),
success_border: Some(rgba(0xd6d9abff).into()),
unreachable: Some(rgba(0x34555eff).into()),
unreachable_background: Some(rgba(0xcfd0c4ff).into()),
unreachable_border: Some(rgba(0x9faaa8ff).into()),
warning: Some(rgba(0xb58904ff).into()),
warning_background: Some(rgba(0xf5e6d0ff).into()),
warning_border: Some(rgba(0xebd3aaff).into()),
..Default::default()
},
player: Some(PlayerColors(vec![
PlayerColor {
cursor: rgba(0x298bd1ff).into(),
background: rgba(0x298bd1ff).into(),
selection: rgba(0x298bd13d).into(),
},
PlayerColor {
cursor: rgba(0xd33882ff).into(),
background: rgba(0xd33882ff).into(),
selection: rgba(0xd338823d).into(),
},
PlayerColor {
cursor: rgba(0xcb4c18ff).into(),
background: rgba(0xcb4c18ff).into(),
selection: rgba(0xcb4c183d).into(),
},
PlayerColor {
cursor: rgba(0x6d71c4ff).into(),
background: rgba(0x6d71c4ff).into(),
selection: rgba(0x6d71c43d).into(),
},
PlayerColor {
cursor: rgba(0x2ca198ff).into(),
background: rgba(0x2ca198ff).into(),
selection: rgba(0x2ca1983d).into(),
},
PlayerColor {
cursor: rgba(0xdc3330ff).into(),
background: rgba(0xdc3330ff).into(),
selection: rgba(0xdc33303d).into(),
},
PlayerColor {
cursor: rgba(0xb58904ff).into(),
background: rgba(0xb58904ff).into(),
selection: rgba(0xb589043d).into(),
},
PlayerColor {
cursor: rgba(0x859904ff).into(),
background: rgba(0x859904ff).into(),
selection: rgba(0x8599043d).into(),
},
])),
syntax: Some(UserSyntaxTheme {
highlights: vec![
(
"attribute".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"boolean".into(),
UserHighlightStyle {
color: Some(rgba(0x859904ff).into()),
..Default::default()
},
),
(
"comment".into(),
UserHighlightStyle {
color: Some(rgba(0x30525bff).into()),
..Default::default()
},
),
(
"comment.doc".into(),
UserHighlightStyle {
color: Some(rgba(0x30525bff).into()),
..Default::default()
},
),
(
"constant".into(),
UserHighlightStyle {
color: Some(rgba(0x859904ff).into()),
..Default::default()
},
),
(
"constructor".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"embedded".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
..Default::default()
},
),
(
"emphasis".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"emphasis.strong".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"enum".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"function".into(),
UserHighlightStyle {
color: Some(rgba(0xb58904ff).into()),
..Default::default()
},
),
(
"hint".into(),
UserHighlightStyle {
color: Some(rgba(0x5889a3ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"keyword".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"label".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"link_text".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"link_uri".into(),
UserHighlightStyle {
color: Some(rgba(0x859904ff).into()),
..Default::default()
},
),
(
"number".into(),
UserHighlightStyle {
color: Some(rgba(0x859904ff).into()),
..Default::default()
},
),
(
"operator".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"predictive".into(),
UserHighlightStyle {
color: Some(rgba(0x679aafff).into()),
font_style: Some(UserFontStyle::Italic),
..Default::default()
},
),
(
"preproc".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
..Default::default()
},
),
(
"primary".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
..Default::default()
},
),
(
"property".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"punctuation".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"punctuation.bracket".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"punctuation.delimiter".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"punctuation.list_marker".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"punctuation.special".into(),
UserHighlightStyle {
color: Some(rgba(0x05333eff).into()),
..Default::default()
},
),
(
"string".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"string.escape".into(),
UserHighlightStyle {
color: Some(rgba(0x30525bff).into()),
..Default::default()
},
),
(
"string.regex".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"string.special".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"string.special.symbol".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"tag".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
(
"text.literal".into(),
UserHighlightStyle {
color: Some(rgba(0xcb4c18ff).into()),
..Default::default()
},
),
(
"title".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
font_weight: Some(UserFontWeight(700.0)),
..Default::default()
},
),
(
"type".into(),
UserHighlightStyle {
color: Some(rgba(0x2ca198ff).into()),
..Default::default()
},
),
(
"variable".into(),
UserHighlightStyle {
color: Some(rgba(0x002b36ff).into()),
..Default::default()
},
),
(
"variant".into(),
UserHighlightStyle {
color: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
),
],
}),
},
},
],
}
}

View File

@ -11,6 +11,7 @@ clap = { version = "4.4", features = ["derive"] }
convert_case = "0.6.0"
gpui = { path = "../gpui" }
indexmap = { version = "1.6.2", features = ["serde"] }
indoc.workspace = true
json_comments = "0.2.2"
log.workspace = true
palette = { version = "0.7.3", default-features = false, features = ["std"] }

View File

@ -18,6 +18,7 @@ use clap::Parser;
use convert_case::{Case, Casing};
use gpui::serde_json;
use indexmap::IndexMap;
use indoc::formatdoc;
use json_comments::StripComments;
use log::LevelFilter;
use serde::Deserialize;
@ -28,7 +29,7 @@ use crate::theme_printer::UserThemeFamilyPrinter;
use crate::vscode::VsCodeTheme;
use crate::vscode::VsCodeThemeConverter;
use crate::zed1::theme::Theme as Zed1Theme;
use crate::zed1::Zed1ThemeConverter;
use crate::zed1::{zed1_theme_licenses, Zed1ThemeConverter};
#[derive(Debug, Deserialize)]
struct FamilyMetadata {
@ -200,7 +201,13 @@ fn main() -> Result<()> {
"Summercamp",
];
let mut zed1_themes_by_family: HashMap<String, Vec<UserTheme>> = HashMap::from_iter(
let zed1_licenses_by_theme: HashMap<String, zed1::Zed1ThemeLicense> = HashMap::from_iter(
zed1_theme_licenses()
.into_iter()
.map(|theme_license| (theme_license.theme.clone(), theme_license)),
);
let mut zed1_themes_by_family: IndexMap<String, Vec<UserTheme>> = IndexMap::from_iter(
zed1_theme_familes
.into_iter()
.map(|family| (family.to_string(), Vec::new())),
@ -254,13 +261,44 @@ fn main() -> Result<()> {
themes_for_family.push(theme);
}
zed1_themes_by_family.sort_keys();
let mut licenses = Vec::new();
for (family, themes) in zed1_themes_by_family {
let theme_family = UserThemeFamily {
let mut theme_family = UserThemeFamily {
name: family,
author: "Zed Industries".to_string(),
themes,
};
theme_family
.themes
.sort_unstable_by_key(|theme| theme.name.clone());
for theme in &theme_family.themes {
let license = zed1_licenses_by_theme
.get(&theme.name)
.ok_or_else(|| anyhow!("missing license for theme: '{}'", theme.name))?;
let license_header = match license.license_url.as_ref() {
Some(license_url) => {
format!("[{theme_name}]({license_url})", theme_name = theme.name)
}
None => theme.name.clone(),
};
licenses.push(formatdoc!(
"
## {license_header}
{license_text}
********************************************************************************
",
license_text = license.license_text
));
}
theme_families.push(theme_family);
}
@ -357,6 +395,12 @@ fn main() -> Result<()> {
mod_rs_file.write_all(mod_rs_contents.as_bytes())?;
log::info!("Writing LICENSES file...");
let mut licenses_file = File::create(themes_output_path.join(format!("LICENSES")))?;
licenses_file.write_all(licenses.join("\n").as_bytes())?;
log::info!("Formatting themes...");
let format_result = format_themes_crate()

View File

@ -1,4 +1,6 @@
mod converter;
mod licenses;
pub mod theme;
pub use converter::*;
pub use licenses::*;

File diff suppressed because it is too large Load Diff

View File

@ -92,6 +92,13 @@ impl Selectable for Button {
}
}
impl SelectableButton for Button {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.base = self.base.selected_style(style);
self
}
}
impl Disableable for Button {
fn disabled(mut self, disabled: bool) -> Self {
self.base = self.base.disabled(disabled);

View File

@ -12,6 +12,7 @@ pub(super) struct ButtonIcon {
disabled: bool,
selected: bool,
selected_icon: Option<Icon>,
selected_style: Option<ButtonStyle>,
}
impl ButtonIcon {
@ -23,6 +24,7 @@ impl ButtonIcon {
disabled: false,
selected: false,
selected_icon: None,
selected_style: None,
}
}
@ -62,6 +64,13 @@ impl Selectable for ButtonIcon {
}
}
impl SelectableButton for ButtonIcon {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.selected_style = Some(style);
self
}
}
impl RenderOnce for ButtonIcon {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let icon = self
@ -71,6 +80,8 @@ impl RenderOnce for ButtonIcon {
let icon_color = if self.disabled {
Color::Disabled
} else if self.selected_style.is_some() && self.selected {
self.selected_style.unwrap().into()
} else if self.selected {
Color::Selected
} else {

View File

@ -4,6 +4,10 @@ use smallvec::SmallVec;
use crate::prelude::*;
pub trait SelectableButton: Selectable {
fn selected_style(self, style: ButtonStyle) -> Self;
}
pub trait ButtonCommon: Clickable + Disableable {
/// A unique element ID to identify the button.
fn id(&self) -> &ElementId;
@ -36,17 +40,68 @@ pub enum IconPosition {
End,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum TintColor {
#[default]
Accent,
Negative,
Warning,
}
impl TintColor {
fn button_like_style(self, cx: &mut WindowContext) -> ButtonLikeStyles {
match self {
TintColor::Accent => ButtonLikeStyles {
background: cx.theme().status().info_background,
border_color: cx.theme().status().info_border,
label_color: cx.theme().colors().text,
icon_color: cx.theme().colors().text,
},
TintColor::Negative => ButtonLikeStyles {
background: cx.theme().status().error_background,
border_color: cx.theme().status().error_border,
label_color: cx.theme().colors().text,
icon_color: cx.theme().colors().text,
},
TintColor::Warning => ButtonLikeStyles {
background: cx.theme().status().warning_background,
border_color: cx.theme().status().warning_border,
label_color: cx.theme().colors().text,
icon_color: cx.theme().colors().text,
},
}
}
}
impl From<TintColor> for Color {
fn from(tint: TintColor) -> Self {
match tint {
TintColor::Accent => Color::Accent,
TintColor::Negative => Color::Error,
TintColor::Warning => Color::Warning,
}
}
}
// Used to go from ButtonStyle -> Color through tint colors.
impl From<ButtonStyle> for Color {
fn from(style: ButtonStyle) -> Self {
match style {
ButtonStyle::Tinted(tint) => tint.into(),
_ => Color::Default,
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum ButtonStyle {
/// A filled button with a solid background color. Provides emphasis versus
/// the more common subtle button.
Filled,
/// 🚧 Under construction 🚧
///
/// Used to emphasize a button in some way, like a selected state, or a semantic
/// coloring like an error or success button.
Tinted,
Tinted(TintColor),
/// The default button style, used for most buttons. Has a transparent background,
/// but has a background color to indicate states like hover and active.
@ -86,12 +141,7 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background,
border_color: transparent_black(),
@ -115,12 +165,7 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_hover,
border_color: transparent_black(),
@ -146,12 +191,7 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_active,
border_color: transparent_black(),
@ -178,12 +218,7 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background,
border_color: cx.theme().colors().border_focused,
@ -208,12 +243,7 @@ impl ButtonStyle {
label_color: Color::Disabled.color(cx),
icon_color: Color::Disabled.color(cx),
},
ButtonStyle::Tinted => ButtonLikeStyles {
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_disabled,
border_color: cx.theme().colors().border_disabled,
@ -264,6 +294,7 @@ pub struct ButtonLike {
pub(super) style: ButtonStyle,
pub(super) disabled: bool,
pub(super) selected: bool,
pub(super) selected_style: Option<ButtonStyle>,
pub(super) width: Option<DefiniteLength>,
size: ButtonSize,
rounding: Option<ButtonLikeRounding>,
@ -280,6 +311,7 @@ impl ButtonLike {
style: ButtonStyle::default(),
disabled: false,
selected: false,
selected_style: None,
width: None,
size: ButtonSize::Default,
rounding: Some(ButtonLikeRounding::All),
@ -309,6 +341,13 @@ impl Selectable for ButtonLike {
}
}
impl SelectableButton for ButtonLike {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.selected_style = Some(style);
self
}
}
impl Clickable for ButtonLike {
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
@ -364,6 +403,11 @@ impl ParentElement for ButtonLike {
impl RenderOnce for ButtonLike {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let style = self
.selected_style
.filter(|_| self.selected)
.unwrap_or(self.style);
self.base
.h_flex()
.id(self.id.clone())
@ -382,12 +426,12 @@ impl RenderOnce for ButtonLike {
ButtonSize::Default | ButtonSize::Compact => this.px_1(),
ButtonSize::None => this,
})
.bg(self.style.enabled(cx).background)
.bg(style.enabled(cx).background)
.when(self.disabled, |this| this.cursor_not_allowed())
.when(!self.disabled, |this| {
this.cursor_pointer()
.hover(|hover| hover.bg(self.style.hovered(cx).background))
.active(|active| active.bg(self.style.active(cx).background))
.hover(|hover| hover.bg(style.hovered(cx).background))
.active(|active| active.bg(style.active(cx).background))
})
.when_some(
self.on_click.filter(|_| !self.disabled),

View File

@ -1,6 +1,6 @@
use gpui::{AnyView, DefiniteLength};
use crate::prelude::*;
use crate::{prelude::*, SelectableButton};
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
use super::button_icon::ButtonIcon;
@ -55,6 +55,13 @@ impl Selectable for IconButton {
}
}
impl SelectableButton for IconButton {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.base = self.base.selected_style(style);
self
}
}
impl Clickable for IconButton {
fn on_click(
mut self,
@ -109,12 +116,14 @@ impl RenderOnce for IconButton {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let is_disabled = self.base.disabled;
let is_selected = self.base.selected;
let selected_style = self.base.selected_style;
self.base.child(
ButtonIcon::new(self.icon)
.disabled(is_disabled)
.selected(is_selected)
.selected_icon(self.selected_icon)
.when_some(selected_style, |this, style| this.selected_style(style))
.size(self.icon_size)
.color(self.icon_color),
)

View File

@ -63,6 +63,13 @@ impl Selectable for ToggleButton {
}
}
impl SelectableButton for ToggleButton {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.base.selected_style = Some(style);
self
}
}
impl Disableable for ToggleButton {
fn disabled(mut self, disabled: bool) -> Self {
self.base = self.base.disabled(disabled);

View File

@ -1,4 +1,4 @@
use gpui::{div, prelude::*, Element, ElementId, IntoElement, Styled, WindowContext};
use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
use crate::prelude::*;
use crate::{Color, Icon, IconElement, Selection};
@ -18,126 +18,6 @@ pub struct Checkbox {
on_click: Option<CheckHandler>,
}
impl RenderOnce for Checkbox {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked {
// When selected, we show a checkmark.
Selection::Selected => {
Some(
IconElement::new(Icon::Check)
.size(crate::IconSize::Small)
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
Color::Disabled
} else {
Color::Selected
},
),
)
}
// In an indeterminate state, we show a dash.
Selection::Indeterminate => {
Some(
IconElement::new(Icon::Dash)
.size(crate::IconSize::Small)
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
Color::Disabled
} else {
Color::Selected
},
),
)
}
// When unselected, we show nothing.
Selection::Unselected => None,
};
// A checkbox could be in an indeterminate state,
// for example the indeterminate state could represent:
// - a group of options of which only some are selected
// - an enabled option that is no longer available
// - a previously agreed to license that has been updated
//
// For the sake of styles we treat the indeterminate state as selected,
// but it's icon will be different.
let selected =
self.checked == Selection::Selected || self.checked == Selection::Indeterminate;
// We could use something like this to make the checkbox background when selected:
//
// ~~~rust
// ...
// .when(selected, |this| {
// this.bg(cx.theme().colors().element_selected)
// })
// ~~~
//
// But we use a match instead here because the checkbox might be disabled,
// and it could be disabled _while_ it is selected, as well as while it is not selected.
let (bg_color, border_color) = match (self.disabled, selected) {
(true, _) => (
cx.theme().colors().ghost_element_disabled,
cx.theme().colors().border_disabled,
),
(false, true) => (
cx.theme().colors().element_selected,
cx.theme().colors().border,
),
(false, false) => (
cx.theme().colors().element_background,
cx.theme().colors().border,
),
};
div()
.id(self.id)
// Rather than adding `px_1()` to add some space around the checkbox,
// we use a larger parent element to create a slightly larger
// click area for the checkbox.
.size_5()
// Because we've enlarged the click area, we need to create a
// `group` to pass down interactivity events to the checkbox.
.group(group_id.clone())
.child(
div()
.flex()
// This prevent the flex element from growing
// or shrinking in response to any size changes
.flex_none()
// The combo of `justify_center()` and `items_center()`
// is used frequently to center elements in a flex container.
//
// We use this to center the icon in the checkbox.
.justify_center()
.items_center()
.m_1()
.size_4()
.rounded_sm()
.bg(bg_color)
.border()
.border_color(border_color)
// We only want the interactivity states to fire when we
// are in a checkbox that isn't disabled.
.when(!self.disabled, |this| {
// Here instead of `hover()` we use `group_hover()`
// to pass it the group id.
this.group_hover(group_id.clone(), |el| {
el.bg(cx.theme().colors().element_hover)
})
})
.children(icon),
)
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)),
)
}
}
impl Checkbox {
pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
Self {
@ -160,42 +40,29 @@ impl Checkbox {
self.on_click = Some(Box::new(handler));
self
}
}
pub fn render(self, cx: &mut WindowContext) -> impl Element {
impl RenderOnce for Checkbox {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked {
// When selected, we show a checkmark.
Selection::Selected => {
Some(
IconElement::new(Icon::Check)
.size(crate::IconSize::Small)
.color(
// If the checkbox is disabled we change the color of the icon.
Selection::Selected => Some(IconElement::new(Icon::Check).size(IconSize::Small).color(
if self.disabled {
Color::Disabled
} else {
Color::Selected
},
),
)
}
// In an indeterminate state, we show a dash.
Selection::Indeterminate => {
Some(
)),
Selection::Indeterminate => Some(
IconElement::new(Icon::Dash)
.size(crate::IconSize::Small)
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
.size(IconSize::Small)
.color(if self.disabled {
Color::Disabled
} else {
Color::Selected
},
}),
),
)
}
// When unselected, we show nothing.
Selection::Unselected => None,
};
@ -212,12 +79,12 @@ impl Checkbox {
// We could use something like this to make the checkbox background when selected:
//
// ~~~rust
// ```rs
// ...
// .when(selected, |this| {
// this.bg(cx.theme().colors().element_selected)
// })
// ~~~
// ```
//
// But we use a match instead here because the checkbox might be disabled,
// and it could be disabled _while_ it is selected, as well as while it is not selected.
@ -236,7 +103,7 @@ impl Checkbox {
),
};
div()
h_stack()
.id(self.id)
// Rather than adding `px_1()` to add some space around the checkbox,
// we use a larger parent element to create a slightly larger

View File

@ -12,7 +12,7 @@ pub use crate::selectable::*;
pub use crate::styles::{vh, vw};
pub use crate::visible_on_hover::*;
pub use crate::{h_stack, v_stack};
pub use crate::{Button, ButtonSize, ButtonStyle, IconButton};
pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};
pub use crate::{ButtonCommon, Color, StyledExt};
pub use crate::{Icon, IconElement, IconPosition, IconSize};
pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle};

View File

@ -59,7 +59,11 @@ pub struct WelcomePage {
impl Render for WelcomePage {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
h_stack().full().track_focus(&self.focus_handle).child(
h_stack()
.full()
.bg(cx.theme().colors().editor_background)
.track_focus(&self.focus_handle)
.child(
v_stack()
.w_96()
.gap_4()
@ -115,9 +119,9 @@ impl Render for WelcomePage {
.full_width()
.on_click(cx.listener(|_, _, cx| {
cx.app_mut()
.spawn(
|cx| async move { install_cli::install_cli(&cx).await },
)
.spawn(|cx| async move {
install_cli::install_cli(&cx).await
})
.detach_and_log_err(cx);
})),
),
@ -142,15 +146,15 @@ impl Render for WelcomePage {
ui::Selection::Unselected
},
)
.on_click(cx.listener(
move |this, selection, cx| {
.on_click(
cx.listener(move |this, selection, cx| {
this.update_settings::<VimModeSetting>(
selection,
cx,
|setting, value| *setting = Some(value),
);
},
)),
}),
),
)
.child(Label::new("Enable vim mode")),
)
@ -166,15 +170,17 @@ impl Render for WelcomePage {
ui::Selection::Unselected
},
)
.on_click(cx.listener(
move |this, selection, cx| {
.on_click(
cx.listener(move |this, selection, cx| {
this.update_settings::<TelemetrySettings>(
selection,
cx,
|settings, value| settings.metrics = Some(value),
);
|settings, value| {
settings.metrics = Some(value)
},
)),
);
}),
),
)
.child(Label::new("Send anonymous usage data")),
)
@ -190,8 +196,8 @@ impl Render for WelcomePage {
ui::Selection::Unselected
},
)
.on_click(cx.listener(
move |this, selection, cx| {
.on_click(
cx.listener(move |this, selection, cx| {
this.update_settings::<TelemetrySettings>(
selection,
cx,
@ -199,8 +205,8 @@ impl Render for WelcomePage {
settings.diagnostics = Some(value)
},
);
},
)),
}),
),
)
.child(Label::new("Send crash reports")),
),

View File

@ -32,6 +32,7 @@ language = { path = "../language" }
node_runtime = { path = "../node_runtime" }
project = { path = "../project" }
settings = { path = "../settings" }
sqlez = { path = "../sqlez" }
terminal = { path = "../terminal" }
theme = { path = "../theme" }
util = { path = "../util" }

View File

@ -672,9 +672,13 @@ impl Render for PanelButtons {
&& panel.position_is_valid(position, cx)
{
let panel = panel.clone();
menu = menu.entry(position.to_label(), None, move |cx| {
menu = menu.entry(
format!("Dock {}", position.to_label()),
None,
move |cx| {
panel.set_position(position, cx);
})
},
)
}
}
menu

View File

@ -246,7 +246,15 @@ impl Member {
.size_full()
.child(pane.clone())
.when_some(leader_border, |this, color| {
this.border_2().border_color(color)
this.child(
div()
.absolute()
.size_full()
.left_0()
.top_0()
.border_2()
.border_color(color),
)
})
.when_some(leader_status_box, |this, status_box| {
this.child(

View File

@ -6,7 +6,12 @@ use std::path::Path;
use anyhow::{anyhow, bail, Context, Result};
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::{Axis, WindowBounds};
use gpui::{point, size, Axis, Bounds, WindowBounds};
use sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
use util::{unzip_option, ResultExt};
use uuid::Uuid;
@ -20,6 +25,121 @@ use model::{
use self::model::DockStructure;
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) struct SerializedAxis(pub(crate) gpui::Axis);
impl sqlez::bindable::StaticColumnCount for SerializedAxis {}
impl sqlez::bindable::Bind for SerializedAxis {
fn bind(
&self,
statement: &sqlez::statement::Statement,
start_index: i32,
) -> anyhow::Result<i32> {
match self.0 {
gpui::Axis::Horizontal => "Horizontal",
gpui::Axis::Vertical => "Vertical",
}
.bind(statement, start_index)
}
}
impl sqlez::bindable::Column for SerializedAxis {
fn column(
statement: &mut sqlez::statement::Statement,
start_index: i32,
) -> anyhow::Result<(Self, i32)> {
String::column(statement, start_index).and_then(|(axis_text, next_index)| {
Ok((
match axis_text.as_str() {
"Horizontal" => Self(Axis::Horizontal),
"Vertical" => Self(Axis::Vertical),
_ => anyhow::bail!("Stored serialized item kind is incorrect"),
},
next_index,
))
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct SerializedWindowsBounds(pub(crate) WindowBounds);
impl StaticColumnCount for SerializedWindowsBounds {
fn column_count() -> usize {
5
}
}
impl Bind for SerializedWindowsBounds {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let (region, next_index) = match self.0 {
WindowBounds::Fullscreen => {
let next_index = statement.bind(&"Fullscreen", start_index)?;
(None, next_index)
}
WindowBounds::Maximized => {
let next_index = statement.bind(&"Maximized", start_index)?;
(None, next_index)
}
WindowBounds::Fixed(region) => {
let next_index = statement.bind(&"Fixed", start_index)?;
(Some(region), next_index)
}
};
statement.bind(
&region.map(|region| {
(
SerializedGlobalPixels(region.origin.x),
SerializedGlobalPixels(region.origin.y),
SerializedGlobalPixels(region.size.width),
SerializedGlobalPixels(region.size.height),
)
}),
next_index,
)
}
}
impl Column for SerializedWindowsBounds {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (window_state, next_index) = String::column(statement, start_index)?;
let bounds = match window_state.as_str() {
"Fullscreen" => SerializedWindowsBounds(WindowBounds::Fullscreen),
"Maximized" => SerializedWindowsBounds(WindowBounds::Maximized),
"Fixed" => {
let ((x, y, width, height), _) = Column::column(statement, next_index)?;
let x: f64 = x;
let y: f64 = y;
let width: f64 = width;
let height: f64 = height;
SerializedWindowsBounds(WindowBounds::Fixed(Bounds {
origin: point(x.into(), y.into()),
size: size(width.into(), height.into()),
}))
}
_ => bail!("Window State did not have a valid string"),
};
Ok((bounds, next_index + 4))
}
}
#[derive(Clone, Debug, PartialEq)]
struct SerializedGlobalPixels(gpui::GlobalPixels);
impl sqlez::bindable::StaticColumnCount for SerializedGlobalPixels {}
impl sqlez::bindable::Bind for SerializedGlobalPixels {
fn bind(
&self,
statement: &sqlez::statement::Statement,
start_index: i32,
) -> anyhow::Result<i32> {
let this: f64 = self.0.into();
let this: f32 = this as _;
this.bind(statement, start_index)
}
}
define_connection! {
// Current schema shape using pseudo-rust syntax:
//
@ -181,7 +301,7 @@ impl WorkspaceDb {
/// Returns a serialized workspace for the given worktree_roots. If the passed array
/// is empty, the most recent workspace is returned instead. If no workspace for the
/// passed roots is stored, returns none.
pub fn workspace_for_roots<P: AsRef<Path>>(
pub(crate) fn workspace_for_roots<P: AsRef<Path>>(
&self,
worktree_roots: &[P],
) -> Option<SerializedWorkspace> {
@ -192,7 +312,7 @@ impl WorkspaceDb {
let (workspace_id, workspace_location, bounds, display, docks): (
WorkspaceId,
WorkspaceLocation,
Option<WindowBounds>,
Option<SerializedWindowsBounds>,
Option<Uuid>,
DockStructure,
) = self
@ -230,7 +350,7 @@ impl WorkspaceDb {
.get_center_pane_group(workspace_id)
.context("Getting center group")
.log_err()?,
bounds,
bounds: bounds.map(|bounds| bounds.0),
display,
docks,
})
@ -238,7 +358,7 @@ impl WorkspaceDb {
/// Saves a workspace using the worktree roots. Will garbage collect any workspaces
/// that used this workspace previously
pub async fn save_workspace(&self, workspace: SerializedWorkspace) {
pub(crate) async fn save_workspace(&self, workspace: SerializedWorkspace) {
self.write(move |conn| {
conn.with_savepoint("update_worktrees", || {
// Clear out panes and pane_groups
@ -367,7 +487,7 @@ impl WorkspaceDb {
type GroupKey = (Option<GroupId>, WorkspaceId);
type GroupOrPane = (
Option<GroupId>,
Option<Axis>,
Option<SerializedAxis>,
Option<PaneId>,
Option<bool>,
Option<String>,
@ -536,7 +656,7 @@ impl WorkspaceDb {
}
query! {
pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> {
pub(crate) async fn set_window_bounds(workspace_id: WorkspaceId, bounds: SerializedWindowsBounds, display: Uuid) -> Result<()> {
UPDATE workspaces
SET window_state = ?2,
window_x = ?3,
@ -683,7 +803,7 @@ mod tests {
fn group(axis: Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
SerializedPaneGroup::Group {
axis,
axis: SerializedAxis(axis),
flexes: None,
children,
}

View File

@ -1,3 +1,4 @@
use super::SerializedAxis;
use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
use anyhow::{Context, Result};
use async_recursion::async_recursion;
@ -5,7 +6,7 @@ use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView, WindowBounds};
use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
use project::Project;
use std::{
path::{Path, PathBuf},
@ -54,13 +55,13 @@ impl Column for WorkspaceLocation {
}
#[derive(Debug, PartialEq, Clone)]
pub struct SerializedWorkspace {
pub id: WorkspaceId,
pub location: WorkspaceLocation,
pub center_group: SerializedPaneGroup,
pub bounds: Option<WindowBounds>,
pub display: Option<Uuid>,
pub docks: DockStructure,
pub(crate) struct SerializedWorkspace {
pub(crate) id: WorkspaceId,
pub(crate) location: WorkspaceLocation,
pub(crate) center_group: SerializedPaneGroup,
pub(crate) bounds: Option<WindowBounds>,
pub(crate) display: Option<Uuid>,
pub(crate) docks: DockStructure,
}
#[derive(Debug, PartialEq, Clone, Default)]
@ -126,9 +127,9 @@ impl Bind for DockData {
}
#[derive(Debug, PartialEq, Clone)]
pub enum SerializedPaneGroup {
pub(crate) enum SerializedPaneGroup {
Group {
axis: Axis,
axis: SerializedAxis,
flexes: Option<Vec<f32>>,
children: Vec<SerializedPaneGroup>,
},
@ -183,7 +184,7 @@ impl SerializedPaneGroup {
}
Some((
Member::Axis(PaneAxis::load(axis, members, flexes)),
Member::Axis(PaneAxis::load(axis.0, members, flexes)),
current_active_pane,
items,
))

View File

@ -42,9 +42,9 @@ use node_runtime::NodeRuntime;
use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
pub use pane::*;
pub use pane_group::*;
use persistence::DB;
use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB};
pub use persistence::{
model::{ItemId, SerializedWorkspace, WorkspaceLocation},
model::{ItemId, WorkspaceLocation},
WorkspaceDb, DB as WORKSPACE_DB,
};
use postage::stream::Stream;
@ -70,8 +70,9 @@ use util::ResultExt;
use uuid::Uuid;
pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
use crate::persistence::model::{
DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
use crate::persistence::{
model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup},
SerializedAxis,
};
lazy_static! {
@ -107,6 +108,7 @@ actions!(
NewCenterTerminal,
ToggleTerminalFocus,
NewSearch,
DeploySearch,
Feedback,
Restart,
Welcome,
@ -624,7 +626,11 @@ impl Workspace {
if let Some(display_uuid) = display.uuid().log_err() {
cx.background_executor()
.spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
.spawn(DB.set_window_bounds(
workspace_id,
SerializedWindowsBounds(bounds),
display_uuid,
))
.detach_and_log_err(cx);
}
}
@ -1186,7 +1192,7 @@ impl Workspace {
mut save_intent: SaveIntent,
cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> {
if self.project.read(cx).is_read_only() {
if self.project.read(cx).is_disconnected() {
return Task::ready(Ok(true));
}
let dirty_items = self
@ -2509,7 +2515,7 @@ impl Workspace {
}
fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
let is_edited = !self.project.read(cx).is_read_only()
let is_edited = !self.project.read(cx).is_disconnected()
&& self
.items(cx)
.any(|item| item.has_conflict(cx) || item.is_dirty(cx));
@ -2988,7 +2994,7 @@ impl Workspace {
flexes,
bounding_boxes: _,
}) => SerializedPaneGroup::Group {
axis: *axis,
axis: SerializedAxis(*axis),
children: members
.iter()
.map(|member| build_serialized_pane_group(member, cx))
@ -3265,6 +3271,7 @@ impl Workspace {
let user_store = project.read(cx).user_store();
let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
cx.activate_window();
let app_state = Arc::new(AppState {
languages: project.read(cx).languages().clone(),
workspace_store,
@ -3633,7 +3640,7 @@ impl Render for Workspace {
})),
)
.child(self.status_bar.clone())
.children(if self.project.read(cx).is_read_only() {
.children(if self.project.read(cx).is_disconnected() {
Some(DisconnectedOverlay)
} else {
None
@ -4763,8 +4770,7 @@ mod tests {
});
// Deactivating the window saves the file.
cx.simulate_deactivation();
cx.executor().run_until_parked();
cx.deactivate_window();
item.update(cx, |item, _| assert_eq!(item.save_count, 1));
// Autosave on focus change.
@ -4784,14 +4790,13 @@ mod tests {
item.update(cx, |item, _| assert_eq!(item.save_count, 2));
// Deactivating the window still saves the file.
cx.simulate_activation();
cx.update(|cx| cx.activate_window());
item.update(cx, |item, cx| {
cx.focus_self();
item.is_dirty = true;
});
cx.simulate_deactivation();
cx.deactivate_window();
cx.executor().run_until_parked();
item.update(cx, |item, _| assert_eq!(item.save_count, 3));
// Autosave after delay.

View File

@ -1,29 +0,0 @@
[⬅ Back to Index](../index.md)
# Generating Theme Types
## How to generate theme types:
Run a script
```bash
./script/build-theme-types
```
Types are generated in `styles/src/types/zed.ts`
## How it works:
1. Rust types
The `crates/theme` contains theme types.
Crate `schemars` used to generate a JSON schema from the theme structs.
Every struct that represent theme type has a `#[derive(JsonSchema)]` attribute.
Task lotaked at `crates/xtask/src/main.rs` generates a JSON schema from the theme structs.
2. TypeScript types
Script `npm run build-types` from `styles` package generates TypeScript types from the JSON schema and saves them to `styles/src/types/zed.ts`.

View File

@ -4,17 +4,12 @@ set -eu
source script/lib/deploy-helpers.sh
if [[ $# < 2 ]]; then
echo "Usage: $0 <production|staging|preview> <tag-name> (nightly is not yet supported)"
echo "Usage: $0 <production|staging|preview|nightly> <tag-name>"
exit 1
fi
environment=$1
version=$2
if [[ ${environment} == "nightly" ]]; then
echo "nightly is not yet supported"
exit 1
fi
export_vars_for_environment ${environment}
image_id=$(image_id_for_version ${version})

View File

@ -9,10 +9,7 @@ OUTPUT_FILE=$(pwd)/assets/licenses.md
echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE
echo "Generating theme licenses"
cd styles
npm --silent ci
npm run --silent build-licenses >> $OUTPUT_FILE
cd ..
cat crates/theme/src/themes/LICENSES >> $OUTPUT_FILE
echo -e "# ###### CODE LICENSES ######\n" >> $OUTPUT_FILE

View File

@ -1,34 +0,0 @@
module.exports = {
env: {
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["@typescript-eslint", "import"],
globals: {
module: true,
},
settings: {
"import/parsers": {
"@typescript-eslint/parser": [".ts"],
},
"import/resolver": {
typescript: true,
node: true,
},
"import/extensions": [".ts"],
},
rules: {
"linebreak-style": ["error", "unix"],
"@typescript-eslint/no-explicit-any": "off",
semi: ["error", "never"],
},
}

2
styles/.gitignore vendored
View File

@ -1,2 +0,0 @@
node_modules/
coverage/

View File

@ -1,3 +0,0 @@
package-lock.json
package.json
target

View File

@ -1,6 +0,0 @@
{
"semi": false,
"printWidth": 80,
"htmlWhitespaceSensitivity": "strict",
"tabWidth": 4
}

Some files were not shown because too many files have changed in this diff Show More