Compare commits

..

3 Commits

Author SHA1 Message Date
Max Brunsfeld
c58605a70c Merge pull request #2203 from zed-industries/collab-ui-fixes
Fix minor issues with new collab UI
2023-02-22 14:22:47 -08:00
Max Brunsfeld
995dc195f6 Bump RPC protocol version number 2023-02-22 13:40:46 -08:00
Max Brunsfeld
d7f0c0f43a v0.75.x preview 2023-02-22 12:34:15 -08:00
160 changed files with 4914 additions and 7486 deletions

View File

@@ -19,9 +19,7 @@ env:
jobs:
rustfmt:
name: Check formatting
runs-on:
- self-hosted
- test
runs-on: self-hosted
steps:
- name: Install Rust
run: |

8
Cargo.lock generated
View File

@@ -794,7 +794,7 @@ dependencies = [
[[package]]
name = "bromberg_sl2"
version = "0.6.0"
source = "git+https://github.com/zed-industries/bromberg_sl2?rev=950bc5482c216c395049ae33ae4501e08975f17f#950bc5482c216c395049ae33ae4501e08975f17f"
source = "git+https://github.com/zed-industries/bromberg_sl2?rev=dac565a90e8f9245f48ff46225c915dc50f76920#dac565a90e8f9245f48ff46225c915dc50f76920"
dependencies = [
"digest 0.9.0",
"lazy_static",
@@ -1188,7 +1188,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.6.1"
version = "0.5.4"
dependencies = [
"anyhow",
"async-tungstenite",
@@ -1257,7 +1257,6 @@ dependencies = [
"client",
"clock",
"collections",
"context_menu",
"editor",
"futures 0.3.25",
"fuzzy",
@@ -4592,7 +4591,6 @@ dependencies = [
"lsp",
"parking_lot 0.11.2",
"postage",
"pretty_assertions",
"pulldown-cmark",
"rand 0.8.5",
"regex",
@@ -8358,7 +8356,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zed"
version = "0.76.0"
version = "0.75.0"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -23,18 +23,10 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
git clone https://github.com/zed-industries/zed.dev
```
* Initialize submodules
```
git submodule update --init --recursive
```
* Set up a local `zed` database and seed it with some initial users:
Create a personal GitHub token to run `script/bootstrap` once successfully. Then delete that token.
```
GITHUB_TOKEN=<$token> script/bootstrap
script/bootstrap
```
### Testing against locally-running servers

View File

@@ -1,3 +0,0 @@
<svg width="14" height="4" viewBox="0 0 14 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 2C3.125 2.62132 2.62132 3.125 2 3.125C1.37868 3.125 0.875 2.62132 0.875 2C0.875 1.37868 1.37868 0.875 2 0.875C2.62132 0.875 3.125 1.37868 3.125 2ZM8.125 2C8.125 2.62132 7.62132 3.125 7 3.125C6.37868 3.125 5.875 2.62132 5.875 2C5.875 1.37868 6.37868 0.875 7 0.875C7.62132 0.875 8.125 1.37868 8.125 2ZM12 3.125C12.6213 3.125 13.125 2.62132 13.125 2C13.125 1.37868 12.6213 0.875 12 0.875C11.3787 0.875 10.875 1.37868 10.875 2C10.875 2.62132 11.3787 3.125 12 3.125Z" fill="#ABB2BF"/>
</svg>

Before

Width:  |  Height:  |  Size: 637 B

View File

@@ -228,7 +228,6 @@
"replace_newest": true
}
],
"cmd-k cmd-i": "editor::Hover",
"cmd-/": [
"editor::ToggleComments",
{
@@ -457,7 +456,7 @@
}
},
{
"context": "Pane && docked",
"context": "Dock",
"bindings": {
"shift-escape": "dock::HideDock",
"cmd-escape": "dock::RemoveTabFromDock"

View File

@@ -27,7 +27,6 @@
"h": "vim::Left",
"backspace": "vim::Backspace",
"j": "vim::Down",
"enter": "vim::NextLineStart",
"k": "vim::Up",
"l": "vim::Right",
"$": "vim::EndOfLine",
@@ -234,8 +233,7 @@
"escape": [
"vim::SwitchMode",
"Normal"
],
"d": "editor::GoToDefinition"
]
}
},
{

View File

@@ -51,12 +51,6 @@
// 3. Position the dock full screen over the entire workspace"
// "default_dock_anchor": "expanded"
"default_dock_anchor": "right",
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
"remove_trailing_whitespace_on_save": true,
// Whether or not to ensure there's a single newline at the end of a buffer
// when saving it.
"ensure_final_newline_on_save": true,
// Whether or not to perform a buffer format before saving
"format_on_save": "on",
// How to perform a buffer format. This setting can take two values:
@@ -89,7 +83,7 @@
"hard_tabs": false,
// How many columns a tab should occupy.
"tab_size": 4,
// Control what info is collected by Zed.
// Control what info Zed sends to our servers
"telemetry": {
// Send debug info like crash reports.
"diagnostics": true,

View File

@@ -20,7 +20,7 @@ use project::Project;
use std::{mem, sync::Arc, time::Duration};
use util::{post_inc, ResultExt, TryFutureExt};
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub const RECONNECT_TIMEOUT: Duration = client::RECEIVE_TIMEOUT;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
@@ -55,7 +55,7 @@ pub struct Room {
leave_when_empty: bool,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
follows_by_leader_id: HashMap<PeerId, Vec<PeerId>>,
subscriptions: Vec<client::Subscription>,
pending_room_update: Option<Task<()>>,
maintain_connection: Option<Task<Option<()>>>,
@@ -149,7 +149,7 @@ impl Room {
pending_room_update: None,
client,
user_store,
follows_by_leader_id_project_id: Default::default(),
follows_by_leader_id: Default::default(),
maintain_connection: Some(maintain_connection),
}
}
@@ -277,12 +277,14 @@ impl Room {
) -> Result<()> {
let mut client_status = client.status();
loop {
let _ = client_status.try_recv();
let is_connected = client_status.borrow().is_connected();
let is_connected = client_status
.next()
.await
.map_or(false, |s| s.is_connected());
// Even if we're initially connected, any future change of the status means we momentarily disconnected.
if !is_connected || client_status.next().await.is_some() {
log::info!("detected client disconnection");
this.upgrade(&cx)
.ok_or_else(|| anyhow!("room was dropped"))?
.update(&mut cx, |this, cx| {
@@ -296,7 +298,12 @@ impl Room {
let client_reconnection = async {
let mut remaining_attempts = 3;
while remaining_attempts > 0 {
if client_status.borrow().is_connected() {
log::info!(
"waiting for client status change, remaining attempts {}",
remaining_attempts
);
let Some(status) = client_status.next().await else { break };
if status.is_connected() {
log::info!("client reconnected, attempting to rejoin room");
let Some(this) = this.upgrade(&cx) else { break };
@@ -310,15 +317,7 @@ impl Room {
} else {
remaining_attempts -= 1;
}
} else if client_status.borrow().is_signed_out() {
return false;
}
log::info!(
"waiting for client status change, remaining attempts {}",
remaining_attempts
);
client_status.next().await;
}
false
}
@@ -340,20 +339,18 @@ impl Room {
}
}
break;
// The client failed to re-establish a connection to the server
// or an error occurred while trying to re-join the room. Either way
// we leave the room and return an error.
if let Some(this) = this.upgrade(&cx) {
log::info!("reconnection failed, leaving room");
let _ = this.update(&mut cx, |this, cx| this.leave(cx));
}
return Err(anyhow!(
"can't reconnect to room: client failed to re-establish connection"
));
}
}
// The client failed to re-establish a connection to the server
// or an error occurred while trying to re-join the room. Either way
// we leave the room and return an error.
if let Some(this) = this.upgrade(&cx) {
log::info!("reconnection failed, leaving room");
let _ = this.update(&mut cx, |this, cx| this.leave(cx));
}
Err(anyhow!(
"can't reconnect to room: client failed to re-establish connection"
))
}
fn rejoin(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
@@ -462,9 +459,9 @@ impl Room {
self.participant_user_ids.contains(&user_id)
}
pub fn followers_for(&self, leader_id: PeerId, project_id: u64) -> &[PeerId] {
self.follows_by_leader_id_project_id
.get(&(leader_id, project_id))
pub fn followers_for(&self, leader_id: PeerId) -> &[PeerId] {
self.follows_by_leader_id
.get(&leader_id)
.map_or(&[], |v| v.as_slice())
}
@@ -634,9 +631,8 @@ impl Room {
}
}
this.follows_by_leader_id_project_id.clear();
this.follows_by_leader_id.clear();
for follower in room.followers {
let project_id = follower.project_id;
let (leader, follower) = match (follower.leader_id, follower.follower_id) {
(Some(leader), Some(follower)) => (leader, follower),
@@ -647,8 +643,8 @@ impl Room {
};
let list = this
.follows_by_leader_id_project_id
.entry((leader, project_id))
.follows_by_leader_id
.entry(leader)
.or_insert(Vec::new());
if !list.contains(&follower) {
list.push(follower);

View File

@@ -66,7 +66,7 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
actions!(client, [Authenticate, SignOut]);
actions!(client, [Authenticate]);
pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
cx.add_global_action({
@@ -79,16 +79,6 @@ pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
.detach();
}
});
cx.add_global_action({
let client = client.clone();
move |_: &SignOut, cx| {
let client = client.clone();
cx.spawn(|cx| async move {
client.disconnect(&cx);
})
.detach();
}
});
}
pub struct Client {
@@ -179,10 +169,6 @@ impl Status {
pub fn is_connected(&self) -> bool {
matches!(self, Self::Connected { .. })
}
pub fn is_signed_out(&self) -> bool {
matches!(self, Self::SignedOut | Self::UpgradeRequired)
}
}
struct ClientState {
@@ -1166,9 +1152,11 @@ impl Client {
})
}
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) {
self.peer.teardown();
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
let conn_id = self.connection_id()?;
self.peer.disconnect(conn_id);
self.set_status(Status::SignedOut, cx);
Ok(())
}
fn connection_id(&self) -> Result<ConnectionId> {

View File

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

View File

@@ -158,7 +158,7 @@ impl Database {
room_id: RoomId,
new_server_id: ServerId,
) -> Result<RoomGuard<RefreshedRoom>> {
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let stale_participant_filter = Condition::all()
.add(room_participant::Column::RoomId.eq(room_id))
.add(room_participant::Column::AnsweringConnectionId.is_not_null())
@@ -191,18 +191,17 @@ impl Database {
.filter(room_participant::Column::RoomId.eq(room_id))
.exec(&*tx)
.await?;
project::Entity::delete_many()
.filter(project::Column::RoomId.eq(room_id))
.exec(&*tx)
.await?;
room::Entity::delete_by_id(room_id).exec(&*tx).await?;
}
Ok(RefreshedRoom {
room,
stale_participant_user_ids,
canceled_calls_to_user_ids,
})
Ok((
room_id,
RefreshedRoom {
room,
stale_participant_user_ids,
canceled_calls_to_user_ids,
},
))
})
.await
}
@@ -1131,16 +1130,18 @@ impl Database {
user_id: UserId,
connection: ConnectionId,
live_kit_room: &str,
) -> Result<proto::Room> {
self.transaction(|tx| async move {
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(|tx| async move {
let room = room::ActiveModel {
live_kit_room: ActiveValue::set(live_kit_room.into()),
..Default::default()
}
.insert(&*tx)
.await?;
let room_id = room.id;
room_participant::ActiveModel {
room_id: ActiveValue::set(room.id),
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(user_id),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_server_id: ActiveValue::set(Some(ServerId(
@@ -1157,8 +1158,8 @@ impl Database {
.insert(&*tx)
.await?;
let room = self.get_room(room.id, &tx).await?;
Ok(room)
let room = self.get_room(room_id, &tx).await?;
Ok((room_id, room))
})
.await
}
@@ -1171,7 +1172,7 @@ impl Database {
called_user_id: UserId,
initial_project_id: Option<ProjectId>,
) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
room_participant::ActiveModel {
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(called_user_id),
@@ -1190,7 +1191,7 @@ impl Database {
let room = self.get_room(room_id, &tx).await?;
let incoming_call = Self::build_incoming_call(&room, called_user_id)
.ok_or_else(|| anyhow!("failed to build incoming call"))?;
Ok((room, incoming_call))
Ok((room_id, (room, incoming_call)))
})
.await
}
@@ -1200,7 +1201,7 @@ impl Database {
room_id: RoomId,
called_user_id: UserId,
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
room_participant::Entity::delete_many()
.filter(
room_participant::Column::RoomId
@@ -1210,7 +1211,7 @@ impl Database {
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
Ok(room)
Ok((room_id, room))
})
.await
}
@@ -1257,7 +1258,7 @@ impl Database {
calling_connection: ConnectionId,
called_user_id: UserId,
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(
Condition::all()
@@ -1276,13 +1277,14 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no call to cancel"))?;
let room_id = participant.room_id;
room_participant::Entity::delete(participant.into_active_model())
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
Ok(room)
Ok((room_id, room))
})
.await
}
@@ -1293,7 +1295,7 @@ impl Database {
user_id: UserId,
connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let result = room_participant::Entity::update_many()
.filter(
Condition::all()
@@ -1315,7 +1317,7 @@ impl Database {
Err(anyhow!("room does not exist or was already joined"))?
} else {
let room = self.get_room(room_id, &tx).await?;
Ok(room)
Ok((room_id, room))
}
})
.await
@@ -1327,9 +1329,9 @@ impl Database {
user_id: UserId,
connection: ConnectionId,
) -> Result<RoomGuard<RejoinedRoom>> {
let room_id = RoomId::from_proto(rejoin_room.id);
self.room_transaction(room_id, |tx| async {
self.room_transaction(|tx| async {
let tx = tx;
let room_id = RoomId::from_proto(rejoin_room.id);
let participant_update = room_participant::Entity::update_many()
.filter(
Condition::all()
@@ -1548,11 +1550,14 @@ impl Database {
}
let room = self.get_room(room_id, &tx).await?;
Ok(RejoinedRoom {
room,
rejoined_projects,
reshared_projects,
})
Ok((
room_id,
RejoinedRoom {
room,
rejoined_projects,
reshared_projects,
},
))
})
.await
}
@@ -1719,8 +1724,8 @@ impl Database {
leader_connection: ConnectionId,
follower_connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let room_id = self.room_id_for_project(project_id, &*tx).await?;
follower::ActiveModel {
room_id: ActiveValue::set(room_id),
project_id: ActiveValue::set(project_id),
@@ -1737,8 +1742,7 @@ impl Database {
.insert(&*tx)
.await?;
let room = self.get_room(room_id, &*tx).await?;
Ok(room)
Ok((room_id, self.get_room(room_id, &*tx).await?))
})
.await
}
@@ -1749,39 +1753,60 @@ impl Database {
leader_connection: ConnectionId,
follower_connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let room_id = self.room_id_for_project(project_id, &*tx).await?;
follower::Entity::delete_many()
.filter(
Condition::all()
.add(follower::Column::ProjectId.eq(project_id))
.add(
follower::Column::LeaderConnectionServerId
.eq(leader_connection.owner_id),
.eq(leader_connection.owner_id)
.and(follower::Column::LeaderConnectionId.eq(leader_connection.id)),
)
.add(follower::Column::LeaderConnectionId.eq(leader_connection.id))
.add(
follower::Column::FollowerConnectionServerId
.eq(follower_connection.owner_id),
)
.add(follower::Column::FollowerConnectionId.eq(follower_connection.id)),
.eq(follower_connection.owner_id)
.and(
follower::Column::FollowerConnectionId
.eq(follower_connection.id),
),
),
)
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &*tx).await?;
Ok(room)
Ok((room_id, self.get_room(room_id, &*tx).await?))
})
.await
}
async fn room_id_for_project(
&self,
project_id: ProjectId,
tx: &DatabaseTransaction,
) -> Result<RoomId> {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
RoomId,
}
Ok(project::Entity::find_by_id(project_id)
.select_only()
.column(project::Column::RoomId)
.into_values::<_, QueryAs>()
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?)
}
pub async fn update_room_participant_location(
&self,
room_id: RoomId,
connection: ConnectionId,
location: proto::ParticipantLocation,
) -> Result<RoomGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async {
self.room_transaction(|tx| async {
let tx = tx;
let location_kind;
let location_project_id;
@@ -1827,7 +1852,7 @@ impl Database {
if result.rows_affected == 1 {
let room = self.get_room(room_id, &tx).await?;
Ok(room)
Ok((room_id, room))
} else {
Err(anyhow!("could not update room participant location"))?
}
@@ -1993,7 +2018,6 @@ impl Database {
followers.push(proto::Follower {
leader_id: Some(db_follower.leader_connection().into()),
follower_id: Some(db_follower.follower_connection().into()),
project_id: db_follower.project_id.to_proto(),
});
}
@@ -2034,7 +2058,7 @@ impl Database {
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(ProjectId, proto::Room)>> {
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(
Condition::all()
@@ -2095,7 +2119,7 @@ impl Database {
.await?;
let room = self.get_room(room_id, &tx).await?;
Ok((project.id, room))
Ok((room_id, (project.id, room)))
})
.await
}
@@ -2105,8 +2129,7 @@ impl Database {
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
let project = project::Entity::find_by_id(project_id)
@@ -2114,11 +2137,12 @@ impl Database {
.await?
.ok_or_else(|| anyhow!("project not found"))?;
if project.host_connection()? == connection {
let room_id = project.room_id;
project::Entity::delete(project.into_active_model())
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
Ok((room, guest_connection_ids))
Ok((room_id, (room, guest_connection_ids)))
} else {
Err(anyhow!("cannot unshare a project hosted by another user"))?
}
@@ -2132,8 +2156,7 @@ impl Database {
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
.filter(
Condition::all()
@@ -2151,7 +2174,7 @@ impl Database {
let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?;
let room = self.get_room(project.room_id, &tx).await?;
Ok((room, guest_connection_ids))
Ok((project.room_id, (room, guest_connection_ids)))
})
.await
}
@@ -2196,12 +2219,12 @@ impl Database {
update: &proto::UpdateWorktree,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id);
let worktree_id = update.worktree_id as i64;
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let project_id = ProjectId::from_proto(update.project_id);
let worktree_id = update.worktree_id as i64;
// Ensure the update comes from the host.
let _project = project::Entity::find_by_id(project_id)
let project = project::Entity::find_by_id(project_id)
.filter(
Condition::all()
.add(project::Column::HostConnectionId.eq(connection.id as i32))
@@ -2212,6 +2235,7 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let room_id = project.room_id;
// Update metadata.
worktree::Entity::update(worktree::ActiveModel {
@@ -2291,7 +2315,7 @@ impl Database {
}
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
Ok(connection_ids)
Ok((room_id, connection_ids))
})
.await
}
@@ -2301,10 +2325,9 @@ impl Database {
update: &proto::UpdateDiagnosticSummary,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id);
let worktree_id = update.worktree_id as i64;
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let project_id = ProjectId::from_proto(update.project_id);
let worktree_id = update.worktree_id as i64;
let summary = update
.summary
.as_ref()
@@ -2346,7 +2369,7 @@ impl Database {
.await?;
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
Ok(connection_ids)
Ok((project.room_id, connection_ids))
})
.await
}
@@ -2356,9 +2379,8 @@ impl Database {
update: &proto::StartLanguageServer,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id);
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let project_id = ProjectId::from_proto(update.project_id);
let server = update
.server
.as_ref()
@@ -2392,7 +2414,7 @@ impl Database {
.await?;
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
Ok(connection_ids)
Ok((project.room_id, connection_ids))
})
.await
}
@@ -2402,8 +2424,7 @@ impl Database {
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<(Project, ReplicaId)>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(
Condition::all()
@@ -2529,6 +2550,7 @@ impl Database {
.all(&*tx)
.await?;
let room_id = project.room_id;
let project = Project {
collaborators: collaborators
.into_iter()
@@ -2548,7 +2570,7 @@ impl Database {
})
.collect(),
};
Ok((project, replica_id as ReplicaId))
Ok((room_id, (project, replica_id as ReplicaId)))
})
.await
}
@@ -2557,9 +2579,8 @@ impl Database {
&self,
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<(proto::Room, LeftProject)>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
) -> Result<RoomGuard<LeftProject>> {
self.room_transaction(|tx| async move {
let result = project_collaborator::Entity::delete_many()
.filter(
Condition::all()
@@ -2589,39 +2610,13 @@ impl Database {
.map(|collaborator| collaborator.connection())
.collect();
follower::Entity::delete_many()
.filter(
Condition::any()
.add(
Condition::all()
.add(follower::Column::ProjectId.eq(project_id))
.add(
follower::Column::LeaderConnectionServerId
.eq(connection.owner_id),
)
.add(follower::Column::LeaderConnectionId.eq(connection.id)),
)
.add(
Condition::all()
.add(follower::Column::ProjectId.eq(project_id))
.add(
follower::Column::FollowerConnectionServerId
.eq(connection.owner_id),
)
.add(follower::Column::FollowerConnectionId.eq(connection.id)),
),
)
.exec(&*tx)
.await?;
let room = self.get_room(project.room_id, &tx).await?;
let left_project = LeftProject {
id: project_id,
host_user_id: project.host_user_id,
host_connection_id: project.host_connection()?,
connection_ids,
};
Ok((room, left_project))
Ok((project.room_id, left_project))
})
.await
}
@@ -2631,8 +2626,11 @@ impl Database {
project_id: ProjectId,
connection_id: ConnectionId,
) -> Result<RoomGuard<Vec<ProjectCollaborator>>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let collaborators = project_collaborator::Entity::find()
.filter(project_collaborator::Column::ProjectId.eq(project_id))
.all(&*tx)
@@ -2650,7 +2648,7 @@ impl Database {
.iter()
.any(|collaborator| collaborator.connection_id == connection_id)
{
Ok(collaborators)
Ok((project.room_id, collaborators))
} else {
Err(anyhow!("no such project"))?
}
@@ -2663,8 +2661,11 @@ impl Database {
project_id: ProjectId,
connection_id: ConnectionId,
) -> Result<RoomGuard<HashSet<ConnectionId>>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
self.room_transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let mut collaborators = project_collaborator::Entity::find()
.filter(project_collaborator::Column::ProjectId.eq(project_id))
.stream(&*tx)
@@ -2677,7 +2678,7 @@ impl Database {
}
if connection_ids.contains(&connection_id) {
Ok(connection_ids)
Ok((project.room_id, connection_ids))
} else {
Err(anyhow!("no such project"))?
}
@@ -2707,17 +2708,6 @@ impl Database {
Ok(guest_connection_ids)
}
async fn room_id_for_project(&self, project_id: ProjectId) -> Result<RoomId> {
self.transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("project {} not found", project_id))?;
Ok(project.room_id)
})
.await
}
// access tokens
pub async fn create_access_token_hash(
@@ -2868,48 +2858,21 @@ impl Database {
self.run(body).await
}
async fn room_transaction<F, Fut, T>(&self, room_id: RoomId, f: F) -> Result<RoomGuard<T>>
async fn room_transaction<F, Fut, T>(&self, f: F) -> Result<RoomGuard<T>>
where
F: Send + Fn(TransactionHandle) -> Fut,
Fut: Send + Future<Output = Result<T>>,
Fut: Send + Future<Output = Result<(RoomId, T)>>,
{
let body = async {
loop {
let lock = self.rooms.entry(room_id).or_default().clone();
let _guard = lock.lock_owned().await;
let (tx, result) = self.with_transaction(&f).await?;
match result {
Ok(data) => {
match tx.commit().await.map_err(Into::into) {
Ok(()) => {
return Ok(RoomGuard {
data,
_guard,
_not_send: PhantomData,
});
}
Err(error) => {
if is_serialization_error(&error) {
// Retry (don't break the loop)
} else {
return Err(error);
}
}
}
}
Err(error) => {
tx.rollback().await?;
if is_serialization_error(&error) {
// Retry (don't break the loop)
} else {
return Err(error);
}
}
let data = self
.optional_room_transaction(move |tx| {
let future = f(tx);
async {
let data = future.await?;
Ok(Some(data))
}
}
};
self.run(body).await
})
.await?;
Ok(data.unwrap())
}
async fn with_transaction<F, Fut, T>(&self, f: &F) -> Result<(DatabaseTransaction, Result<T>)>

View File

@@ -57,7 +57,7 @@ use tokio::sync::watch;
use tower::ServiceBuilder;
use tracing::{info_span, instrument, Instrument};
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(5);
pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
lazy_static! {
@@ -270,11 +270,8 @@ impl Server {
let mut live_kit_room = String::new();
let mut delete_live_kit_room = false;
if let Some(mut refreshed_room) = app_state
.db
.refresh_room(room_id, server_id)
.await
.trace_err()
if let Ok(mut refreshed_room) =
app_state.db.refresh_room(room_id, server_id).await
{
tracing::info!(
room_id = room_id.0,
@@ -1408,7 +1405,7 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
let sender_id = session.connection_id;
let project_id = ProjectId::from_proto(request.project_id);
let (room, project) = &*session
let project = session
.db()
.await
.leave_project(project_id, sender_id)
@@ -1419,9 +1416,7 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
host_connection_id = %project.host_connection_id,
"leave project"
);
project_left(&project, &session);
room_updated(&room, &session.peer);
Ok(())
}

View File

@@ -733,14 +733,6 @@ async fn test_server_restarts(
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
client_a
.fs
.insert_tree("/a", json!({ "a.txt": "a-contents" }))
.await;
// Invite client B to collaborate on a project
let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
let client_b = server.create_client(cx_b, "user_b").await;
let client_c = server.create_client(cx_c, "user_c").await;
let client_d = server.create_client(cx_d, "user_d").await;
@@ -761,19 +753,19 @@ async fn test_server_restarts(
// User A calls users B, C, and D.
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
call.invite(client_b.user_id().unwrap(), None, cx)
})
.await
.unwrap();
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_c.user_id().unwrap(), Some(project_a.clone()), cx)
call.invite(client_c.user_id().unwrap(), None, cx)
})
.await
.unwrap();
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_d.user_id().unwrap(), Some(project_a.clone()), cx)
call.invite(client_d.user_id().unwrap(), None, cx)
})
.await
.unwrap();
@@ -829,7 +821,7 @@ async fn test_server_restarts(
// Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
deterministic.advance_clock(RECONNECT_TIMEOUT);
deterministic.advance_clock(RECEIVE_TIMEOUT);
assert_eq!(
room_participants(&room_a, cx_a),
RoomParticipants {
@@ -1001,7 +993,7 @@ async fn test_server_restarts(
client_a.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
client_b.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
deterministic.advance_clock(RECONNECT_TIMEOUT);
deterministic.advance_clock(RECEIVE_TIMEOUT);
assert_eq!(
room_participants(&room_a, cx_a),
RoomParticipants {
@@ -1091,7 +1083,7 @@ async fn test_calls_on_multiple_connections(
assert!(incoming_call_b2.next().await.unwrap().is_none());
// User B disconnects the client that is not on the call. Everything should be fine.
client_b1.disconnect(&cx_b1.to_async());
client_b1.disconnect(&cx_b1.to_async()).unwrap();
deterministic.advance_clock(RECEIVE_TIMEOUT);
client_b1
.authenticate_and_connect(false, &cx_b1.to_async())
@@ -3235,7 +3227,7 @@ async fn test_leaving_project(
buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
// Drop client B's connection and ensure client A and client C observe client B leaving.
client_b.disconnect(&cx_b.to_async());
client_b.disconnect(&cx_b.to_async()).unwrap();
deterministic.advance_clock(RECONNECT_TIMEOUT);
project_a.read_with(cx_a, |project, _| {
assert_eq!(project.collaborators().len(), 1);
@@ -3892,11 +3884,9 @@ async fn test_formatting_buffer(
})
.await
.unwrap();
// The edits from the LSP are applied, and a final newline is added.
assert_eq!(
buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
"let honey = \"two\"\n"
"let honey = \"two\""
);
// Ensure buffer can be formatted using an external command. Notice how the
@@ -5782,7 +5772,7 @@ async fn test_contact_requests(
.is_empty());
async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
client.disconnect(&cx.to_async());
client.disconnect(&cx.to_async()).unwrap();
client.clear_contacts(cx).await;
client
.authenticate_and_connect(false, &cx.to_async())
@@ -5792,12 +5782,11 @@ async fn test_contact_requests(
}
#[gpui::test(iterations = 10)]
async fn test_basic_following(
async fn test_following(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
cx_c: &mut TestAppContext,
cx_d: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_a.update(editor::init);
@@ -5807,14 +5796,11 @@ async fn test_basic_following(
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
let client_c = server.create_client(cx_c, "user_c").await;
let client_d = server.create_client(cx_d, "user_d").await;
server
.create_room(&mut [
(&client_a, cx_a),
(&client_b, cx_b),
(&client_c, cx_c),
(&client_d, cx_d),
])
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
server
.make_contacts(&mut [(&client_a, cx_a), (&client_c, cx_c)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
@@ -5881,7 +5867,6 @@ async fn test_basic_following(
let peer_id_a = client_a.peer_id().unwrap();
let peer_id_b = client_b.peer_id().unwrap();
let peer_id_c = client_c.peer_id().unwrap();
let peer_id_d = client_d.peer_id().unwrap();
// Client A updates their selections in those editors
editor_a1.update(cx_a, |editor, cx| {
@@ -5901,15 +5886,25 @@ async fn test_basic_following(
.await
.unwrap();
// Client A invites client C to the call.
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_c.current_user_id(cx_c).to_proto(), None, cx)
})
.await
.unwrap();
cx_c.foreground().run_until_parked();
let active_call_c = cx_c.read(ActiveCall::global);
active_call_c
.update(cx_c, |call, cx| call.accept_incoming(cx))
.await
.unwrap();
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let workspace_c = client_c.build_workspace(&project_c, cx_c);
active_call_c
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
.await
.unwrap();
drop(project_c);
// Client C also follows client A.
workspace_c
@@ -5921,28 +5916,17 @@ async fn test_basic_following(
.await
.unwrap();
cx_d.foreground().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
let project_d = client_d.build_remote_project(project_id, cx_d).await;
let workspace_d = client_d.build_workspace(&project_d, cx_d);
active_call_d
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
.await
.unwrap();
drop(project_d);
// All clients see that clients B and C are following client A.
cx_c.foreground().run_until_parked();
for (name, active_call, cx) in [
("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_a, project_id),
room.followers_for(peer_id_a),
&[peer_id_b, peer_id_c],
"checking followers for A as {name}"
);
@@ -5960,102 +5944,17 @@ async fn test_basic_following(
("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_a, project_id),
room.followers_for(peer_id_a),
&[peer_id_b],
"checking followers for A as {name}"
);
});
}
// Client C re-follows client A.
workspace_c.update(cx_c, |workspace, cx| {
workspace.toggle_follow(&ToggleFollow(peer_id_a), cx);
});
// All clients see that clients B and C are following client A.
cx_c.foreground().run_until_parked();
for (name, active_call, cx) in [
("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_a, project_id),
&[peer_id_b, peer_id_c],
"checking followers for A as {name}"
);
});
}
// Client D follows client C.
workspace_d
.update(cx_d, |workspace, cx| {
workspace
.toggle_follow(&ToggleFollow(peer_id_c), cx)
.unwrap()
})
.await
.unwrap();
// All clients see that D is following C
cx_d.foreground().run_until_parked();
for (name, active_call, cx) in [
("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_c, project_id),
&[peer_id_d],
"checking followers for C as {name}"
);
});
}
// Client C closes the project.
cx_c.drop_last(workspace_c);
// Clients A and B see that client B is following A, and client C is not present in the followers.
cx_c.foreground().run_until_parked();
for (name, active_call, cx) in [("A", &active_call_a, &cx_a), ("B", &active_call_b, &cx_b)] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_a, project_id),
&[peer_id_b],
"checking followers for A as {name}"
);
});
}
// All clients see that no-one is following C
for (name, active_call, cx) in [
("A", &active_call_a, &cx_a),
("B", &active_call_b, &cx_b),
("C", &active_call_c, &cx_c),
("D", &active_call_d, &cx_d),
] {
active_call.read_with(*cx, |call, cx| {
let room = call.room().unwrap().read(cx);
assert_eq!(
room.followers_for(peer_id_c, project_id),
&[],
"checking followers for C as {name}"
);
});
}
let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
workspace
.active_item(cx)
@@ -6287,7 +6186,7 @@ async fn test_basic_following(
);
// Following interrupts when client B disconnects.
client_b.disconnect(&cx_b.to_async());
client_b.disconnect(&cx_b.to_async()).unwrap();
deterministic.advance_clock(RECONNECT_TIMEOUT);
assert_eq!(
workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),

View File

@@ -27,7 +27,6 @@ call = { path = "../call" }
client = { path = "../client" }
clock = { path = "../clock" }
collections = { path = "../collections" }
context_menu = { path = "../context_menu" }
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }

View File

@@ -4,10 +4,9 @@ use crate::{
ToggleScreenSharing,
};
use call::{ActiveCall, ParticipantLocation, Room};
use client::{proto::PeerId, Authenticate, ContactEventKind, SignOut, User, UserStore};
use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore};
use clock::ReplicaId;
use contacts_popover::ContactsPopover;
use context_menu::{ContextMenu, ContextMenuItem};
use gpui::{
actions,
color::Color,
@@ -29,9 +28,8 @@ actions!(
[
ToggleCollaboratorList,
ToggleContactsMenu,
ToggleUserMenu,
ShareProject,
UnshareProject,
UnshareProject
]
);
@@ -40,20 +38,25 @@ impl_internal_actions!(collab, [LeaveCall]);
#[derive(Copy, Clone, PartialEq)]
pub(crate) struct LeaveCall;
#[derive(PartialEq, Eq)]
enum ContactsPopoverSide {
Left,
Right,
}
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(CollabTitlebarItem::toggle_collaborator_list_popover);
cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
cx.add_action(CollabTitlebarItem::share_project);
cx.add_action(CollabTitlebarItem::unshare_project);
cx.add_action(CollabTitlebarItem::leave_call);
cx.add_action(CollabTitlebarItem::toggle_user_menu);
}
pub struct CollabTitlebarItem {
workspace: WeakViewHandle<Workspace>,
user_store: ModelHandle<UserStore>,
contacts_popover: Option<ViewHandle<ContactsPopover>>,
user_menu: ViewHandle<ContextMenu>,
contacts_popover_side: ContactsPopoverSide,
collaborator_list_popover: Option<ViewHandle<CollaboratorListPopover>>,
_subscriptions: Vec<Subscription>,
}
@@ -87,9 +90,9 @@ impl View for CollabTitlebarItem {
}
let theme = cx.global::<Settings>().theme.clone();
let user = workspace.read(cx).user_store().read(cx).current_user();
let mut left_container = Flex::row();
let mut right_container = Flex::row();
left_container.add_child(
Label::new(project_title, theme.workspace.titlebar.title.clone())
@@ -100,30 +103,40 @@ impl View for CollabTitlebarItem {
.boxed(),
);
let user = workspace.read(cx).user_store().read(cx).current_user();
let peer_id = workspace.read(cx).client().peer_id();
if let Some(((user, peer_id), room)) = user
.zip(peer_id)
.zip(ActiveCall::global(cx).read(cx).room().cloned())
{
left_container
.add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
left_container.add_child(self.render_current_user(&workspace, &theme, &user, cx));
left_container.add_children(self.render_collaborators(&workspace, &theme, room, cx));
left_container.add_child(self.render_toggle_contacts_button(&theme, cx));
}
right_container.add_children(self.render_collaborators(&workspace, &theme, &room, cx));
right_container
.add_child(self.render_current_user(&workspace, &theme, &user, peer_id, cx));
let mut right_container = Flex::row();
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
right_container.add_child(self.render_toggle_screen_sharing_button(&theme, &room, cx));
}
let status = workspace.read(cx).client().status();
let status = &*status.borrow();
if matches!(status, client::Status::Connected { .. }) {
right_container.add_child(self.render_toggle_contacts_button(&theme, cx));
right_container.add_child(self.render_leave_call_button(&theme, cx));
right_container
.add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
} else {
right_container.add_children(self.render_connection_status(status, cx));
right_container.add_child(self.render_outside_call_share_button(&theme, cx));
}
right_container.add_child(self.render_user_menu_button(&theme, cx));
right_container.add_children(self.render_connection_status(&workspace, cx));
if let Some(user) = user {
//TODO: Add style
right_container.add_child(
Label::new(
user.github_login.clone(),
theme.workspace.titlebar.title.clone(),
)
.aligned()
.contained()
.with_margin_left(theme.workspace.titlebar.item_spacing)
.boxed(),
);
} else {
right_container.add_child(Self::render_authenticate(&theme, cx));
}
Stack::new()
.with_child(left_container.boxed())
@@ -173,11 +186,7 @@ impl CollabTitlebarItem {
workspace: workspace.downgrade(),
user_store: user_store.clone(),
contacts_popover: None,
user_menu: cx.add_view(|cx| {
let mut menu = ContextMenu::new(cx);
menu.set_position_mode(OverlayPositionMode::Local);
menu
}),
contacts_popover_side: ContactsPopoverSide::Right,
collaborator_list_popover: None,
_subscriptions: subscriptions,
}
@@ -269,6 +278,12 @@ impl CollabTitlebarItem {
cx.notify();
})
.detach();
self.contacts_popover_side = match ActiveCall::global(cx).read(cx).room() {
Some(_) => ContactsPopoverSide::Left,
None => ContactsPopoverSide::Right,
};
self.contacts_popover = Some(view);
}
}
@@ -276,59 +291,6 @@ impl CollabTitlebarItem {
cx.notify();
}
pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
let theme = cx.global::<Settings>().theme.clone();
let avatar_style = theme.workspace.titlebar.leader_avatar.clone();
let item_style = theme.context_menu.item.disabled_style().clone();
self.user_menu.update(cx, |user_menu, cx| {
let items = if let Some(user) = self.user_store.read(cx).current_user() {
vec![
ContextMenuItem::Static(Box::new(move |_| {
Flex::row()
.with_children(user.avatar.clone().map(|avatar| {
Self::render_face(
avatar,
avatar_style.clone(),
Color::transparent_black(),
)
}))
.with_child(
Label::new(user.github_login.clone(), item_style.label.clone())
.boxed(),
)
.contained()
.with_style(item_style.container)
.boxed()
})),
ContextMenuItem::Item {
label: "Sign out".into(),
action: Box::new(SignOut),
},
]
} else {
vec![ContextMenuItem::Item {
label: "Sign in".into(),
action: Box::new(Authenticate),
}]
};
user_menu.show(
vec2f(
theme
.workspace
.titlebar
.user_menu_button
.default
.button_width,
theme.workspace.titlebar.height,
),
AnchorCorner::TopRight,
items,
cx,
);
});
}
fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext<Self>) {
ActiveCall::global(cx)
.update(cx, |call, cx| call.hang_up(cx))
@@ -366,9 +328,11 @@ impl CollabTitlebarItem {
Stack::new()
.with_child(
MouseEventHandler::<ToggleContactsMenu>::new(0, cx, |state, _| {
let style = titlebar
.toggle_contacts_button
.style_for(state, self.contacts_popover.is_some());
let style = titlebar.toggle_contacts_button.style_for(
state,
self.contacts_popover.is_some()
&& self.contacts_popover_side == ContactsPopoverSide::Left,
);
Svg::new("icons/plus_8.svg")
.with_color(style.color)
.constrained()
@@ -396,7 +360,11 @@ impl CollabTitlebarItem {
.boxed(),
)
.with_children(badge)
.with_children(self.render_contacts_popover_host(titlebar, cx))
.with_children(self.render_contacts_popover_host(
ContactsPopoverSide::Left,
titlebar,
cx,
))
.boxed()
}
@@ -446,6 +414,40 @@ impl CollabTitlebarItem {
.boxed()
}
fn render_leave_call_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
let titlebar = &theme.workspace.titlebar;
MouseEventHandler::<LeaveCall>::new(0, cx, |state, _| {
let style = titlebar.call_control.style_for(state, false);
Svg::new("icons/leave_12.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
.contained()
.with_style(style.container)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(LeaveCall);
})
.with_tooltip::<LeaveCall, _>(
0,
"Leave call".to_owned(),
Some(Box::new(LeaveCall)),
theme.tooltip.clone(),
cx,
)
.contained()
.with_margin_left(theme.workspace.titlebar.item_spacing)
.aligned()
.boxed()
}
fn render_in_call_share_unshare_button(
&self,
workspace: &ViewHandle<Workspace>,
@@ -473,9 +475,11 @@ impl CollabTitlebarItem {
.with_child(
MouseEventHandler::<ShareUnshare>::new(0, cx, |state, _| {
//TODO: Ensure this button has consistant width for both text variations
let style = titlebar
.share_button
.style_for(state, self.contacts_popover.is_some());
let style = titlebar.share_button.style_for(
state,
self.contacts_popover.is_some()
&& self.contacts_popover_side == ContactsPopoverSide::Right,
);
Label::new(label, style.text.clone())
.contained()
.with_style(style.container)
@@ -498,6 +502,11 @@ impl CollabTitlebarItem {
)
.boxed(),
)
.with_children(self.render_contacts_popover_host(
ContactsPopoverSide::Right,
titlebar,
cx,
))
.aligned()
.contained()
.with_margin_left(theme.workspace.titlebar.item_spacing)
@@ -505,71 +514,83 @@ impl CollabTitlebarItem {
)
}
fn render_user_menu_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
fn render_outside_call_share_button(
&self,
theme: &Theme,
cx: &mut RenderContext<Self>,
) -> ElementBox {
let tooltip = "Share project with new call";
let titlebar = &theme.workspace.titlebar;
enum OutsideCallShare {}
Stack::new()
.with_child(
MouseEventHandler::<ToggleUserMenu>::new(0, cx, |state, _| {
let style = titlebar.call_control.style_for(state, false);
Svg::new("icons/ellipsis_14.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
MouseEventHandler::<OutsideCallShare>::new(0, cx, |state, _| {
//TODO: Ensure this button has consistant width for both text variations
let style = titlebar.share_button.style_for(
state,
self.contacts_popover.is_some()
&& self.contacts_popover_side == ContactsPopoverSide::Right,
);
Label::new("Share".to_owned(), style.text.clone())
.contained()
.with_style(style.container)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ToggleUserMenu);
cx.dispatch_action(ToggleContactsMenu);
})
.with_tooltip::<ToggleUserMenu, _>(
.with_tooltip::<OutsideCallShare, _>(
0,
"Toggle user menu".to_owned(),
Some(Box::new(ToggleUserMenu)),
tooltip.to_owned(),
None,
theme.tooltip.clone(),
cx,
)
.contained()
.with_margin_left(theme.workspace.titlebar.item_spacing)
.aligned()
.boxed(),
)
.with_child(ChildView::new(&self.user_menu, cx).boxed())
.with_children(self.render_contacts_popover_host(
ContactsPopoverSide::Right,
titlebar,
cx,
))
.aligned()
.contained()
.with_margin_left(theme.workspace.titlebar.item_spacing)
.boxed()
}
fn render_contacts_popover_host<'a>(
&'a self,
side: ContactsPopoverSide,
theme: &'a theme::Titlebar,
cx: &'a RenderContext<Self>,
) -> Option<ElementBox> {
self.contacts_popover.as_ref().map(|popover| {
Overlay::new(
ChildView::new(popover, cx)
.contained()
.with_margin_top(theme.height)
.with_margin_left(theme.toggle_contacts_button.default.button_width)
.with_margin_right(-theme.toggle_contacts_button.default.button_width)
.boxed(),
)
.with_fit_mode(OverlayFitMode::SwitchAnchor)
.with_anchor_corner(AnchorCorner::BottomLeft)
.with_z_index(999)
.boxed()
})
) -> impl Iterator<Item = ElementBox> + 'a {
self.contacts_popover
.iter()
.filter(move |_| self.contacts_popover_side == side)
.map(|popover| {
Overlay::new(
ChildView::new(popover, cx)
.contained()
.with_margin_top(theme.height)
.with_margin_left(theme.toggle_contacts_button.default.button_width)
.with_margin_right(-theme.toggle_contacts_button.default.button_width)
.boxed(),
)
.with_fit_mode(OverlayFitMode::SwitchAnchor)
.with_anchor_corner(AnchorCorner::BottomLeft)
.with_z_index(999)
.boxed()
})
}
fn render_collaborators(
&self,
workspace: &ViewHandle<Workspace>,
theme: &Theme,
room: &ModelHandle<Room>,
room: ModelHandle<Room>,
cx: &mut RenderContext<Self>,
) -> Vec<ElementBox> {
let project = workspace.read(cx).project().read(cx);
@@ -601,7 +622,7 @@ impl CollabTitlebarItem {
theme,
cx,
))
.with_margin_right(theme.workspace.titlebar.face_pile_spacing)
.with_margin_left(theme.workspace.titlebar.face_pile_spacing)
.boxed(),
)
})
@@ -612,21 +633,35 @@ impl CollabTitlebarItem {
&self,
workspace: &ViewHandle<Workspace>,
theme: &Theme,
user: &Arc<User>,
peer_id: PeerId,
user: &Option<Arc<User>>,
cx: &mut RenderContext<Self>,
) -> ElementBox {
let user = user.as_ref().expect("Active call without user");
let replica_id = workspace.read(cx).project().read(cx).replica_id();
Container::new(self.render_face_pile(
user,
Some(replica_id),
peer_id,
None,
workspace,
theme,
cx,
))
.with_margin_right(theme.workspace.titlebar.item_spacing)
let peer_id = workspace
.read(cx)
.client()
.peer_id()
.expect("Active call without peer id");
self.render_face_pile(user, Some(replica_id), peer_id, None, workspace, theme, cx)
}
fn render_authenticate(theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
MouseEventHandler::<Authenticate>::new(0, cx, |state, _| {
let style = theme
.workspace
.titlebar
.sign_in_prompt
.style_for(state, false);
Label::new("Sign in", style.text.clone())
.contained()
.with_style(style.container)
.with_margin_left(theme.workspace.titlebar.item_spacing)
.boxed()
})
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(Authenticate))
.with_cursor_style(CursorStyle::PointingHand)
.aligned()
.boxed()
}
@@ -640,26 +675,33 @@ impl CollabTitlebarItem {
theme: &Theme,
cx: &mut RenderContext<Self>,
) -> ElementBox {
let project_id = workspace.read(cx).project().read(cx).remote_id();
let room = ActiveCall::global(cx).read(cx).room();
let is_being_followed = workspace.read(cx).is_being_followed(peer_id);
let followed_by_self = room
.and_then(|room| {
Some(
is_being_followed
&& room
.read(cx)
.followers_for(peer_id, project_id?)
.iter()
.any(|&follower| {
Some(follower) == workspace.read(cx).client().peer_id()
}),
)
.map(|room| {
is_being_followed
&& room
.read(cx)
.followers_for(peer_id)
.iter()
.any(|&follower| Some(follower) == workspace.read(cx).client().peer_id())
})
.unwrap_or(false);
let leader_style = theme.workspace.titlebar.leader_avatar;
let follower_style = theme.workspace.titlebar.follower_avatar;
let avatar_style;
if let Some(location) = location {
if let ParticipantLocation::SharedProject { project_id } = location {
if Some(project_id) == workspace.read(cx).project().read(cx).remote_id() {
avatar_style = &theme.workspace.titlebar.avatar;
} else {
avatar_style = &theme.workspace.titlebar.inactive_avatar;
}
} else {
avatar_style = &theme.workspace.titlebar.inactive_avatar;
}
} else {
avatar_style = &theme.workspace.titlebar.avatar;
}
let mut background_color = theme
.workspace
@@ -675,26 +717,23 @@ impl CollabTitlebarItem {
}
}
let mut content = Stack::new()
let content = Stack::new()
.with_children(user.avatar.as_ref().map(|avatar| {
let face_pile = FacePile::new(theme.workspace.titlebar.follower_avatar_overlap)
.with_child(Self::render_face(
avatar.clone(),
Self::location_style(workspace, location, leader_style, cx),
avatar_style.clone(),
background_color,
))
.with_children(
(|| {
let project_id = project_id?;
let room = room?.read(cx);
let followers = room.followers_for(peer_id, project_id);
let followers = room.followers_for(peer_id);
Some(followers.into_iter().flat_map(|&follower| {
let remote_participant =
room.remote_participant_for_peer_id(follower);
let avatar = remote_participant
.and_then(|p| p.user.avatar.clone())
let avatar = room
.remote_participant_for_peer_id(follower)
.and_then(|participant| participant.user.avatar.clone())
.or_else(|| {
if follower == workspace.read(cx).client().peer_id()? {
workspace
@@ -709,11 +748,9 @@ impl CollabTitlebarItem {
}
})?;
let location = remote_participant.map(|p| p.location);
Some(Self::render_face(
avatar.clone(),
Self::location_style(workspace, location, follower_style, cx),
theme.workspace.titlebar.follower_avatar.clone(),
background_color,
))
}))
@@ -752,10 +789,7 @@ impl CollabTitlebarItem {
if let Some(location) = location {
if let Some(replica_id) = replica_id {
content =
MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| {
content
})
MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| content)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ToggleFollow(peer_id))
@@ -771,14 +805,12 @@ impl CollabTitlebarItem {
theme.tooltip.clone(),
cx,
)
.boxed();
.boxed()
} else if let ParticipantLocation::SharedProject { project_id } = location {
let user_id = user.id;
content = MouseEventHandler::<JoinProject>::new(
peer_id.as_u64() as usize,
cx,
move |_, _| content,
)
MouseEventHandler::<JoinProject>::new(peer_id.as_u64() as usize, cx, move |_, _| {
content
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(JoinProject {
@@ -793,29 +825,13 @@ impl CollabTitlebarItem {
theme.tooltip.clone(),
cx,
)
.boxed();
}
}
content
}
fn location_style(
workspace: &ViewHandle<Workspace>,
location: Option<ParticipantLocation>,
mut style: AvatarStyle,
cx: &RenderContext<Self>,
) -> AvatarStyle {
if let Some(location) = location {
if let ParticipantLocation::SharedProject { project_id } = location {
if Some(project_id) != workspace.read(cx).project().read(cx).remote_id() {
style.image.grayscale = true;
}
.boxed()
} else {
style.image.grayscale = true;
content
}
} else {
content
}
style
}
fn render_face(
@@ -838,13 +854,13 @@ impl CollabTitlebarItem {
fn render_connection_status(
&self,
status: &client::Status,
workspace: &ViewHandle<Workspace>,
cx: &mut RenderContext<Self>,
) -> Option<ElementBox> {
enum ConnectionStatusButton {}
let theme = &cx.global::<Settings>().theme.clone();
match status {
match &*workspace.read(cx).client().status().borrow() {
client::Status::ConnectionError
| client::Status::ConnectionLost
| client::Status::Reauthenticating { .. }

View File

@@ -1294,7 +1294,7 @@ impl View for ContactList {
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut cx = Self::default_keymap_context();
cx.add_identifier("menu");
cx.set.insert("menu".into());
cx
}

View File

@@ -5,9 +5,7 @@ use gpui::{
};
use menu::*;
use settings::Settings;
use std::{any::TypeId, borrow::Cow, time::Duration};
pub type StaticItem = Box<dyn Fn(&mut MutableAppContext) -> ElementBox>;
use std::{any::TypeId, time::Duration};
#[derive(Copy, Clone, PartialEq)]
struct Clicked;
@@ -26,17 +24,16 @@ pub fn init(cx: &mut MutableAppContext) {
pub enum ContextMenuItem {
Item {
label: Cow<'static, str>,
label: String,
action: Box<dyn Action>,
},
Static(StaticItem),
Separator,
}
impl ContextMenuItem {
pub fn item(label: impl Into<Cow<'static, str>>, action: impl 'static + Action) -> Self {
pub fn item(label: impl ToString, action: impl 'static + Action) -> Self {
Self::Item {
label: label.into(),
label: label.to_string(),
action: Box::new(action),
}
}
@@ -45,14 +42,14 @@ impl ContextMenuItem {
Self::Separator
}
fn is_action(&self) -> bool {
matches!(self, Self::Item { .. })
fn is_separator(&self) -> bool {
matches!(self, Self::Separator)
}
fn action_id(&self) -> Option<TypeId> {
match self {
ContextMenuItem::Item { action, .. } => Some(action.id()),
ContextMenuItem::Static(..) | ContextMenuItem::Separator => None,
ContextMenuItem::Separator => None,
}
}
}
@@ -61,7 +58,6 @@ pub struct ContextMenu {
show_count: usize,
anchor_position: Vector2F,
anchor_corner: AnchorCorner,
position_mode: OverlayPositionMode,
items: Vec<ContextMenuItem>,
selected_index: Option<usize>,
visible: bool,
@@ -82,7 +78,7 @@ impl View for ContextMenu {
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut cx = Self::default_keymap_context();
cx.add_identifier("menu");
cx.set.insert("menu".into());
cx
}
@@ -109,7 +105,6 @@ impl View for ContextMenu {
.with_fit_mode(OverlayFitMode::SnapToWindow)
.with_anchor_position(self.anchor_position)
.with_anchor_corner(self.anchor_corner)
.with_position_mode(self.position_mode)
.boxed()
}
@@ -126,7 +121,6 @@ impl ContextMenu {
show_count: 0,
anchor_position: Default::default(),
anchor_corner: AnchorCorner::TopLeft,
position_mode: OverlayPositionMode::Window,
items: Default::default(),
selected_index: Default::default(),
visible: Default::default(),
@@ -194,13 +188,13 @@ impl ContextMenu {
}
fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
self.selected_index = self.items.iter().position(|item| item.is_action());
self.selected_index = self.items.iter().position(|item| !item.is_separator());
cx.notify();
}
fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
for (ix, item) in self.items.iter().enumerate().rev() {
if item.is_action() {
if !item.is_separator() {
self.selected_index = Some(ix);
cx.notify();
break;
@@ -211,7 +205,7 @@ impl ContextMenu {
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.selected_index {
for (ix, item) in self.items.iter().enumerate().skip(ix + 1) {
if item.is_action() {
if !item.is_separator() {
self.selected_index = Some(ix);
cx.notify();
break;
@@ -225,7 +219,7 @@ impl ContextMenu {
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.selected_index {
for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
if item.is_action() {
if !item.is_separator() {
self.selected_index = Some(ix);
cx.notify();
break;
@@ -240,7 +234,7 @@ impl ContextMenu {
&mut self,
anchor_position: Vector2F,
anchor_corner: AnchorCorner,
items: Vec<ContextMenuItem>,
items: impl IntoIterator<Item = ContextMenuItem>,
cx: &mut ViewContext<Self>,
) {
let mut items = items.into_iter().peekable();
@@ -260,10 +254,6 @@ impl ContextMenu {
cx.notify();
}
pub fn set_position_mode(&mut self, mode: OverlayPositionMode) {
self.position_mode = mode;
}
fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element {
let window_id = cx.window_id();
let style = cx.global::<Settings>().theme.context_menu.clone();
@@ -283,9 +273,6 @@ impl ContextMenu {
.with_style(style.container)
.boxed()
}
ContextMenuItem::Static(f) => f(cx),
ContextMenuItem::Separator => Empty::new()
.collapsed()
.contained()
@@ -315,9 +302,6 @@ impl ContextMenu {
)
.boxed()
}
ContextMenuItem::Static(_) => Empty::new().boxed(),
ContextMenuItem::Separator => Empty::new()
.collapsed()
.constrained()
@@ -355,7 +339,7 @@ impl ContextMenu {
Flex::row()
.with_child(
Label::new(label.clone(), style.label.clone())
Label::new(label.to_string(), style.label.clone())
.contained()
.boxed(),
)
@@ -382,9 +366,6 @@ impl ContextMenu {
.on_drag(MouseButton::Left, |_, _| {})
.boxed()
}
ContextMenuItem::Static(f) => f(cx),
ContextMenuItem::Separator => Empty::new()
.constrained()
.with_height(1.)

View File

@@ -8,7 +8,6 @@ use block_map::{BlockMap, BlockPoint};
use collections::{HashMap, HashSet};
use fold_map::FoldMap;
use gpui::{
color::Color,
fonts::{FontId, HighlightStyle},
Entity, ModelContext, ModelHandle,
};
@@ -24,12 +23,6 @@ pub use block_map::{
BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FoldStatus {
Folded,
Foldable,
}
pub trait ToDisplayPoint {
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint;
}
@@ -219,10 +212,6 @@ impl DisplayMap {
.update(cx, |map, cx| map.set_font(font_id, font_size, cx))
}
pub fn set_fold_ellipses_color(&mut self, color: Color) -> bool {
self.fold_map.set_ellipses_color(color)
}
pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut ModelContext<Self>) -> bool {
self.wrap_map
.update(cx, |map, cx| map.set_wrap_width(width, cx))
@@ -602,59 +591,6 @@ impl DisplaySnapshot {
self.blocks_snapshot.longest_row()
}
pub fn fold_for_line(self: &Self, display_row: u32) -> Option<FoldStatus> {
if self.is_foldable(display_row) {
Some(FoldStatus::Foldable)
} else if self.is_line_folded(display_row) {
Some(FoldStatus::Folded)
} else {
None
}
}
pub fn is_foldable(self: &Self, row: u32) -> bool {
let max_point = self.max_point();
if row >= max_point.row() {
return false;
}
let (start_indent, is_blank) = self.line_indent(row);
if is_blank {
return false;
}
for display_row in next_rows(row, self) {
let (indent, is_blank) = self.line_indent(display_row);
if !is_blank {
return indent > start_indent;
}
}
return false;
}
pub fn foldable_range(self: &Self, row: u32) -> Option<Range<DisplayPoint>> {
let start = DisplayPoint::new(row, self.line_len(row));
if self.is_foldable(row) && !self.is_line_folded(start.row()) {
let (start_indent, _) = self.line_indent(row);
let max_point = self.max_point();
let mut end = None;
for row in next_rows(row, self) {
let (indent, is_blank) = self.line_indent(row);
if !is_blank && indent <= start_indent {
end = Some(DisplayPoint::new(row - 1, self.line_len(row - 1)));
break;
}
}
let end = end.unwrap_or(max_point);
Some(start..end)
} else {
None
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn highlight_ranges<Tag: ?Sized + 'static>(
&self,
@@ -742,24 +678,6 @@ impl ToDisplayPoint for Anchor {
}
}
pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterator<Item = u32> {
let max_row = display_map.max_point().row();
let start_row = display_row + 1;
let mut current = None;
std::iter::from_fn(move || {
if current == None {
current = Some(start_row);
} else {
current = Some(current.unwrap() + 1)
}
if current.unwrap() > max_row {
None
} else {
current
}
})
}
#[cfg(test)]
pub mod tests {
use super::*;
@@ -1249,7 +1167,7 @@ pub mod tests {
vec![
("fn ".to_string(), None),
("out".to_string(), Some(Color::blue())),
("".to_string(), None),
("".to_string(), None),
(" fn ".to_string(), Some(Color::red())),
("inner".to_string(), Some(Color::blue())),
("() {}\n}".to_string(), Some(Color::red())),
@@ -1330,7 +1248,7 @@ pub mod tests {
cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)),
[
("out".to_string(), Some(Color::blue())),
("\n".to_string(), None),
("\n".to_string(), None),
(" \nfn ".to_string(), Some(Color::red())),
("i\n".to_string(), Some(Color::blue()))
]

View File

@@ -4,7 +4,7 @@ use crate::{
ToOffset,
};
use collections::BTreeMap;
use gpui::{color::Color, fonts::HighlightStyle};
use gpui::fonts::HighlightStyle;
use language::{Chunk, Edit, Point, TextSummary};
use parking_lot::Mutex;
use std::{
@@ -133,7 +133,6 @@ impl<'a> FoldMapWriter<'a> {
folds: self.0.folds.clone(),
buffer_snapshot: buffer,
version: self.0.version.load(SeqCst),
ellipses_color: self.0.ellipses_color,
};
(snapshot, edits)
}
@@ -183,7 +182,6 @@ impl<'a> FoldMapWriter<'a> {
folds: self.0.folds.clone(),
buffer_snapshot: buffer,
version: self.0.version.load(SeqCst),
ellipses_color: self.0.ellipses_color,
};
(snapshot, edits)
}
@@ -194,7 +192,6 @@ pub struct FoldMap {
transforms: Mutex<SumTree<Transform>>,
folds: SumTree<Fold>,
version: AtomicUsize,
ellipses_color: Option<Color>,
}
impl FoldMap {
@@ -212,7 +209,6 @@ impl FoldMap {
},
&(),
)),
ellipses_color: None,
version: Default::default(),
};
@@ -221,7 +217,6 @@ impl FoldMap {
folds: this.folds.clone(),
buffer_snapshot: this.buffer.lock().clone(),
version: this.version.load(SeqCst),
ellipses_color: None,
};
(this, snapshot)
}
@@ -238,7 +233,6 @@ impl FoldMap {
folds: self.folds.clone(),
buffer_snapshot: self.buffer.lock().clone(),
version: self.version.load(SeqCst),
ellipses_color: self.ellipses_color,
};
(snapshot, edits)
}
@@ -252,15 +246,6 @@ impl FoldMap {
(FoldMapWriter(self), snapshot, edits)
}
pub fn set_ellipses_color(&mut self, color: Color) -> bool {
if self.ellipses_color != Some(color) {
self.ellipses_color = Some(color);
true
} else {
false
}
}
fn check_invariants(&self) {
if cfg!(test) {
assert_eq!(
@@ -385,7 +370,7 @@ impl FoldMap {
}
if fold.end > fold.start {
let output_text = "";
let output_text = "";
new_transforms.push(
Transform {
summary: TransformSummary {
@@ -492,7 +477,6 @@ pub struct FoldSnapshot {
folds: SumTree<Fold>,
buffer_snapshot: MultiBufferSnapshot,
pub version: usize,
pub ellipses_color: Option<Color>,
}
impl FoldSnapshot {
@@ -755,7 +739,6 @@ impl FoldSnapshot {
max_output_offset: range.end.0,
highlight_endpoints: highlight_endpoints.into_iter().peekable(),
active_highlights: Default::default(),
ellipses_color: self.ellipses_color,
}
}
@@ -1046,7 +1029,6 @@ pub struct FoldChunks<'a> {
max_output_offset: usize,
highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
active_highlights: BTreeMap<Option<TypeId>, HighlightStyle>,
ellipses_color: Option<Color>,
}
impl<'a> Iterator for FoldChunks<'a> {
@@ -1076,10 +1058,7 @@ impl<'a> Iterator for FoldChunks<'a> {
return Some(Chunk {
text: output_text,
syntax_highlight_id: None,
highlight_style: self.ellipses_color.map(|color| HighlightStyle {
color: Some(color),
..Default::default()
}),
highlight_style: None,
diagnostic_severity: None,
is_unnecessary: false,
});
@@ -1235,7 +1214,7 @@ mod tests {
Point::new(0, 2)..Point::new(2, 2),
Point::new(2, 4)..Point::new(4, 1),
]);
assert_eq!(snapshot2.text(), "aacceeeee");
assert_eq!(snapshot2.text(), "aacceeeee");
assert_eq!(
edits,
&[
@@ -1262,7 +1241,7 @@ mod tests {
buffer.snapshot(cx)
});
let (snapshot3, edits) = map.read(buffer_snapshot, subscription.consume().into_inner());
assert_eq!(snapshot3.text(), "123ac123ceeeee");
assert_eq!(snapshot3.text(), "123ac123ceeeee");
assert_eq!(
edits,
&[
@@ -1282,12 +1261,12 @@ mod tests {
buffer.snapshot(cx)
});
let (snapshot4, _) = map.read(buffer_snapshot.clone(), subscription.consume().into_inner());
assert_eq!(snapshot4.text(), "123ac123456eee");
assert_eq!(snapshot4.text(), "123ac123456eee");
let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]);
writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false);
let (snapshot5, _) = map.read(buffer_snapshot.clone(), vec![]);
assert_eq!(snapshot5.text(), "123ac123456eee");
assert_eq!(snapshot5.text(), "123ac123456eee");
let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]);
writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true);
@@ -1308,19 +1287,19 @@ mod tests {
let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]);
writer.fold(vec![5..8]);
let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]);
assert_eq!(snapshot.text(), "abcdeijkl");
assert_eq!(snapshot.text(), "abcdeijkl");
// Create an fold adjacent to the start of the first fold.
let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]);
writer.fold(vec![0..1, 2..5]);
let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]);
assert_eq!(snapshot.text(), "⋯b⋯ijkl");
assert_eq!(snapshot.text(), "…b…ijkl");
// Create an fold adjacent to the end of the first fold.
let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]);
writer.fold(vec![11..11, 8..10]);
let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]);
assert_eq!(snapshot.text(), "⋯b⋯kl");
assert_eq!(snapshot.text(), "…b…kl");
}
{
@@ -1330,7 +1309,7 @@ mod tests {
let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]);
writer.fold(vec![0..2, 2..5]);
let (snapshot, _) = map.read(buffer_snapshot, vec![]);
assert_eq!(snapshot.text(), "fghijkl");
assert_eq!(snapshot.text(), "fghijkl");
// Edit within one of the folds.
let buffer_snapshot = buffer.update(cx, |buffer, cx| {
@@ -1338,7 +1317,7 @@ mod tests {
buffer.snapshot(cx)
});
let (snapshot, _) = map.read(buffer_snapshot, subscription.consume().into_inner());
assert_eq!(snapshot.text(), "12345fghijkl");
assert_eq!(snapshot.text(), "12345fghijkl");
}
}
@@ -1355,7 +1334,7 @@ mod tests {
Point::new(3, 1)..Point::new(4, 1),
]);
let (snapshot, _) = map.read(buffer_snapshot, vec![]);
assert_eq!(snapshot.text(), "aaeeeee");
assert_eq!(snapshot.text(), "aaeeeee");
}
#[gpui::test]
@@ -1372,14 +1351,14 @@ mod tests {
Point::new(3, 1)..Point::new(4, 1),
]);
let (snapshot, _) = map.read(buffer_snapshot, vec![]);
assert_eq!(snapshot.text(), "aacccc\ndeeeee");
assert_eq!(snapshot.text(), "aacccc\ndeeeee");
let buffer_snapshot = buffer.update(cx, |buffer, cx| {
buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
buffer.snapshot(cx)
});
let (snapshot, _) = map.read(buffer_snapshot, subscription.consume().into_inner());
assert_eq!(snapshot.text(), "aaeeeee");
assert_eq!(snapshot.text(), "aaeeeee");
}
#[gpui::test]
@@ -1471,7 +1450,7 @@ mod tests {
let mut expected_text: String = buffer_snapshot.text().to_string();
for fold_range in map.merged_fold_ranges().into_iter().rev() {
expected_text.replace_range(fold_range.start..fold_range.end, "");
expected_text.replace_range(fold_range.start..fold_range.end, "");
}
assert_eq!(snapshot.text(), expected_text);
@@ -1676,7 +1655,7 @@ mod tests {
]);
let (snapshot, _) = map.read(buffer_snapshot, vec![]);
assert_eq!(snapshot.text(), "aacccc\ndeeeee\nffffff\n");
assert_eq!(snapshot.text(), "aacccc\ndeeeee\nffffff\n");
assert_eq!(
snapshot.buffer_rows(0).collect::<Vec<_>>(),
[Some(0), Some(3), Some(5), Some(6)]

View File

@@ -1,7 +1,6 @@
mod blink_manager;
pub mod display_map;
mod element;
mod git;
mod highlight_matching_bracket;
mod hover_popover;
@@ -161,21 +160,6 @@ pub struct ToggleComments {
pub advance_downwards: bool,
}
#[derive(Clone, Default, Deserialize, PartialEq)]
pub struct FoldAt {
pub display_row: u32,
}
#[derive(Clone, Default, Deserialize, PartialEq)]
pub struct UnfoldAt {
pub display_row: u32,
}
#[derive(Clone, Default, Deserialize, PartialEq)]
pub struct GutterHover {
pub hovered: bool,
}
actions!(
editor,
[
@@ -274,9 +258,6 @@ impl_actions!(
ConfirmCompletion,
ConfirmCodeAction,
ToggleComments,
FoldAt,
UnfoldAt,
GutterHover
]
);
@@ -367,10 +348,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::go_to_definition);
cx.add_action(Editor::go_to_type_definition);
cx.add_action(Editor::fold);
cx.add_action(Editor::fold_at);
cx.add_action(Editor::unfold_lines);
cx.add_action(Editor::unfold_at);
cx.add_action(Editor::gutter_hover);
cx.add_action(Editor::fold_selected_ranges);
cx.add_action(Editor::show_completions);
cx.add_action(Editor::toggle_code_actions);
@@ -502,7 +480,6 @@ pub struct Editor {
leader_replica_id: Option<u16>,
remote_id: Option<ViewId>,
hover_state: HoverState,
gutter_hovered: bool,
link_go_to_definition_state: LinkGoToDefinitionState,
_subscriptions: Vec<Subscription>,
}
@@ -1174,7 +1151,6 @@ impl Editor {
remote_id: None,
hover_state: Default::default(),
link_go_to_definition_state: Default::default(),
gutter_hovered: false,
_subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed),
cx.subscribe(&buffer, Self::on_buffer_event),
@@ -2669,15 +2645,14 @@ impl Editor {
pub fn render_code_actions_indicator(
&self,
style: &EditorStyle,
active: bool,
cx: &mut RenderContext<Self>,
) -> Option<ElementBox> {
if self.available_code_actions.is_some() {
enum CodeActions {}
enum Tag {}
Some(
MouseEventHandler::<CodeActions>::new(0, cx, |state, _| {
MouseEventHandler::<Tag>::new(0, cx, |_, _| {
Svg::new("icons/bolt_8.svg")
.with_color(style.code_actions.indicator.style_for(state, active).color)
.with_color(style.code_actions.indicator)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
@@ -2694,80 +2669,6 @@ impl Editor {
}
}
pub fn render_fold_indicators(
&self,
fold_data: Option<Vec<(u32, FoldStatus)>>,
active_rows: &BTreeMap<u32, bool>,
style: &EditorStyle,
gutter_hovered: bool,
line_height: f32,
gutter_margin: f32,
cx: &mut RenderContext<Self>,
) -> Option<Vec<(u32, ElementBox)>> {
enum FoldIndicators {}
let style = style.folds.clone();
fold_data.map(|fold_data| {
fold_data
.iter()
.copied()
.filter_map(|(fold_location, fold_status)| {
(gutter_hovered
|| fold_status == FoldStatus::Folded
|| !*active_rows.get(&fold_location).unwrap_or(&true))
.then(|| {
(
fold_location,
MouseEventHandler::<FoldIndicators>::new(
fold_location as usize,
cx,
|mouse_state, _| -> ElementBox {
Svg::new(match fold_status {
FoldStatus::Folded => style.folded_icon.clone(),
FoldStatus::Foldable => style.foldable_icon.clone(),
})
.with_color(
style
.indicator
.style_for(
mouse_state,
fold_status == FoldStatus::Folded,
)
.color,
)
.constrained()
.with_width(style.icon_width)
.aligned()
.constrained()
.with_height(line_height)
.with_width(gutter_margin)
.aligned()
.boxed()
},
)
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(3.))
.on_click(MouseButton::Left, {
move |_, cx| {
cx.dispatch_any_action(match fold_status {
FoldStatus::Folded => Box::new(UnfoldAt {
display_row: fold_location,
}),
FoldStatus::Foldable => Box::new(FoldAt {
display_row: fold_location,
}),
});
}
})
.boxed(),
)
})
})
.collect()
})
}
pub fn context_menu_visible(&self) -> bool {
self.context_menu
.as_ref()
@@ -3350,12 +3251,26 @@ impl Editor {
while let Some(selection) = selections.next() {
// Find all the selections that span a contiguous row range
let (start_row, end_row) = consume_contiguous_rows(
&mut contiguous_row_selections,
selection,
&display_map,
&mut selections,
);
contiguous_row_selections.push(selection.clone());
let start_row = selection.start.row;
let mut end_row = if selection.end.column > 0 || selection.is_empty() {
display_map.next_line_boundary(selection.end).0.row + 1
} else {
selection.end.row
};
while let Some(next_selection) = selections.peek() {
if next_selection.start.row <= end_row {
end_row = if next_selection.end.column > 0 || next_selection.is_empty() {
display_map.next_line_boundary(next_selection.end).0.row + 1
} else {
next_selection.end.row
};
contiguous_row_selections.push(selections.next().unwrap().clone());
} else {
break;
}
}
// Move the text spanned by the row range to be before the line preceding the row range
if start_row > 0 {
@@ -3420,13 +3335,13 @@ impl Editor {
}
self.transact(cx, |this, cx| {
this.unfold_ranges(unfold_ranges, true, true, cx);
this.unfold_ranges(unfold_ranges, true, cx);
this.buffer.update(cx, |buffer, cx| {
for (range, text) in edits {
buffer.edit([(range, text)], None, cx);
}
});
this.fold_ranges(refold_ranges, true, cx);
this.fold_ranges(refold_ranges, cx);
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(new_selections);
})
@@ -3448,12 +3363,26 @@ impl Editor {
while let Some(selection) = selections.next() {
// Find all the selections that span a contiguous row range
let (start_row, end_row) = consume_contiguous_rows(
&mut contiguous_row_selections,
selection,
&display_map,
&mut selections,
);
contiguous_row_selections.push(selection.clone());
let start_row = selection.start.row;
let mut end_row = if selection.end.column > 0 || selection.is_empty() {
display_map.next_line_boundary(selection.end).0.row + 1
} else {
selection.end.row
};
while let Some(next_selection) = selections.peek() {
if next_selection.start.row <= end_row {
end_row = if next_selection.end.column > 0 || next_selection.is_empty() {
display_map.next_line_boundary(next_selection.end).0.row + 1
} else {
next_selection.end.row
};
contiguous_row_selections.push(selections.next().unwrap().clone());
} else {
break;
}
}
// Move the text spanned by the row range to be after the last line of the row range
if end_row <= buffer.max_point().row {
@@ -3511,13 +3440,13 @@ impl Editor {
}
self.transact(cx, |this, cx| {
this.unfold_ranges(unfold_ranges, true, true, cx);
this.unfold_ranges(unfold_ranges, true, cx);
this.buffer.update(cx, |buffer, cx| {
for (range, text) in edits {
buffer.edit([(range, text)], None, cx);
}
});
this.fold_ranges(refold_ranges, true, cx);
this.fold_ranges(refold_ranges, cx);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
});
}
@@ -4345,7 +4274,7 @@ impl Editor {
to_unfold.push(selection.start..selection.end);
}
}
self.unfold_ranges(to_unfold, true, true, cx);
self.unfold_ranges(to_unfold, true, cx);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(new_selection_ranges);
});
@@ -4494,7 +4423,7 @@ impl Editor {
}
if let Some(next_selected_range) = next_selected_range {
self.unfold_ranges([next_selected_range.clone()], false, true, cx);
self.unfold_ranges([next_selected_range.clone()], false, cx);
self.change_selections(Some(Autoscroll::newest()), cx, |s| {
if action.replace_newest {
s.delete(s.newest_anchor().id);
@@ -4527,7 +4456,7 @@ impl Editor {
wordwise: true,
done: false,
};
self.unfold_ranges([selection.start..selection.end], false, true, cx);
self.unfold_ranges([selection.start..selection.end], false, cx);
self.change_selections(Some(Autoscroll::newest()), cx, |s| {
s.select(selections);
});
@@ -5530,20 +5459,21 @@ impl Editor {
None => return None,
};
Some(self.perform_format(project, FormatTrigger::Manual, cx))
Some(self.perform_format(project, cx))
}
fn perform_format(
&mut self,
project: ModelHandle<Project>,
trigger: FormatTrigger,
cx: &mut ViewContext<'_, Self>,
) -> Task<Result<()>> {
let buffer = self.buffer().clone();
let buffers = buffer.read(cx).all_buffers();
let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse();
let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
let format = project.update(cx, |project, cx| {
project.format(buffers, true, FormatTrigger::Manual, cx)
});
cx.spawn(|_, mut cx| async move {
let transaction = futures::select_biased! {
@@ -5747,18 +5677,14 @@ impl Editor {
let mut fold_ranges = Vec::new();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(cx);
for selection in selections {
let range = selection.display_range(&display_map).sorted();
let buffer_start_row = range.start.to_point(&display_map).row;
for row in (0..=range.end.row()).rev() {
let fold_range = display_map.foldable_range(row).map(|range| {
range.start.to_point(&display_map)..range.end.to_point(&display_map)
});
if let Some(fold_range) = fold_range {
if self.is_line_foldable(&display_map, row) && !display_map.is_line_folded(row) {
let fold_range = self.foldable_range_for_line(&display_map, row);
if fold_range.end.row >= buffer_start_row {
fold_ranges.push(fold_range);
if row <= range.start.row() {
@@ -5769,26 +5695,7 @@ impl Editor {
}
}
self.fold_ranges(fold_ranges, true, cx);
}
pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
let display_row = fold_at.display_row;
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
if let Some(fold_range) = display_map.foldable_range(display_row) {
let autoscroll = self
.selections
.all::<Point>(cx)
.iter()
.any(|selection| fold_range.overlaps(&selection.display_range(&display_map)));
let fold_range =
fold_range.start.to_point(&display_map)..fold_range.end.to_point(&display_map);
self.fold_ranges(std::iter::once(fold_range), autoscroll, cx);
}
self.fold_ranges(fold_ranges, cx);
}
pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
@@ -5806,86 +5713,85 @@ impl Editor {
start..end
})
.collect::<Vec<_>>();
self.unfold_ranges(ranges, true, true, cx);
self.unfold_ranges(ranges, true, cx);
}
pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
fn is_line_foldable(&self, display_map: &DisplaySnapshot, display_row: u32) -> bool {
let max_point = display_map.max_point();
if display_row >= max_point.row() {
false
} else {
let (start_indent, is_blank) = display_map.line_indent(display_row);
if is_blank {
false
} else {
for display_row in display_row + 1..=max_point.row() {
let (indent, is_blank) = display_map.line_indent(display_row);
if !is_blank {
return indent > start_indent;
}
}
false
}
}
}
let intersection_range = DisplayPoint::new(unfold_at.display_row, 0)
..DisplayPoint::new(
unfold_at.display_row,
display_map.line_len(unfold_at.display_row),
);
fn foldable_range_for_line(
&self,
display_map: &DisplaySnapshot,
start_row: u32,
) -> Range<Point> {
let max_point = display_map.max_point();
let autoscroll =
self.selections.all::<Point>(cx).iter().any(|selection| {
intersection_range.overlaps(&selection.display_range(&display_map))
});
let (start_indent, _) = display_map.line_indent(start_row);
let start = DisplayPoint::new(start_row, display_map.line_len(start_row));
let mut end = None;
for row in start_row + 1..=max_point.row() {
let (indent, is_blank) = display_map.line_indent(row);
if !is_blank && indent <= start_indent {
end = Some(DisplayPoint::new(row - 1, display_map.line_len(row - 1)));
break;
}
}
let display_point = DisplayPoint::new(unfold_at.display_row, 0).to_point(&display_map);
let mut point_range = display_point..display_point;
point_range.start.column = 0;
point_range.end.column = display_map.buffer_snapshot.line_len(point_range.end.row);
self.unfold_ranges(std::iter::once(point_range), true, autoscroll, cx)
let end = end.unwrap_or(max_point);
start.to_point(display_map)..end.to_point(display_map)
}
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
let selections = self.selections.all::<Point>(cx);
let ranges = selections.into_iter().map(|s| s.start..s.end);
self.fold_ranges(ranges, true, cx);
self.fold_ranges(ranges, cx);
}
pub fn fold_ranges<T: ToOffset + Clone>(
pub fn fold_ranges<T: ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = Range<T>>,
auto_scroll: bool,
cx: &mut ViewContext<Self>,
) {
let mut ranges = ranges.into_iter().peekable();
if ranges.peek().is_some() {
self.display_map.update(cx, |map, cx| map.fold(ranges, cx));
if auto_scroll {
self.request_autoscroll(Autoscroll::fit(), cx);
}
self.request_autoscroll(Autoscroll::fit(), cx);
cx.notify();
}
}
pub fn unfold_ranges<T: ToOffset + Clone>(
pub fn unfold_ranges<T: ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = Range<T>>,
inclusive: bool,
auto_scroll: bool,
cx: &mut ViewContext<Self>,
) {
let mut ranges = ranges.into_iter().peekable();
if ranges.peek().is_some() {
self.display_map
.update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
if auto_scroll {
self.request_autoscroll(Autoscroll::fit(), cx);
}
self.request_autoscroll(Autoscroll::fit(), cx);
cx.notify();
}
}
pub fn gutter_hover(
&mut self,
GutterHover { hovered }: &GutterHover,
cx: &mut ViewContext<Self>,
) {
self.gutter_hovered = *hovered;
cx.notify();
}
pub fn insert_blocks(
&mut self,
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
@@ -6347,35 +6253,6 @@ impl Editor {
}
}
fn consume_contiguous_rows(
contiguous_row_selections: &mut Vec<Selection<Point>>,
selection: &Selection<Point>,
display_map: &DisplaySnapshot,
selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
) -> (u32, u32) {
contiguous_row_selections.push(selection.clone());
let start_row = selection.start.row;
let mut end_row = ending_row(selection, display_map);
while let Some(next_selection) = selections.peek() {
if next_selection.start.row <= end_row {
end_row = ending_row(next_selection, display_map);
contiguous_row_selections.push(selections.next().unwrap().clone());
} else {
break;
}
}
(start_row, end_row)
}
fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> u32 {
if next_selection.end.column > 0 || next_selection.is_empty() {
display_map.next_line_boundary(next_selection.end).0.row + 1
} else {
next_selection.end.row
}
}
impl EditorSnapshot {
pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
self.display_snapshot.buffer_snapshot.language_at(position)
@@ -6447,7 +6324,6 @@ impl View for Editor {
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
let style = self.style(cx);
let font_changed = self.display_map.update(cx, |map, cx| {
map.set_fold_ellipses_color(style.folds.ellipses.text_color);
map.set_font(style.text.font_id, style.text.font_size, cx)
});
@@ -6557,13 +6433,17 @@ impl View for Editor {
EditorMode::AutoHeight { .. } => "auto_height",
EditorMode::Full => "full",
};
context.add_key("mode", mode);
context.map.insert("mode".into(), mode.into());
if self.pending_rename.is_some() {
context.add_identifier("renaming");
context.set.insert("renaming".into());
}
match self.context_menu.as_ref() {
Some(ContextMenu::Completions(_)) => context.add_identifier("showing_completions"),
Some(ContextMenu::CodeActions(_)) => context.add_identifier("showing_code_actions"),
Some(ContextMenu::Completions(_)) => {
context.set.insert("showing_completions".into());
}
Some(ContextMenu::CodeActions(_)) => {
context.set.insert("showing_code_actions".into());
}
None => {}
}

View File

@@ -1,23 +1,23 @@
use drag_and_drop::DragAndDrop;
use futures::StreamExt;
use indoc::indoc;
use std::{cell::RefCell, rc::Rc, time::Instant};
use unindent::Unindent;
use super::*;
use crate::test::{
assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
editor_test_context::EditorTestContext, select_ranges,
};
use drag_and_drop::DragAndDrop;
use futures::StreamExt;
use gpui::{
executor::Deterministic,
geometry::{rect::RectF, vector::vec2f},
platform::{WindowBounds, WindowOptions},
serde_json,
};
use indoc::indoc;
use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
use parking_lot::Mutex;
use project::FakeFs;
use settings::EditorSettings;
use std::{cell::RefCell, rc::Rc, time::Instant};
use unindent::Unindent;
use util::{
assert_set_eq,
test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
@@ -447,7 +447,6 @@ fn test_clone(cx: &mut gpui::MutableAppContext) {
Point::new(1, 0)..Point::new(2, 0),
Point::new(3, 0)..Point::new(4, 0),
],
true,
cx,
);
});
@@ -670,10 +669,10 @@ fn test_fold(cx: &mut gpui::MutableAppContext) {
1
}
fn b() {
fn b() {
}
fn c() {
fn c() {
}
}
"
@@ -684,7 +683,7 @@ fn test_fold(cx: &mut gpui::MutableAppContext) {
assert_eq!(
view.display_text(cx),
"
impl Foo {
impl Foo {
}
"
.unindent(),
@@ -701,10 +700,10 @@ fn test_fold(cx: &mut gpui::MutableAppContext) {
1
}
fn b() {
fn b() {
}
fn c() {
fn c() {
}
}
"
@@ -808,10 +807,9 @@ fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
Point::new(1, 2)..Point::new(1, 4),
Point::new(2, 4)..Point::new(2, 8),
],
true,
cx,
);
assert_eq!(view.display_text(cx), "ⓐⓑ\nabe\nαβε\n");
assert_eq!(view.display_text(cx), "ⓐⓑ\nabe\nαβε\n");
view.move_right(&MoveRight, cx);
assert_eq!(
@@ -826,13 +824,13 @@ fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
view.move_right(&MoveRight, cx);
assert_eq!(
view.selections.display_ranges(cx),
&[empty_range(0, "ⓐⓑ".len())]
&[empty_range(0, "ⓐⓑ".len())]
);
view.move_down(&MoveDown, cx);
assert_eq!(
view.selections.display_ranges(cx),
&[empty_range(1, "ab".len())]
&[empty_range(1, "ab".len())]
);
view.move_left(&MoveLeft, cx);
assert_eq!(
@@ -858,28 +856,28 @@ fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) {
view.move_right(&MoveRight, cx);
assert_eq!(
view.selections.display_ranges(cx),
&[empty_range(2, "αβ".len())]
&[empty_range(2, "αβ".len())]
);
view.move_right(&MoveRight, cx);
assert_eq!(
view.selections.display_ranges(cx),
&[empty_range(2, "αβε".len())]
&[empty_range(2, "αβε".len())]
);
view.move_up(&MoveUp, cx);
assert_eq!(
view.selections.display_ranges(cx),
&[empty_range(1, "abe".len())]
&[empty_range(1, "abe".len())]
);
view.move_up(&MoveUp, cx);
assert_eq!(
view.selections.display_ranges(cx),
&[empty_range(0, "ⓐⓑ".len())]
&[empty_range(0, "ⓐⓑ".len())]
);
view.move_left(&MoveLeft, cx);
assert_eq!(
view.selections.display_ranges(cx),
&[empty_range(0, "ⓐⓑ".len())]
&[empty_range(0, "ⓐⓑ".len())]
);
view.move_left(&MoveLeft, cx);
assert_eq!(
@@ -2121,7 +2119,6 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
Point::new(2, 3)..Point::new(4, 1),
Point::new(7, 0)..Point::new(8, 4),
],
true,
cx,
);
view.change_selections(None, cx, |s| {
@@ -2134,13 +2131,13 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
});
assert_eq!(
view.display_text(cx),
"aabbb\nccceeee\nfffff\nggggg\ni\njjjjj"
"aabbb\nccceeee\nfffff\nggggg\ni\njjjjj"
);
view.move_line_up(&MoveLineUp, cx);
assert_eq!(
view.display_text(cx),
"aabbb\nccceeee\nggggg\ni\njjjjj\nfffff"
"aabbb\nccceeee\nggggg\ni\njjjjj\nfffff"
);
assert_eq!(
view.selections.display_ranges(cx),
@@ -2157,7 +2154,7 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
view.move_line_down(&MoveLineDown, cx);
assert_eq!(
view.display_text(cx),
"ccceeee\naabbb\nfffff\nggggg\ni\njjjjj"
"ccceeee\naabbb\nfffff\nggggg\ni\njjjjj"
);
assert_eq!(
view.selections.display_ranges(cx),
@@ -2174,7 +2171,7 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
view.move_line_down(&MoveLineDown, cx);
assert_eq!(
view.display_text(cx),
"ccceeee\nfffff\naabbb\nggggg\ni\njjjjj"
"ccceeee\nfffff\naabbb\nggggg\ni\njjjjj"
);
assert_eq!(
view.selections.display_ranges(cx),
@@ -2191,7 +2188,7 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) {
view.move_line_up(&MoveLineUp, cx);
assert_eq!(
view.display_text(cx),
"ccceeee\naabbb\nggggg\ni\njjjjj\nfffff"
"ccceeee\naabbb\nggggg\ni\njjjjj\nfffff"
);
assert_eq!(
view.selections.display_ranges(cx),
@@ -2589,7 +2586,6 @@ fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
Point::new(2, 3)..Point::new(4, 1),
Point::new(7, 0)..Point::new(8, 4),
],
true,
cx,
);
view.change_selections(None, cx, |s| {
@@ -2600,14 +2596,14 @@ fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) {
DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
])
});
assert_eq!(view.display_text(cx), "aabbb\nccceeee\nfffff\nggggg\ni");
assert_eq!(view.display_text(cx), "aabbb\nccceeee\nfffff\nggggg\ni");
});
view.update(cx, |view, cx| {
view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
assert_eq!(
view.display_text(cx),
"aaaaa\nbbbbb\nccceeee\nfffff\nggggg\ni"
"aaaaa\nbbbbb\nccceeee\nfffff\nggggg\ni"
);
assert_eq!(
view.selections.display_ranges(cx),
@@ -2987,7 +2983,6 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
Point::new(0, 21)..Point::new(0, 24),
Point::new(3, 20)..Point::new(3, 22),
],
true,
cx,
);
view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
@@ -4198,9 +4193,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
let format = editor.update(cx, |editor, cx| {
editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
});
let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
fake_server
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
@@ -4232,9 +4225,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
futures::future::pending::<()>().await;
unreachable!()
});
let format = editor.update(cx, |editor, cx| {
editor.perform_format(project, FormatTrigger::Manual, cx)
});
let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
cx.foreground().start_waiting();
format.await.unwrap();
@@ -4301,121 +4292,6 @@ async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
"});
}
#[gpui::test]
async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
cx.foreground().forbid_parking();
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
document_formatting_provider: Some(lsp::OneOf::Left(true)),
..Default::default()
},
cx,
)
.await;
// Set up a buffer white some trailing whitespace and no trailing newline.
cx.set_state(
&[
"one ", //
"twoˇ", //
"three ", //
"four", //
]
.join("\n"),
);
// Submit a format request.
let format = cx
.update_editor(|editor, cx| editor.format(&Format, cx))
.unwrap();
// Record which buffer changes have been sent to the language server
let buffer_changes = Arc::new(Mutex::new(Vec::new()));
cx.lsp
.handle_notification::<lsp::notification::DidChangeTextDocument, _>({
let buffer_changes = buffer_changes.clone();
move |params, _| {
buffer_changes.lock().extend(
params
.content_changes
.into_iter()
.map(|e| (e.range.unwrap(), e.text)),
);
}
});
// Handle formatting requests to the language server.
cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
let buffer_changes = buffer_changes.clone();
move |_, _| {
// When formatting is requested, trailing whitespace has already been stripped,
// and the trailing newline has already been added.
assert_eq!(
&buffer_changes.lock()[1..],
&[
(
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
"".into()
),
(
lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
"".into()
),
(
lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
"\n".into()
),
]
);
// Insert blank lines between each line of the buffer.
async move {
Ok(Some(vec![
lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
new_text: "\n".into(),
},
lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
new_text: "\n".into(),
},
]))
}
}
});
// After formatting the buffer, the trailing whitespace is stripped,
// a newline is appended, and the edits provided by the language server
// have been applied.
format.await.unwrap();
cx.assert_editor_state(
&[
"one", //
"", //
"twoˇ", //
"", //
"three", //
"four", //
"", //
]
.join("\n"),
);
// Undoing the formatting undoes the trailing whitespace removal, the
// trailing newline, and the LSP edits.
cx.update_buffer(|buffer, cx| buffer.undo(cx));
cx.assert_editor_state(
&[
"one ", //
"twoˇ", //
"three ", //
"four", //
]
.join("\n"),
);
}
#[gpui::test]
async fn test_completion(cx: &mut gpui::TestAppContext) {
let mut cx = EditorLspTestContext::new_rust(

View File

@@ -4,7 +4,7 @@ use super::{
ToPoint, MAX_LINE_LEN,
};
use crate::{
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock},
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
git::{diff_hunk_to_display, DisplayDiffHunk},
hover_popover::{
HideHover, HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
@@ -14,7 +14,7 @@ use crate::{
},
mouse_context_menu::DeployMouseContextMenu,
scroll::actions::Scroll,
EditorStyle, GutterHover, UnfoldAt,
EditorStyle,
};
use clock::ReplicaId;
use collections::{BTreeMap, HashMap};
@@ -48,9 +48,6 @@ use std::{
ops::{DerefMut, Range},
sync::Arc,
};
use workspace::item::Item;
enum FoldMarkers {}
struct SelectionLayout {
head: DisplayPoint,
@@ -215,17 +212,6 @@ impl EditorElement {
}
}),
);
enum GutterHandlers {}
cx.scene.push_mouse_region(
MouseRegion::new::<GutterHandlers>(view.id(), view.id() + 1, gutter_bounds).on_hover(
|hover, cx| {
cx.dispatch_action(GutterHover {
hovered: hover.started,
})
},
),
)
}
fn mouse_down(
@@ -414,7 +400,16 @@ impl EditorElement {
) -> bool {
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
// Don't trigger hover popover if mouse is hovering over context menu
let point = position_to_display_point(position, text_bounds, position_map);
let point = if text_bounds.contains_point(position) {
let (point, target_point) = position_map.point_for_position(text_bounds, position);
if point == target_point {
Some(point)
} else {
None
}
} else {
None
};
cx.dispatch_action(UpdateGoToDefinitionLink {
point,
@@ -423,7 +418,6 @@ impl EditorElement {
});
cx.dispatch_action(HoverAt { point });
true
}
@@ -575,25 +569,12 @@ impl EditorElement {
}
if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() {
let mut x = 0.;
let mut x = bounds.width() - layout.gutter_padding;
let mut y = *row as f32 * line_height - scroll_top;
x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
y += (line_height - indicator.size().y()) / 2.;
indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
}
layout.fold_indicators.as_mut().map(|fold_indicators| {
for (line, fold_indicator) in fold_indicators.iter_mut() {
let mut x = bounds.width() - layout.gutter_padding;
let mut y = *line as f32 * line_height - scroll_top;
x += ((layout.gutter_padding + layout.gutter_margin) - fold_indicator.size().x())
/ 2.;
y += (line_height - fold_indicator.size().y()) / 2.;
fold_indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
}
});
}
fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
@@ -695,7 +676,6 @@ impl EditorElement {
let max_glyph_width = layout.position_map.em_width;
let scroll_left = scroll_position.x() * max_glyph_width;
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
let line_end_overshoot = 0.15 * layout.position_map.line_height;
cx.scene.push_layer(Some(bounds));
@@ -708,54 +688,12 @@ impl EditorElement {
},
});
let fold_corner_radius =
self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
for (id, range, color) in layout.fold_ranges.iter() {
self.paint_highlighted_range(
range.clone(),
*color,
fold_corner_radius,
fold_corner_radius * 2.,
layout,
content_origin,
scroll_top,
scroll_left,
bounds,
cx,
);
for bound in range_to_bounds(
&range,
content_origin,
scroll_left,
scroll_top,
&layout.visible_display_row_range,
line_end_overshoot,
&layout.position_map,
) {
cx.scene.push_cursor_region(CursorRegion {
bounds: bound,
style: CursorStyle::PointingHand,
});
let display_row = range.start.row();
cx.scene.push_mouse_region(
MouseRegion::new::<FoldMarkers>(self.view.id(), *id as usize, bound)
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(UnfoldAt { display_row })
})
.with_notify_on_hover(true)
.with_notify_on_click(true),
)
}
}
for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range(
range.clone(),
*color,
0.,
line_end_overshoot,
0.15 * layout.position_map.line_height,
layout,
content_origin,
scroll_top,
@@ -766,10 +704,9 @@ impl EditorElement {
}
let mut cursors = SmallVec::<[Cursor; 32]>::new();
let corner_radius = 0.15 * layout.position_map.line_height;
for (replica_id, selections) in &layout.selections {
let selection_style = style.replica_selection_style(*replica_id);
let corner_radius = 0.15 * layout.position_map.line_height;
for selection in selections {
self.paint_highlighted_range(
@@ -1181,24 +1118,6 @@ impl EditorElement {
.width()
}
fn get_fold_indicators(
&self,
is_singleton: bool,
display_rows: Range<u32>,
snapshot: &EditorSnapshot,
) -> Option<Vec<(u32, FoldStatus)>> {
is_singleton.then(|| {
display_rows
.into_iter()
.filter_map(|display_row| {
snapshot
.fold_for_line(display_row)
.map(|fold_status| (display_row, fold_status))
})
.collect()
})
}
//Folds contained in a hunk are ignored apart from shrinking visual size
//If a fold contains any hunks then that fold line is marked as modified
fn layout_git_gutters(
@@ -1519,7 +1438,7 @@ impl EditorElement {
} else {
let text_style = self.style.text.clone();
Flex::row()
.with_child(Label::new("", text_style).boxed())
.with_child(Label::new("", text_style).boxed())
.with_children(jump_icon)
.contained()
.with_padding_left(gutter_padding)
@@ -1687,13 +1606,9 @@ impl Element for EditorElement {
let mut active_rows = BTreeMap::new();
let mut highlighted_rows = None;
let mut highlighted_ranges = Vec::new();
let mut fold_ranges = Vec::new();
let mut show_scrollbars = false;
let mut include_root = false;
let mut is_singleton = false;
self.update_view(cx.app, |view, cx| {
is_singleton = view.is_singleton(cx);
let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx));
highlighted_rows = view.highlighted_rows();
@@ -1701,19 +1616,6 @@ impl Element for EditorElement {
highlighted_ranges =
view.background_highlights_in_range(start_anchor..end_anchor, &display_map, theme);
fold_ranges.extend(
snapshot
.folds_in_range(start_anchor..end_anchor)
.map(|anchor| {
let start = anchor.start.to_point(&snapshot.buffer_snapshot);
(
start.row,
start.to_display_point(&snapshot.display_snapshot)
..anchor.end.to_display_point(&snapshot),
)
}),
);
let mut remote_selections = HashMap::default();
for (replica_id, line_mode, cursor_shape, selection) in display_map
.buffer_snapshot
@@ -1782,28 +1684,11 @@ impl Element for EditorElement {
.unwrap_or_default()
});
let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)> = fold_ranges
.into_iter()
.map(|(id, fold)| {
let color = self
.style
.folds
.ellipses
.background
.style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize), false)
.color;
(id, fold, color)
})
.collect();
let line_number_layouts =
self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx);
let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
let folds = self.get_fold_indicators(is_singleton, start_row..end_row, &snapshot);
let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines);
let mut max_visible_line_width = 0.0;
@@ -1870,7 +1755,7 @@ impl Element for EditorElement {
let mut code_actions_indicator = None;
let mut hover = None;
let mut mode = EditorMode::Full;
let mut fold_indicators = cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
let newest_selection_head = view
.selections
.newest::<usize>(cx)
@@ -1884,26 +1769,14 @@ impl Element for EditorElement {
view.render_context_menu(newest_selection_head, style.clone(), cx);
}
let active = matches!(view.context_menu, Some(crate::ContextMenu::CodeActions(_)));
code_actions_indicator = view
.render_code_actions_indicator(&style, active, cx)
.render_code_actions_indicator(&style, cx)
.map(|indicator| (newest_selection_head.row(), indicator));
}
let visible_rows = start_row..start_row + line_layouts.len() as u32;
hover = view.hover_state.render(&snapshot, &style, visible_rows, cx);
mode = view.mode;
view.render_fold_indicators(
folds,
&active_rows,
&style,
view.gutter_hovered,
line_height,
gutter_margin,
cx,
)
});
if let Some((_, context_menu)) = context_menu.as_mut() {
@@ -1929,18 +1802,6 @@ impl Element for EditorElement {
);
}
fold_indicators.as_mut().map(|fold_indicators| {
for (_, indicator) in fold_indicators.iter_mut() {
indicator.layout(
SizeConstraint::strict_along(
Axis::Vertical,
line_height * style.code_actions.vertical_scale,
),
cx,
);
}
});
if let Some((_, hover_popovers)) = hover.as_mut() {
for hover_popover in hover_popovers.iter_mut() {
hover_popover.layout(
@@ -1984,14 +1845,12 @@ impl Element for EditorElement {
active_rows,
highlighted_rows,
highlighted_ranges,
fold_ranges,
line_number_layouts,
display_hunks,
blocks,
selections,
context_menu,
code_actions_indicator,
fold_indicators,
hover_popovers: hover,
},
)
@@ -2099,8 +1958,6 @@ impl Element for EditorElement {
}
}
type BufferRow = u32;
pub struct LayoutState {
position_map: Arc<PositionMap>,
gutter_size: Vector2F,
@@ -2115,7 +1972,6 @@ pub struct LayoutState {
display_hunks: Vec<DisplayDiffHunk>,
blocks: Vec<BlockLayout>,
highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)>,
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
scrollbar_row_range: Range<f32>,
show_scrollbars: bool,
@@ -2123,7 +1979,6 @@ pub struct LayoutState {
context_menu: Option<(DisplayPoint, ElementBox)>,
code_actions_indicator: Option<(u32, ElementBox)>,
hover_popovers: Option<(DisplayPoint, Vec<ElementBox>)>,
fold_indicators: Option<Vec<(u32, ElementBox)>>,
}
pub struct PositionMap {
@@ -2422,75 +2277,6 @@ impl HighlightedRange {
}
}
pub fn position_to_display_point(
position: Vector2F,
text_bounds: RectF,
position_map: &PositionMap,
) -> Option<DisplayPoint> {
if text_bounds.contains_point(position) {
let (point, target_point) = position_map.point_for_position(text_bounds, position);
if point == target_point {
Some(point)
} else {
None
}
} else {
None
}
}
pub fn range_to_bounds(
range: &Range<DisplayPoint>,
content_origin: Vector2F,
scroll_left: f32,
scroll_top: f32,
visible_row_range: &Range<u32>,
line_end_overshoot: f32,
position_map: &PositionMap,
) -> impl Iterator<Item = RectF> {
let mut bounds: SmallVec<[RectF; 1]> = SmallVec::new();
if range.start == range.end {
return bounds.into_iter();
}
let start_row = visible_row_range.start;
let end_row = visible_row_range.end;
let row_range = if range.end.column() == 0 {
cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
} else {
cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
};
let first_y =
content_origin.y() + row_range.start as f32 * position_map.line_height - scroll_top;
for (idx, row) in row_range.enumerate() {
let line_layout = &position_map.line_layouts[(row - start_row) as usize];
let start_x = if row == range.start.row() {
content_origin.x() + line_layout.x_for_index(range.start.column() as usize)
- scroll_left
} else {
content_origin.x() - scroll_left
};
let end_x = if row == range.end.row() {
content_origin.x() + line_layout.x_for_index(range.end.column() as usize) - scroll_left
} else {
content_origin.x() + line_layout.width() + line_end_overshoot - scroll_left
};
bounds.push(RectF::from_points(
vec2f(start_x, first_y + position_map.line_height * idx as f32),
vec2f(end_x, first_y + position_map.line_height * (idx + 1) as f32),
))
}
bounds.into_iter()
}
pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
delta.powf(1.5) / 100.0
}

View File

@@ -14,7 +14,7 @@ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
SelectionGoal,
};
use project::{FormatTrigger, Item as _, Project, ProjectPath};
use project::{Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view};
use settings::Settings;
use smallvec::SmallVec;
@@ -608,7 +608,7 @@ impl Item for Editor {
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.report_event("save editor", cx);
let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
let format = self.perform_format(project.clone(), cx);
let buffers = self.buffer().clone().read(cx).all_buffers();
cx.as_mut().spawn(|mut cx| async move {
format.await?;
@@ -886,7 +886,7 @@ impl SearchableItem for Editor {
matches: Vec<Range<Anchor>>,
cx: &mut ViewContext<Self>,
) {
self.unfold_ranges([matches[index].clone()], false, true, cx);
self.unfold_ranges([matches[index].clone()], false, cx);
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([matches[index].clone()])
});

View File

@@ -39,7 +39,7 @@ impl<'a> EditorLspTestContext<'a> {
pane::init(cx);
});
let app_state = cx.update(AppState::test);
let params = cx.update(AppState::test);
let file_name = format!(
"file.{}",
@@ -56,10 +56,10 @@ impl<'a> EditorLspTestContext<'a> {
}))
.await;
let project = Project::test(app_state.fs.clone(), [], cx).await;
let project = Project::test(params.fs.clone(), [], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
app_state
params
.fs
.as_fake()
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))

View File

@@ -185,7 +185,6 @@ impl<'a> EditorTestContext<'a> {
/// of its selections using a string containing embedded range markers.
///
/// See the `util::test::marked_text_ranges` function for more information.
#[track_caller]
pub fn assert_editor_state(&mut self, marked_text: &str) {
let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
let buffer_text = self.buffer_text();

View File

@@ -20,12 +20,7 @@ impl_actions!(zed, [OpenBrowser]);
actions!(
zed,
[
CopySystemSpecsIntoClipboard,
FileBugReport,
RequestFeature,
OpenZedCommunityRepo
]
[CopySystemSpecsIntoClipboard, FileBugReport, RequestFeature]
);
pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
@@ -71,11 +66,4 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
});
},
);
cx.add_action(
|_: &mut Workspace, _: &OpenZedCommunityRepo, cx: &mut ViewContext<Workspace>| {
let url = "https://github.com/zed-industries/community";
cx.dispatch_action(OpenBrowser { url: url.into() });
},
);
}

View File

@@ -1,12 +1,10 @@
use gpui::{
elements::{Flex, Label, MouseEventHandler, ParentElement, Text},
CursorStyle, Element, ElementBox, Entity, MouseButton, RenderContext, View, ViewContext,
ViewHandle,
elements::Label, Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
};
use settings::Settings;
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
use crate::{feedback_editor::FeedbackEditor, OpenZedCommunityRepo};
use crate::feedback_editor::FeedbackEditor;
pub struct FeedbackInfoText {
active_item: Option<ViewHandle<FeedbackEditor>>,
@@ -31,44 +29,9 @@ impl View for FeedbackInfoText {
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
let theme = cx.global::<Settings>().theme.clone();
Flex::row()
.with_child(
Text::new(
"We read whatever you submit here. For issues and discussions, visit the ",
theme.feedback.info_text_default.text.clone(),
)
.with_soft_wrap(false)
.aligned()
.boxed(),
)
.with_child(
MouseEventHandler::<OpenZedCommunityRepo>::new(0, cx, |state, _| {
let contained_text = if state.hovered() {
&theme.feedback.link_text_hover
} else {
&theme.feedback.link_text_default
};
Label::new("community repo", contained_text.text.clone())
.contained()
.aligned()
.left()
.clipped()
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, cx| {
cx.dispatch_action(OpenZedCommunityRepo)
})
.boxed(),
)
.with_child(
Text::new(" on GitHub.", theme.feedback.info_text_default.text.clone())
.with_soft_wrap(false)
.aligned()
.boxed(),
)
let text = "We read whatever you submit here. For issues and discussions, visit the community repo on GitHub.";
Label::new(text, theme.feedback.info_text.text.clone())
.contained()
.aligned()
.left()
.clipped()

View File

@@ -23,7 +23,6 @@ pub struct FileFinder {
latest_search_id: usize,
latest_search_did_cancel: bool,
latest_search_query: String,
relative_to: Option<Arc<Path>>,
matches: Vec<PathMatch>,
selected: Option<(usize, Arc<Path>)>,
cancel_flag: Arc<AtomicBool>,
@@ -91,11 +90,7 @@ impl FileFinder {
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
workspace.toggle_modal(cx, |workspace, cx| {
let project = workspace.project().clone();
let relative_to = workspace
.active_item(cx)
.and_then(|item| item.project_path(cx))
.map(|project_path| project_path.path.clone());
let finder = cx.add_view(|cx| Self::new(project, relative_to, cx));
let finder = cx.add_view(|cx| Self::new(project, cx));
cx.subscribe(&finder, Self::on_event).detach();
finder
});
@@ -120,11 +115,7 @@ impl FileFinder {
}
}
pub fn new(
project: ModelHandle<Project>,
relative_to: Option<Arc<Path>>,
cx: &mut ViewContext<Self>,
) -> Self {
pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
let handle = cx.weak_handle();
cx.observe(&project, Self::project_updated).detach();
Self {
@@ -134,7 +125,6 @@ impl FileFinder {
latest_search_id: 0,
latest_search_did_cancel: false,
latest_search_query: String::new(),
relative_to,
matches: Vec::new(),
selected: None,
cancel_flag: Arc::new(AtomicBool::new(false)),
@@ -147,7 +137,6 @@ impl FileFinder {
}
fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
let relative_to = self.relative_to.clone();
let worktrees = self
.project
.read(cx)
@@ -176,7 +165,6 @@ impl FileFinder {
let matches = fuzzy::match_path_sets(
candidate_sets.as_slice(),
&query,
relative_to,
false,
100,
&cancel_flag,
@@ -389,7 +377,7 @@ mod tests {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
let query = "hi".to_string();
finder
@@ -465,7 +453,7 @@ mod tests {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
finder
.update(cx, |f, cx| f.spawn_search("hi".into(), cx))
.await;
@@ -491,7 +479,7 @@ mod tests {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@@ -544,9 +532,8 @@ mod tests {
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
// Run a search that matches two files with the same relative path.
finder
@@ -564,48 +551,6 @@ mod tests {
});
}
#[gpui::test]
async fn test_path_distance_ordering(cx: &mut gpui::TestAppContext) {
cx.foreground().forbid_parking();
let app_state = cx.update(AppState::test);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"dir1": { "a.txt": "" },
"dir2": {
"a.txt": "",
"b.txt": ""
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
// When workspace has an active item, sort items which are closer to that item
// first when they have the same name. In this case, b.txt is closer to dir2's a.txt
// so that one should be sorted earlier
let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt")));
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), b_path, cx));
finder
.update(cx, |f, cx| f.spawn_search("a.txt".into(), cx))
.await;
finder.read_with(cx, |f, _| {
assert_eq!(f.matches[0].path.as_ref(), Path::new("dir2/a.txt"));
assert_eq!(f.matches[1].path.as_ref(), Path::new("dir1/a.txt"));
});
}
#[gpui::test]
async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) {
let app_state = cx.update(AppState::test);
@@ -628,7 +573,7 @@ mod tests {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
finder
.update(cx, |f, cx| f.spawn_search("dir".into(), cx))
.await;

View File

@@ -443,7 +443,6 @@ mod tests {
positions: Vec::new(),
path: candidate.path.clone(),
path_prefix: "".into(),
distance_to_relative_ancestor: usize::MAX,
},
);

View File

@@ -25,9 +25,6 @@ pub struct PathMatch {
pub worktree_id: usize,
pub path: Arc<Path>,
pub path_prefix: Arc<str>,
/// Number of steps removed from a shared parent with the relative path
/// Used to order closer paths first in the search list
pub distance_to_relative_ancestor: usize,
}
pub trait PathMatchCandidateSet<'a>: Send + Sync {
@@ -81,11 +78,6 @@ impl Ord for PathMatch {
.partial_cmp(&other.score)
.unwrap_or(Ordering::Equal)
.then_with(|| self.worktree_id.cmp(&other.worktree_id))
.then_with(|| {
other
.distance_to_relative_ancestor
.cmp(&self.distance_to_relative_ancestor)
})
.then_with(|| self.path.cmp(&other.path))
}
}
@@ -93,7 +85,6 @@ impl Ord for PathMatch {
pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
candidate_sets: &'a [Set],
query: &str,
relative_to: Option<Arc<Path>>,
smart_case: bool,
max_results: usize,
cancel_flag: &AtomicBool,
@@ -120,7 +111,6 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
background
.scoped(|scope| {
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
let relative_to = relative_to.clone();
scope.spawn(async move {
let segment_start = segment_idx * segment_size;
let segment_end = segment_start + segment_size;
@@ -159,15 +149,6 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
positions: Vec::new(),
path: candidate.path.clone(),
path_prefix: candidate_set.prefix(),
distance_to_relative_ancestor: relative_to.as_ref().map_or(
usize::MAX,
|relative_to| {
distance_between_paths(
candidate.path.as_ref(),
relative_to.as_ref(),
)
},
),
},
);
}
@@ -191,30 +172,3 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
}
results
}
/// Compute the distance from a given path to some other path
/// If there is no shared path, returns usize::MAX
fn distance_between_paths(path: &Path, relative_to: &Path) -> usize {
let mut path_components = path.components();
let mut relative_components = relative_to.components();
while path_components
.next()
.zip(relative_components.next())
.map(|(path_component, relative_component)| path_component == relative_component)
.unwrap_or_default()
{}
path_components.count() + relative_components.count() + 1
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::distance_between_paths;
#[test]
fn test_distance_between_paths_empty() {
distance_between_paths(Path::new(""), Path::new(""));
}
}

View File

@@ -86,7 +86,7 @@ pub trait View: Entity + Sized {
}
fn default_keymap_context() -> keymap_matcher::KeymapContext {
let mut cx = keymap_matcher::KeymapContext::default();
cx.add_identifier(Self::ui_name());
cx.set.insert(Self::ui_name().into());
cx
}
fn debug_json(&self, _: &AppContext) -> serde_json::Value {
@@ -4159,10 +4159,10 @@ pub struct RenderContext<'a, T: View> {
#[derive(Debug, Clone, Default)]
pub struct MouseState {
pub(crate) hovered: bool,
pub(crate) clicked: Option<MouseButton>,
pub(crate) accessed_hovered: bool,
pub(crate) accessed_clicked: bool,
hovered: bool,
clicked: Option<MouseButton>,
accessed_hovered: bool,
accessed_clicked: bool,
}
impl MouseState {
@@ -6639,12 +6639,12 @@ mod tests {
let mut view_1 = View::new(1);
let mut view_2 = View::new(2);
let mut view_3 = View::new(3);
view_1.keymap_context.add_identifier("a");
view_2.keymap_context.add_identifier("a");
view_2.keymap_context.add_identifier("b");
view_3.keymap_context.add_identifier("a");
view_3.keymap_context.add_identifier("b");
view_3.keymap_context.add_identifier("c");
view_1.keymap_context.set.insert("a".into());
view_2.keymap_context.set.insert("a".into());
view_2.keymap_context.set.insert("b".into());
view_3.keymap_context.set.insert("a".into());
view_3.keymap_context.set.insert("b".into());
view_3.keymap_context.set.insert("c".into());
let (window_id, view_1) = cx.add_window(Default::default(), |_| view_1);
let view_2 = cx.add_view(&view_1, |_| view_2);

View File

@@ -16,14 +16,6 @@ pub trait Action: 'static {
Self: Sized;
}
impl std::fmt::Debug for dyn Action {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("dyn Action")
.field("namespace", &self.namespace())
.field("name", &self.name())
.finish()
}
}
/// Define a set of unit struct types that all implement the `Action` trait.
///
/// The first argument is a namespace that will be associated with each of

View File

@@ -18,10 +18,9 @@ use smol::stream::StreamExt;
use crate::{
executor, geometry::vector::Vector2F, keymap_matcher::Keystroke, platform, Action,
AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, Handle, InputHandler,
KeyDownEvent, ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith,
ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle,
WeakHandle,
AnyViewHandle, AppContext, Appearance, Entity, Event, FontCache, InputHandler, KeyDownEvent,
ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith, ReadViewWith,
RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle, WeakHandle,
};
use collections::BTreeMap;
@@ -330,14 +329,6 @@ impl TestAppContext {
.assert_dropped(handle.id())
}
/// Drop a handle, assuming it is the last. If it is not the last, panic with debug information about
/// where the stray handles were created.
pub fn drop_last<T, W: WeakHandle, H: Handle<T, Weak = W>>(&mut self, handle: H) {
let weak = handle.downgrade();
self.update(|_| drop(handle));
self.assert_dropped(weak);
}
fn window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
let (_, window) = state

View File

@@ -296,10 +296,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
paint,
}
}
Lifecycle::Empty => panic!("invalid element lifecycle state"),
Lifecycle::Init { .. } => {
panic!("invalid element lifecycle state, paint called before layout")
}
_ => panic!("invalid element lifecycle state"),
}
}

View File

@@ -5,7 +5,7 @@ mod keystroke;
use std::{any::TypeId, fmt::Debug};
use collections::HashMap;
use collections::{BTreeMap, HashMap};
use smallvec::SmallVec;
use crate::Action;
@@ -68,8 +68,8 @@ impl KeymapMatcher {
/// There exist bindings which are still waiting for more keys.
/// MatchResult::Complete(matches) =>
/// 1 or more bindings have recieved the necessary key presses.
/// The order of the matched actions is by position of the matching first,
// and order in the keymap second.
/// The order of the matched actions is by order in the keymap file first and
/// position of the matching view second.
pub fn push_keystroke(
&mut self,
keystroke: Keystroke,
@@ -80,7 +80,8 @@ impl KeymapMatcher {
// and then the order the binding matched in the view tree second.
// The key is the reverse position of the binding in the bindings list so that later bindings
// match before earlier ones in the user's config
let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
let mut matched_bindings: BTreeMap<usize, Vec<(usize, Box<dyn Action>)>> =
Default::default();
let first_keystroke = self.pending_keystrokes.is_empty();
self.pending_keystrokes.push(keystroke.clone());
@@ -104,11 +105,14 @@ impl KeymapMatcher {
}
}
for binding in self.keymap.bindings().iter().rev() {
for (order, binding) in self.keymap.bindings().iter().rev().enumerate() {
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
{
BindingMatchResult::Complete(action) => {
matched_bindings.push((*view_id, action));
matched_bindings
.entry(order)
.or_default()
.push((*view_id, action));
}
BindingMatchResult::Partial => {
self.pending_views
@@ -127,7 +131,7 @@ impl KeymapMatcher {
if !matched_bindings.is_empty() {
// Collect the sorted matched bindings into the final vec for ease of use
// Matched bindings are in order by precedence
MatchResult::Matches(matched_bindings)
MatchResult::Matches(matched_bindings.into_values().flatten().collect())
} else if any_pending {
MatchResult::Pending
} else {
@@ -221,47 +225,15 @@ mod tests {
use super::*;
#[test]
fn test_keymap_and_view_ordering() -> Result<()> {
actions!(test, [EditorAction, ProjectPanelAction]);
let mut editor = KeymapContext::default();
editor.add_identifier("Editor");
let mut project_panel = KeymapContext::default();
project_panel.add_identifier("ProjectPanel");
// Editor 'deeper' in than project panel
let dispatch_path = vec![(2, editor), (1, project_panel)];
// But editor actions 'higher' up in keymap
let keymap = Keymap::new(vec![
Binding::new("left", EditorAction, Some("Editor")),
Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
]);
let mut matcher = KeymapMatcher::new(keymap);
assert_eq!(
matcher.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
MatchResult::Matches(vec![
(2, Box::new(EditorAction)),
(1, Box::new(ProjectPanelAction)),
]),
);
Ok(())
}
#[test]
fn test_push_keystroke() -> Result<()> {
actions!(test, [B, AB, C, D, DA, E, EF]);
let mut context1 = KeymapContext::default();
context1.add_identifier("1");
context1.set.insert("1".into());
let mut context2 = KeymapContext::default();
context2.add_identifier("2");
context2.set.insert("2".into());
let dispatch_path = vec![(2, context2), (1, context1)];
@@ -395,22 +367,22 @@ mod tests {
let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
let mut context = KeymapContext::default();
context.add_identifier("a");
context.set.insert("a".into());
assert!(!predicate.eval(&[context]));
let mut context = KeymapContext::default();
context.add_identifier("a");
context.add_identifier("b");
context.set.insert("a".into());
context.set.insert("b".into());
assert!(predicate.eval(&[context]));
let mut context = KeymapContext::default();
context.add_identifier("a");
context.add_key("c", "x");
context.set.insert("a".into());
context.map.insert("c".into(), "x".into());
assert!(!predicate.eval(&[context]));
let mut context = KeymapContext::default();
context.add_identifier("a");
context.add_key("c", "d");
context.set.insert("a".into());
context.map.insert("c".into(), "d".into());
assert!(predicate.eval(&[context]));
let predicate = KeymapContextPredicate::parse("!a").unwrap();
@@ -450,11 +422,10 @@ mod tests {
assert!(!predicate.eval(&contexts[6..]));
fn context_set(names: &[&str]) -> KeymapContext {
let mut keymap = KeymapContext::new();
names
.iter()
.for_each(|name| keymap.add_identifier(name.to_string()));
keymap
KeymapContext {
set: names.iter().copied().map(str::to_string).collect(),
..Default::default()
}
}
}
@@ -477,10 +448,10 @@ mod tests {
]);
let mut context_a = KeymapContext::default();
context_a.add_identifier("a");
context_a.set.insert("a".into());
let mut context_b = KeymapContext::default();
context_b.add_identifier("b");
context_b.set.insert("b".into());
let mut matcher = KeymapMatcher::new(keymap);
@@ -525,7 +496,7 @@ mod tests {
matcher.clear_pending();
let mut context_c = KeymapContext::default();
context_c.add_identifier("c");
context_c.set.insert("c".into());
// Pending keystrokes are maintained per-view
assert_eq!(

View File

@@ -1,22 +1,13 @@
use std::borrow::Cow;
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct KeymapContext {
set: HashSet<Cow<'static, str>>,
map: HashMap<Cow<'static, str>, Cow<'static, str>>,
pub set: HashSet<String>,
pub map: HashMap<String, String>,
}
impl KeymapContext {
pub fn new() -> Self {
KeymapContext {
set: HashSet::default(),
map: HashMap::default(),
}
}
pub fn extend(&mut self, other: &Self) {
for v in &other.set {
self.set.insert(v.clone());
@@ -25,18 +16,6 @@ impl KeymapContext {
self.map.insert(k.clone(), v.clone());
}
}
pub fn add_identifier<I: Into<Cow<'static, str>>>(&mut self, identifier: I) {
self.set.insert(identifier.into());
}
pub fn add_key<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>>(
&mut self,
key: S1,
value: S2,
) {
self.map.insert(key.into(), value.into());
}
}
#[derive(Debug, Eq, PartialEq)]
@@ -67,12 +46,12 @@ impl KeymapContextPredicate {
Self::Identifier(name) => (&context.set).contains(name.as_str()),
Self::Equal(left, right) => context
.map
.get(left.as_str())
.get(left)
.map(|value| value == right)
.unwrap_or(false),
Self::NotEqual(left, right) => context
.map
.get(left.as_str())
.get(left)
.map(|value| value != right)
.unwrap_or(true),
Self::Not(pred) => !pred.eval(contexts),

View File

@@ -85,12 +85,16 @@ impl SpriteCache {
) -> Option<GlyphSprite> {
const SUBPIXEL_VARIANTS: u8 = 4;
let target_position = target_position * self.scale_factor;
let scale_factor = self.scale_factor;
let target_position = target_position * scale_factor;
let fonts = &self.fonts;
let atlases = &mut self.atlases;
let subpixel_variant = (
(target_position.x().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
(target_position.y().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
(target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
% SUBPIXEL_VARIANTS,
(target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
% SUBPIXEL_VARIANTS,
);
self.glyphs
.entry(GlyphDescriptor {
font_id,
@@ -103,17 +107,16 @@ impl SpriteCache {
subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
);
let (glyph_bounds, mask) = self.fonts.rasterize_glyph(
let (glyph_bounds, mask) = fonts.rasterize_glyph(
font_id,
font_size,
glyph_id,
subpixel_shift,
self.scale_factor,
scale_factor,
RasterizationOptions::Alpha,
)?;
let (alloc_id, atlas_bounds) = self
.atlases
let (alloc_id, atlas_bounds) = atlases
.upload(glyph_bounds.size(), &mask)
.expect("could not upload glyph");
Some(GlyphSprite {

View File

@@ -737,7 +737,6 @@ impl platform::Window for Window {
let title = ns_string(title);
let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
let _: () = msg_send![window, setTitle: title];
self.0.borrow().move_traffic_light();
}
}

View File

@@ -12,9 +12,9 @@ use crate::{
text_layout::TextLayoutCache,
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, Appearance,
AssetCache, ElementBox, Entity, FontSystem, ModelHandle, MouseButton, MouseMovedEvent,
MouseRegion, MouseRegionId, MouseState, ParentId, ReadModel, ReadView, RenderContext,
RenderParams, SceneBuilder, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle,
WeakModelHandle, WeakViewHandle,
MouseRegion, MouseRegionId, ParentId, ReadModel, ReadView, RenderContext, RenderParams,
SceneBuilder, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle,
WeakViewHandle,
};
use anyhow::bail;
use collections::{HashMap, HashSet};
@@ -507,18 +507,15 @@ impl Presenter {
}
// Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would
// not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
// This behavior can be overridden by adding a Down handler
// This behavior can be overridden by adding a Down handler that calls cx.propogate_event
if let MouseEvent::Down(e) = &mouse_event {
let has_click = valid_region
if valid_region
.handlers
.contains(MouseEvent::click_disc(), Some(e.button));
let has_drag = valid_region
.handlers
.contains(MouseEvent::drag_disc(), Some(e.button));
let has_down = valid_region
.handlers
.contains(MouseEvent::down_disc(), Some(e.button));
if !has_down && (has_click || has_drag) {
.contains(MouseEvent::click_disc(), Some(e.button))
|| valid_region
.handlers
.contains(MouseEvent::drag_disc(), Some(e.button))
{
event_cx.handled = true;
}
}
@@ -526,13 +523,14 @@ impl Presenter {
// `event_consumed` should only be true if there are any handlers for this event.
let mut event_consumed = event_cx.handled;
if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) {
event_consumed = true;
for callback in callbacks {
event_cx.handled = true;
event_cx.with_current_view(valid_region.id().view_id(), {
let region_event = mouse_event.clone();
|cx| callback(region_event, cx)
});
event_consumed |= event_cx.handled;
event_consumed &= event_cx.handled;
any_event_handled |= event_cx.handled;
}
}
@@ -605,24 +603,6 @@ pub struct LayoutContext<'a> {
}
impl<'a> LayoutContext<'a> {
pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
let view_id = self.view_stack.last().unwrap();
let region_id = MouseRegionId::new::<Tag>(*view_id, region_id);
MouseState {
hovered: self.hovered_region_ids.contains(&region_id),
clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| {
if ids.contains(&region_id) {
Some(*button)
} else {
None
}
}),
accessed_hovered: false,
accessed_clicked: false,
}
}
fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
let print_error = |view_id| {
format!(

View File

@@ -305,7 +305,7 @@ pub struct Chunk<'a> {
}
pub struct Diff {
pub(crate) base_version: clock::Global,
base_version: clock::Global,
line_ending: LineEnding,
edits: Vec<(Range<usize>, Arc<str>)>,
}
@@ -569,21 +569,18 @@ impl Buffer {
.read_with(&cx, |this, cx| this.diff(new_text, cx))
.await;
this.update(&mut cx, |this, cx| {
if this.version() == diff.base_version {
this.finalize_last_transaction();
this.apply_diff(diff, cx);
if let Some(transaction) = this.finalize_last_transaction().cloned() {
this.did_reload(
this.version(),
this.as_rope().fingerprint(),
this.line_ending(),
new_mtime,
cx,
);
return Ok(Some(transaction));
}
if let Some(transaction) = this.apply_diff(diff, cx).cloned() {
this.did_reload(
this.version(),
this.as_rope().fingerprint(),
this.line_ending(),
new_mtime,
cx,
);
Ok(Some(transaction))
} else {
Ok(None)
}
Ok(None)
})
} else {
Ok(None)
@@ -1157,84 +1154,20 @@ impl Buffer {
})
}
/// Spawn a background task that searches the buffer for any whitespace
/// at the ends of a lines, and returns a `Diff` that removes that whitespace.
pub fn remove_trailing_whitespace(&self, cx: &AppContext) -> Task<Diff> {
let old_text = self.as_rope().clone();
let line_ending = self.line_ending();
let base_version = self.version();
cx.background().spawn(async move {
let ranges = trailing_whitespace_ranges(&old_text);
let empty = Arc::<str>::from("");
Diff {
base_version,
line_ending,
edits: ranges
.into_iter()
.map(|range| (range, empty.clone()))
.collect(),
}
})
}
/// Ensure that the buffer ends with a single newline character, and
/// no other whitespace.
pub fn ensure_final_newline(&mut self, cx: &mut ModelContext<Self>) {
let len = self.len();
let mut offset = len;
for chunk in self.as_rope().reversed_chunks_in_range(0..len) {
let non_whitespace_len = chunk
.trim_end_matches(|c: char| c.is_ascii_whitespace())
.len();
offset -= chunk.len();
offset += non_whitespace_len;
if non_whitespace_len != 0 {
if offset == len - 1 && chunk.get(non_whitespace_len..) == Some("\n") {
return;
}
break;
pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> Option<&Transaction> {
if self.version == diff.base_version {
self.finalize_last_transaction();
self.start_transaction();
self.text.set_line_ending(diff.line_ending);
self.edit(diff.edits, None, cx);
if self.end_transaction(cx).is_some() {
self.finalize_last_transaction()
} else {
None
}
} else {
None
}
self.edit([(offset..len, "\n")], None, cx);
}
/// Apply a diff to the buffer. If the buffer has changed since the given diff was
/// calculated, then adjust the diff to account for those changes, and discard any
/// parts of the diff that conflict with those changes.
pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
// Check for any edits to the buffer that have occurred since this diff
// was computed.
let snapshot = self.snapshot();
let mut edits_since = snapshot.edits_since::<usize>(&diff.base_version).peekable();
let mut delta = 0;
let adjusted_edits = diff.edits.into_iter().filter_map(|(range, new_text)| {
while let Some(edit_since) = edits_since.peek() {
// If the edit occurs after a diff hunk, then it does not
// affect that hunk.
if edit_since.old.start > range.end {
break;
}
// If the edit precedes the diff hunk, then adjust the hunk
// to reflect the edit.
else if edit_since.old.end < range.start {
delta += edit_since.new_len() as i64 - edit_since.old_len() as i64;
edits_since.next();
}
// If the edit intersects a diff hunk, then discard that hunk.
else {
return None;
}
}
let start = (range.start as i64 + delta) as usize;
let end = (range.end as i64 + delta) as usize;
Some((start..end, new_text))
});
self.start_transaction();
self.text.set_line_ending(diff.line_ending);
self.edit(adjusted_edits, None, cx);
self.end_transaction(cx)
}
pub fn is_dirty(&self) -> bool {
@@ -2907,42 +2840,3 @@ pub fn char_kind(c: char) -> CharKind {
CharKind::Punctuation
}
}
/// Find all of the ranges of whitespace that occur at the ends of lines
/// in the given rope.
///
/// This could also be done with a regex search, but this implementation
/// avoids copying text.
pub fn trailing_whitespace_ranges(rope: &Rope) -> Vec<Range<usize>> {
let mut ranges = Vec::new();
let mut offset = 0;
let mut prev_chunk_trailing_whitespace_range = 0..0;
for chunk in rope.chunks() {
let mut prev_line_trailing_whitespace_range = 0..0;
for (i, line) in chunk.split('\n').enumerate() {
let line_end_offset = offset + line.len();
let trimmed_line_len = line.trim_end_matches(|c| matches!(c, ' ' | '\t')).len();
let mut trailing_whitespace_range = (offset + trimmed_line_len)..line_end_offset;
if i == 0 && trimmed_line_len == 0 {
trailing_whitespace_range.start = prev_chunk_trailing_whitespace_range.start;
}
if !prev_line_trailing_whitespace_range.is_empty() {
ranges.push(prev_line_trailing_whitespace_range);
}
offset = line_end_offset + 1;
prev_line_trailing_whitespace_range = trailing_whitespace_range;
}
offset -= 1;
prev_chunk_trailing_whitespace_range = prev_line_trailing_whitespace_range;
}
if !prev_chunk_trailing_whitespace_range.is_empty() {
ranges.push(prev_chunk_trailing_whitespace_range);
}
ranges
}

View File

@@ -6,7 +6,6 @@ use gpui::{ModelHandle, MutableAppContext};
use indoc::indoc;
use proto::deserialize_operation;
use rand::prelude::*;
use regex::RegexBuilder;
use settings::Settings;
use std::{
cell::RefCell,
@@ -19,13 +18,6 @@ use text::network::Network;
use unindent::Unindent as _;
use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
lazy_static! {
static ref TRAILING_WHITESPACE_REGEX: Regex = RegexBuilder::new("[ \t]+$")
.multi_line(true)
.build()
.unwrap();
}
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {
@@ -219,79 +211,6 @@ async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
});
}
#[gpui::test(iterations = 10)]
async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
let text = [
"zero", //
"one ", // 2 trailing spaces
"two", //
"three ", // 3 trailing spaces
"four", //
"five ", // 4 trailing spaces
]
.join("\n");
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
// Spawn a task to format the buffer's whitespace.
// Pause so that the foratting task starts running.
let format = buffer.read_with(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
smol::future::yield_now().await;
// Edit the buffer while the normalization task is running.
let version_before_edit = buffer.read_with(cx, |buffer, _| buffer.version());
buffer.update(cx, |buffer, cx| {
buffer.edit(
[
(Point::new(0, 1)..Point::new(0, 1), "EE"),
(Point::new(3, 5)..Point::new(3, 5), "EEE"),
],
None,
cx,
);
});
let format_diff = format.await;
buffer.update(cx, |buffer, cx| {
let version_before_format = format_diff.base_version.clone();
buffer.apply_diff(format_diff, cx);
// The outcome depends on the order of concurrent taks.
//
// If the edit occurred while searching for trailing whitespace ranges,
// then the trailing whitespace region touched by the edit is left intact.
if version_before_format == version_before_edit {
assert_eq!(
buffer.text(),
[
"zEEero", //
"one", //
"two", //
"threeEEE ", //
"four", //
"five", //
]
.join("\n")
);
}
// Otherwise, all trailing whitespace is removed.
else {
assert_eq!(
buffer.text(),
[
"zEEero", //
"one", //
"two", //
"threeEEE", //
"four", //
"five", //
]
.join("\n")
);
}
});
}
#[gpui::test]
async fn test_reparse(cx: &mut gpui::TestAppContext) {
let text = "fn a() {}";
@@ -2024,45 +1943,6 @@ fn test_contiguous_ranges() {
);
}
#[gpui::test(iterations = 500)]
fn test_trailing_whitespace_ranges(mut rng: StdRng) {
// Generate a random multi-line string containing
// some lines with trailing whitespace.
let mut text = String::new();
for _ in 0..rng.gen_range(0..16) {
for _ in 0..rng.gen_range(0..36) {
text.push(match rng.gen_range(0..10) {
0..=1 => ' ',
3 => '\t',
_ => rng.gen_range('a'..'z'),
});
}
text.push('\n');
}
match rng.gen_range(0..10) {
// sometimes remove the last newline
0..=1 => drop(text.pop()), //
// sometimes add extra newlines
2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
_ => {}
}
let rope = Rope::from(text.as_str());
let actual_ranges = trailing_whitespace_ranges(&rope);
let expected_ranges = TRAILING_WHITESPACE_REGEX
.find_iter(&text)
.map(|m| m.range())
.collect::<Vec<_>>();
assert_eq!(
actual_ranges,
expected_ranges,
"wrong ranges for text lines:\n{:?}",
text.split("\n").collect::<Vec<_>>()
);
}
fn ruby_lang() -> Language {
Language::new(
LanguageConfig {

View File

@@ -165,7 +165,6 @@ struct ParseStep {
mode: ParseMode,
}
#[derive(Debug)]
enum ParseStepLanguage {
Loaded { language: Arc<Language> },
Pending { name: Arc<str> },
@@ -515,32 +514,15 @@ impl SyntaxSnapshot {
let Some(grammar) = language.grammar() else { continue };
let tree;
let changed_ranges;
let mut included_ranges = step.included_ranges;
for range in &mut included_ranges {
range.start_byte -= step_start_byte;
range.end_byte -= step_start_byte;
range.start_point = (Point::from_ts_point(range.start_point)
- step_start_point)
.to_ts_point();
range.end_point = (Point::from_ts_point(range.end_point)
- step_start_point)
.to_ts_point();
}
if let Some(SyntaxLayerContent::Parsed { tree: old_tree, .. }) =
old_layer.map(|layer| &layer.content)
{
if let ParseMode::Combined {
mut parent_layer_changed_ranges,
parent_layer_changed_ranges,
..
} = step.mode
{
for range in &mut parent_layer_changed_ranges {
range.start -= step_start_byte;
range.end -= step_start_byte;
}
included_ranges = splice_included_ranges(
old_tree.included_ranges(),
&parent_layer_changed_ranges,
@@ -552,6 +534,7 @@ impl SyntaxSnapshot {
grammar,
text.as_rope(),
step_start_byte,
step_start_point,
included_ranges,
Some(old_tree.clone()),
);
@@ -568,6 +551,7 @@ impl SyntaxSnapshot {
grammar,
text.as_rope(),
step_start_byte,
step_start_point,
included_ranges,
None,
);
@@ -1076,9 +1060,17 @@ fn parse_text(
grammar: &Grammar,
text: &Rope,
start_byte: usize,
ranges: Vec<tree_sitter::Range>,
start_point: Point,
mut ranges: Vec<tree_sitter::Range>,
old_tree: Option<Tree>,
) -> Tree {
for range in &mut ranges {
range.start_byte -= start_byte;
range.end_byte -= start_byte;
range.start_point = (Point::from_ts_point(range.start_point) - start_point).to_ts_point();
range.end_point = (Point::from_ts_point(range.end_point) - start_point).to_ts_point();
}
PARSER.with(|parser| {
let mut parser = parser.borrow_mut();
let mut chunks = text.chunks_in_range(start_byte..text.len());
@@ -2216,37 +2208,6 @@ mod tests {
);
}
#[gpui::test]
fn test_combined_injections_inside_injections() {
let (_buffer, _syntax_map) = test_edit_sequence(
"Markdown",
&[
r#"
here is some ERB code:
```erb
<ul>
<% people.each do |person| %>
<li><%= person.name %></li>
<% end %>
</ul>
```
"#,
r#"
here is some ERB code:
```erb
<ul>
<% people«2».each do |person| %>
<li><%= person.name %></li>
<% end %>
</ul>
```
"#,
],
);
}
#[gpui::test(iterations = 50)]
fn test_random_syntax_map_edits(mut rng: StdRng) {
let operations = env::var("OPERATIONS")

View File

@@ -422,10 +422,6 @@ impl LanguageServer {
self.notification_handlers.lock().remove(T::METHOD);
}
pub fn remove_notification_handler<T: notification::Notification>(&self) {
self.notification_handlers.lock().remove(T::METHOD);
}
#[must_use]
pub fn on_custom_notification<Params, F>(&self, method: &'static str, mut f: F) -> Subscription
where
@@ -784,26 +780,6 @@ impl FakeLanguageServer {
responded_rx
}
pub fn handle_notification<T, F>(
&self,
mut handler: F,
) -> futures::channel::mpsc::UnboundedReceiver<()>
where
T: 'static + notification::Notification,
T::Params: 'static + Send,
F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext),
{
let (handled_tx, handled_rx) = futures::channel::mpsc::unbounded();
self.server.remove_notification_handler::<T>();
self.server
.on_notification::<T, _>(move |params, cx| {
handler(params, cx.clone());
handled_tx.unbounded_send(()).ok();
})
.detach();
handled_rx
}
pub fn remove_request_handler<T>(&mut self)
where
T: 'static + request::Request,

View File

@@ -126,7 +126,7 @@ impl<D: PickerDelegate> View for Picker<D> {
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut cx = Self::default_keymap_context();
cx.add_identifier("menu");
cx.set.insert("menu".into());
cx
}

View File

@@ -57,7 +57,6 @@ thiserror = "1.0.29"
toml = "0.5"
[dev-dependencies]
pretty_assertions = "1.3.0"
client = { path = "../client", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
db = { path = "../db", features = ["test-support"] }

View File

@@ -1,7 +1,6 @@
mod ignore;
mod lsp_command;
pub mod search;
pub mod terminals;
pub mod worktree;
#[cfg(test)]
@@ -27,7 +26,7 @@ use language::{
serialize_anchor, serialize_version,
},
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent,
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt,
Operation, Patch, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16,
Transaction, Unclipped,
@@ -62,8 +61,7 @@ use std::{
},
time::{Duration, Instant, SystemTime},
};
use terminals::Terminals;
use terminal::{Terminal, TerminalBuilder};
use util::{debug_panic, defer, post_inc, ResultExt, TryFutureExt as _};
pub use fs::*;
@@ -125,7 +123,6 @@ pub struct Project {
buffers_being_formatted: HashSet<usize>,
nonce: u128,
_maintain_buffer_languages: Task<()>,
terminals: Terminals,
}
enum OpenBuffer {
@@ -442,9 +439,6 @@ impl Project {
buffers_being_formatted: Default::default(),
next_language_server_id: 0,
nonce: StdRng::from_entropy().gen(),
terminals: Terminals {
local_handles: Vec::new(),
},
})
}
@@ -522,9 +516,6 @@ impl Project {
buffers_being_formatted: Default::default(),
buffer_snapshots: Default::default(),
nonce: StdRng::from_entropy().gen(),
terminals: Terminals {
local_handles: Vec::new(),
},
};
for worktree in worktrees {
let _ = this.add_worktree(&worktree, cx);
@@ -1193,6 +1184,34 @@ impl Project {
!self.is_local()
}
pub fn create_terminal(
&mut self,
working_directory: Option<PathBuf>,
window_id: usize,
cx: &mut ModelContext<Self>,
) -> Result<ModelHandle<Terminal>> {
if self.is_remote() {
return Err(anyhow!(
"creating terminals as a guest is not supported yet"
));
} else {
let settings = cx.global::<Settings>();
let shell = settings.terminal_shell();
let envs = settings.terminal_env();
let scroll = settings.terminal_scroll();
TerminalBuilder::new(
working_directory.clone(),
shell,
envs,
settings.terminal_overrides.blinking.clone(),
scroll,
window_id,
)
.map(|builder| cx.add_model(|cx| builder.subscribe(cx)))
}
}
pub fn create_buffer(
&mut self,
text: &str,
@@ -2538,7 +2557,7 @@ impl Project {
pub fn update_diagnostics(
&mut self,
language_server_id: usize,
mut params: lsp::PublishDiagnosticsParams,
params: lsp::PublishDiagnosticsParams,
disk_based_sources: &[String],
cx: &mut ModelContext<Self>,
) -> Result<()> {
@@ -2550,10 +2569,6 @@ impl Project {
let mut primary_diagnostic_group_ids = HashMap::default();
let mut sources_by_group_id = HashMap::default();
let mut supporting_diagnostics = HashMap::default();
// Ensure that primary diagnostics are always the most severe
params.diagnostics.sort_by_key(|item| item.severity);
for diagnostic in &params.diagnostics {
let source = diagnostic.source.as_ref();
let code = diagnostic.code.as_ref().map(|code| match code {
@@ -2843,11 +2858,9 @@ impl Project {
.filter_map(|buffer_handle| {
let buffer = buffer_handle.read(cx);
let file = File::from_dyn(buffer.file())?;
let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
let server = self
.language_server_for_buffer(buffer, cx)
.map(|s| s.1.clone());
Some((buffer_handle, buffer_abs_path, server))
let buffer_abs_path = file.as_local()?.abs_path(cx);
let (_, server) = self.language_server_for_buffer(buffer, cx)?;
Some((buffer_handle, buffer_abs_path, server.clone()))
})
.collect::<Vec<_>>();
@@ -2862,10 +2875,10 @@ impl Project {
let _cleanup = defer({
let this = this.clone();
let mut cx = cx.clone();
let buffers = &buffers_with_paths_and_servers;
let local_buffers = &buffers_with_paths_and_servers;
move || {
this.update(&mut cx, |this, _| {
for (buffer, _, _) in buffers {
for (buffer, _, _) in local_buffers {
this.buffers_being_formatted.remove(&buffer.id());
}
});
@@ -2874,138 +2887,60 @@ impl Project {
let mut project_transaction = ProjectTransaction::default();
for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
let (
format_on_save,
remove_trailing_whitespace,
ensure_final_newline,
formatter,
tab_size,
) = buffer.read_with(&cx, |buffer, cx| {
let settings = cx.global::<Settings>();
let language_name = buffer.language().map(|language| language.name());
(
settings.format_on_save(language_name.as_deref()),
settings.remove_trailing_whitespace_on_save(language_name.as_deref()),
settings.ensure_final_newline_on_save(language_name.as_deref()),
settings.formatter(language_name.as_deref()),
settings.tab_size(language_name.as_deref()),
)
});
let (format_on_save, formatter, tab_size) =
buffer.read_with(&cx, |buffer, cx| {
let settings = cx.global::<Settings>();
let language_name = buffer.language().map(|language| language.name());
(
settings.format_on_save(language_name.as_deref()),
settings.formatter(language_name.as_deref()),
settings.tab_size(language_name.as_deref()),
)
});
// First, format buffer's whitespace according to the settings.
let trailing_whitespace_diff = if remove_trailing_whitespace {
Some(
buffer
.read_with(&cx, |b, cx| b.remove_trailing_whitespace(cx))
.await,
)
} else {
None
};
let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
buffer.finalize_last_transaction();
buffer.start_transaction();
if let Some(diff) = trailing_whitespace_diff {
buffer.apply_diff(diff, cx);
}
if ensure_final_newline {
buffer.ensure_final_newline(cx);
}
buffer.end_transaction(cx)
});
// Currently, formatting operations are represented differently depending on
// whether they come from a language server or an external command.
enum FormatOperation {
Lsp(Vec<(Range<Anchor>, String)>),
External(Diff),
}
// Apply language-specific formatting using either a language server
// or external command.
let mut format_operation = None;
match (formatter, format_on_save) {
(_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {}
let transaction = match (formatter, format_on_save) {
(_, FormatOnSave::Off) if trigger == FormatTrigger::Save => continue,
(Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off)
| (_, FormatOnSave::LanguageServer) => {
if let Some((language_server, buffer_abs_path)) =
language_server.as_ref().zip(buffer_abs_path.as_ref())
{
format_operation = Some(FormatOperation::Lsp(
Self::format_via_lsp(
&this,
&buffer,
buffer_abs_path,
&language_server,
tab_size,
&mut cx,
)
.await
.context("failed to format via language server")?,
));
}
}
| (_, FormatOnSave::LanguageServer) => Self::format_via_lsp(
&this,
&buffer,
&buffer_abs_path,
&language_server,
tab_size,
&mut cx,
)
.await
.context("failed to format via language server")?,
(
Formatter::External { command, arguments },
FormatOnSave::On | FormatOnSave::Off,
)
| (_, FormatOnSave::External { command, arguments }) => {
if let Some(buffer_abs_path) = buffer_abs_path {
format_operation = Self::format_via_external_command(
&buffer,
&buffer_abs_path,
&command,
&arguments,
&mut cx,
)
.await
.context(format!(
"failed to format via external command {:?}",
command
))?
.map(FormatOperation::External);
}
Self::format_via_external_command(
&buffer,
&buffer_abs_path,
&command,
&arguments,
&mut cx,
)
.await
.context(format!(
"failed to format via external command {:?}",
command
))?
}
};
buffer.update(&mut cx, |b, cx| {
// If the buffer had its whitespace formatted and was edited while the language-specific
// formatting was being computed, avoid applying the language-specific formatting, because
// it can't be grouped with the whitespace formatting in the undo history.
if let Some(transaction_id) = whitespace_transaction_id {
if b.peek_undo_stack()
.map_or(true, |e| e.transaction_id() != transaction_id)
{
format_operation.take();
}
if let Some(transaction) = transaction {
if !push_to_history {
buffer.update(&mut cx, |buffer, _| {
buffer.forget_transaction(transaction.id)
});
}
// Apply any language-specific formatting, and group the two formatting operations
// in the buffer's undo history.
if let Some(operation) = format_operation {
match operation {
FormatOperation::Lsp(edits) => {
b.edit(edits, None, cx);
}
FormatOperation::External(diff) => {
b.apply_diff(diff, cx);
}
}
if let Some(transaction_id) = whitespace_transaction_id {
b.group_until_transaction(transaction_id);
}
}
if let Some(transaction) = b.finalize_last_transaction().cloned() {
if !push_to_history {
b.forget_transaction(transaction.id);
}
project_transaction.0.insert(buffer.clone(), transaction);
}
});
project_transaction.0.insert(buffer.clone(), transaction);
}
}
Ok(project_transaction)
@@ -3046,7 +2981,7 @@ impl Project {
language_server: &Arc<LanguageServer>,
tab_size: NonZeroU32,
cx: &mut AsyncAppContext,
) -> Result<Vec<(Range<Anchor>, String)>> {
) -> Result<Option<Transaction>> {
let text_document =
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(abs_path).unwrap());
let capabilities = &language_server.capabilities();
@@ -3093,12 +3028,26 @@ impl Project {
};
if let Some(lsp_edits) = lsp_edits {
this.update(cx, |this, cx| {
this.edits_from_lsp(buffer, lsp_edits, None, cx)
let edits = this
.update(cx, |this, cx| {
this.edits_from_lsp(buffer, lsp_edits, None, cx)
})
.await?;
buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction();
buffer.start_transaction();
for (range, text) in edits {
buffer.edit([(range, text)], None, cx);
}
if buffer.end_transaction(cx).is_some() {
let transaction = buffer.finalize_last_transaction().unwrap().clone();
Ok(Some(transaction))
} else {
Ok(None)
}
})
.await
} else {
Ok(Default::default())
Ok(None)
}
}
@@ -3108,7 +3057,7 @@ impl Project {
command: &str,
arguments: &[String],
cx: &mut AsyncAppContext,
) -> Result<Option<Diff>> {
) -> Result<Option<Transaction>> {
let working_dir_path = buffer.read_with(cx, |buffer, cx| {
let file = File::from_dyn(buffer.file())?;
let worktree = file.worktree.read(cx).as_local()?;
@@ -3151,11 +3100,10 @@ impl Project {
}
let stdout = String::from_utf8(output.stdout)?;
Ok(Some(
buffer
.read_with(cx, |buffer, cx| buffer.diff(stdout, cx))
.await,
))
let diff = buffer
.read_with(cx, |buffer, cx| buffer.diff(stdout, cx))
.await;
Ok(buffer.update(cx, |buffer, cx| buffer.apply_diff(diff, cx).cloned()))
} else {
Ok(None)
}

View File

@@ -8,7 +8,6 @@ use language::{
OffsetRangeExt, Point, ToPoint,
};
use lsp::Url;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::{cell::RefCell, os::unix, rc::Rc, task::Poll};
use unindent::Unindent as _;
@@ -2847,7 +2846,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::WARNING,
message: "error 1".to_string(),
group_id: 1,
group_id: 0,
is_primary: true,
..Default::default()
}
@@ -2857,7 +2856,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 1 hint 1".to_string(),
group_id: 1,
group_id: 0,
is_primary: false,
..Default::default()
}
@@ -2867,7 +2866,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 1".to_string(),
group_id: 0,
group_id: 1,
is_primary: false,
..Default::default()
}
@@ -2877,7 +2876,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 2".to_string(),
group_id: 0,
group_id: 1,
is_primary: false,
..Default::default()
}
@@ -2887,7 +2886,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::ERROR,
message: "error 2".to_string(),
group_id: 0,
group_id: 1,
is_primary: true,
..Default::default()
}
@@ -2897,49 +2896,13 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
assert_eq!(
buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
&[
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 1".to_string(),
group_id: 0,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 2".to_string(),
group_id: 0,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(2, 8)..Point::new(2, 17),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::ERROR,
message: "error 2".to_string(),
group_id: 0,
is_primary: true,
..Default::default()
}
}
]
);
assert_eq!(
buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
&[
DiagnosticEntry {
range: Point::new(1, 8)..Point::new(1, 9),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::WARNING,
message: "error 1".to_string(),
group_id: 1,
group_id: 0,
is_primary: true,
..Default::default()
}
@@ -2949,11 +2912,46 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 1 hint 1".to_string(),
group_id: 0,
is_primary: false,
..Default::default()
}
},
]
);
assert_eq!(
buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
&[
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 1".to_string(),
group_id: 1,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 2".to_string(),
group_id: 1,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(2, 8)..Point::new(2, 17),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::ERROR,
message: "error 2".to_string(),
group_id: 1,
is_primary: true,
..Default::default()
}
}
]
);
}

View File

@@ -1,63 +0,0 @@
use std::path::PathBuf;
use gpui::{ModelContext, ModelHandle, WeakModelHandle};
use settings::Settings;
use terminal::{Terminal, TerminalBuilder};
use crate::Project;
pub struct Terminals {
pub(crate) local_handles: Vec<WeakModelHandle<terminal::Terminal>>,
}
impl Project {
pub fn create_terminal(
&mut self,
working_directory: Option<PathBuf>,
window_id: usize,
cx: &mut ModelContext<Self>,
) -> anyhow::Result<ModelHandle<Terminal>> {
if self.is_remote() {
return Err(anyhow::anyhow!(
"creating terminals as a guest is not supported yet"
));
} else {
let settings = cx.global::<Settings>();
let shell = settings.terminal_shell();
let envs = settings.terminal_env();
let scroll = settings.terminal_scroll();
let terminal = TerminalBuilder::new(
working_directory.clone(),
shell,
envs,
settings.terminal_overrides.blinking.clone(),
scroll,
window_id,
)
.map(|builder| {
let terminal_handle = cx.add_model(|cx| builder.subscribe(cx));
self.terminals
.local_handles
.push(terminal_handle.downgrade());
let id = terminal_handle.id();
cx.observe_release(&terminal_handle, move |project, _terminal, _cx| {
let handles = &mut project.terminals.local_handles;
if let Some(index) = handles.iter().position(|terminal| terminal.id() == id) {
handles.remove(index);
}
})
.detach();
terminal_handle
});
terminal
}
}
}
// TODO: Add a few tests for adding and removing terminal tabs

View File

@@ -1314,7 +1314,7 @@ impl View for ProjectPanel {
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut cx = Self::default_keymap_context();
cx.add_identifier("menu");
cx.set.insert("menu".into());
cx
}
}

View File

@@ -8,7 +8,7 @@ publish = false
path = "src/rope.rs"
[dependencies]
bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" }
bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "dac565a90e8f9245f48ff46225c915dc50f76920" }
smallvec = { version = "1.6", features = ["union"] }
sum_tree = { path = "../sum_tree" }
arrayvec = "0.7.1"

View File

@@ -231,7 +231,6 @@ message ParticipantProject {
message Follower {
PeerId leader_id = 1;
PeerId follower_id = 2;
uint64 project_id = 3;
}
message ParticipantLocation {

View File

@@ -114,7 +114,7 @@ pub struct ConnectionState {
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1);
const WRITE_TIMEOUT: Duration = Duration::from_secs(2);
pub const RECEIVE_TIMEOUT: Duration = Duration::from_secs(10);
pub const RECEIVE_TIMEOUT: Duration = Duration::from_secs(5);
impl Peer {
pub fn new(epoch: u32) -> Arc<Self> {

View File

@@ -6,4 +6,4 @@ pub use conn::Connection;
pub use peer::*;
mod macros;
pub const PROTOCOL_VERSION: u32 = 49;
pub const PROTOCOL_VERSION: u32 = 48;

View File

@@ -248,15 +248,15 @@ impl Item for ProjectSearchView {
tab_theme: &theme::Tab,
cx: &gpui::AppContext,
) -> ElementBox {
let settings = cx.global::<Settings>();
let search_theme = &settings.theme.search;
Flex::row()
.with_child(
Svg::new("icons/magnifying_glass_12.svg")
.with_color(tab_theme.label.text.color)
.constrained()
.with_width(tab_theme.type_icon_width)
.with_width(search_theme.tab_icon_width)
.aligned()
.contained()
.with_margin_right(tab_theme.spacing)
.boxed(),
)
.with_children(self.model.read(cx).active_query.as_ref().map(|query| {
@@ -264,6 +264,8 @@ impl Item for ProjectSearchView {
Label::new(query_text, tab_theme.label.clone())
.aligned()
.contained()
.with_margin_left(search_theme.tab_icon_spacing)
.boxed()
}))
.boxed()
@@ -538,7 +540,7 @@ impl ProjectSearchView {
let range_to_select = match_ranges[new_index].clone();
self.results_editor.update(cx, |editor, cx| {
editor.unfold_ranges([range_to_select.clone()], false, true, cx);
editor.unfold_ranges([range_to_select.clone()], false, cx);
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range_to_select])
});

View File

@@ -94,8 +94,6 @@ pub struct EditorSettings {
pub soft_wrap: Option<SoftWrap>,
pub preferred_line_length: Option<u32>,
pub format_on_save: Option<FormatOnSave>,
pub remove_trailing_whitespace_on_save: Option<bool>,
pub ensure_final_newline_on_save: Option<bool>,
pub formatter: Option<Formatter>,
pub enable_language_server: Option<bool>,
}
@@ -363,12 +361,6 @@ impl Settings {
hard_tabs: required(defaults.editor.hard_tabs),
soft_wrap: required(defaults.editor.soft_wrap),
preferred_line_length: required(defaults.editor.preferred_line_length),
remove_trailing_whitespace_on_save: required(
defaults.editor.remove_trailing_whitespace_on_save,
),
ensure_final_newline_on_save: required(
defaults.editor.ensure_final_newline_on_save,
),
format_on_save: required(defaults.editor.format_on_save),
formatter: required(defaults.editor.formatter),
enable_language_server: required(defaults.editor.enable_language_server),
@@ -468,18 +460,6 @@ impl Settings {
self.language_setting(language, |settings| settings.preferred_line_length)
}
pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool {
self.language_setting(language, |settings| {
settings.remove_trailing_whitespace_on_save.clone()
})
}
pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
self.language_setting(language, |settings| {
settings.ensure_final_newline_on_save.clone()
})
}
pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
self.language_setting(language, |settings| settings.format_on_save.clone())
}
@@ -578,8 +558,6 @@ impl Settings {
hard_tabs: Some(false),
soft_wrap: Some(SoftWrap::None),
preferred_line_length: Some(80),
remove_trailing_whitespace_on_save: Some(true),
ensure_final_newline_on_save: Some(true),
format_on_save: Some(FormatOnSave::On),
formatter: Some(Formatter::LanguageServer),
enable_language_server: Some(true),

View File

@@ -469,50 +469,53 @@ impl View for TerminalView {
let mut context = Self::default_keymap_context();
let mode = self.terminal.read(cx).last_content.mode;
context.add_key(
"screen",
if mode.contains(TermMode::ALT_SCREEN) {
context.map.insert(
"screen".to_string(),
(if mode.contains(TermMode::ALT_SCREEN) {
"alt"
} else {
"normal"
},
})
.to_string(),
);
if mode.contains(TermMode::APP_CURSOR) {
context.add_identifier("DECCKM");
context.set.insert("DECCKM".to_string());
}
if mode.contains(TermMode::APP_KEYPAD) {
context.add_identifier("DECPAM");
} else {
context.add_identifier("DECPNM");
context.set.insert("DECPAM".to_string());
}
//Note the ! here
if !mode.contains(TermMode::APP_KEYPAD) {
context.set.insert("DECPNM".to_string());
}
if mode.contains(TermMode::SHOW_CURSOR) {
context.add_identifier("DECTCEM");
context.set.insert("DECTCEM".to_string());
}
if mode.contains(TermMode::LINE_WRAP) {
context.add_identifier("DECAWM");
context.set.insert("DECAWM".to_string());
}
if mode.contains(TermMode::ORIGIN) {
context.add_identifier("DECOM");
context.set.insert("DECOM".to_string());
}
if mode.contains(TermMode::INSERT) {
context.add_identifier("IRM");
context.set.insert("IRM".to_string());
}
//LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html
if mode.contains(TermMode::LINE_FEED_NEW_LINE) {
context.add_identifier("LNM");
context.set.insert("LNM".to_string());
}
if mode.contains(TermMode::FOCUS_IN_OUT) {
context.add_identifier("report_focus");
context.set.insert("report_focus".to_string());
}
if mode.contains(TermMode::ALTERNATE_SCROLL) {
context.add_identifier("alternate_scroll");
context.set.insert("alternate_scroll".to_string());
}
if mode.contains(TermMode::BRACKETED_PASTE) {
context.add_identifier("bracketed_paste");
context.set.insert("bracketed_paste".to_string());
}
if mode.intersects(TermMode::MOUSE_MODE) {
context.add_identifier("any_mouse_reporting");
context.set.insert("any_mouse_reporting".to_string());
}
{
let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) {
@@ -524,7 +527,9 @@ impl View for TerminalView {
} else {
"off"
};
context.add_key("mouse_reporting", mouse_reporting);
context
.map
.insert("mouse_reporting".to_string(), mouse_reporting.to_string());
}
{
let format = if mode.contains(TermMode::SGR_MOUSE) {
@@ -534,7 +539,9 @@ impl View for TerminalView {
} else {
"normal"
};
context.add_key("mouse_format", format);
context
.map
.insert("mouse_format".to_string(), format.to_string());
}
context
}
@@ -582,16 +589,11 @@ impl Item for TerminalView {
Flex::row()
.with_child(
gpui::elements::Svg::new("icons/terminal_12.svg")
.with_color(tab_theme.label.text.color)
.constrained()
.with_width(tab_theme.type_icon_width)
Label::new(title, tab_theme.label.clone())
.aligned()
.contained()
.with_margin_right(tab_theme.spacing)
.boxed(),
)
.with_child(Label::new(title, tab_theme.label.clone()).aligned().boxed())
.boxed()
}

View File

@@ -80,19 +80,18 @@ pub struct Titlebar {
pub follower_avatar_overlap: f32,
pub leader_selection: ContainerStyle,
pub offline_icon: OfflineIcon,
pub leader_avatar: AvatarStyle,
pub avatar: AvatarStyle,
pub inactive_avatar: AvatarStyle,
pub follower_avatar: AvatarStyle,
pub inactive_avatar_grayscale: bool,
pub sign_in_prompt: Interactive<ContainedText>,
pub outdated_warning: ContainedText,
pub share_button: Interactive<ContainedText>,
pub call_control: Interactive<IconButton>,
pub toggle_contacts_button: Interactive<IconButton>,
pub user_menu_button: Interactive<IconButton>,
pub toggle_contacts_badge: ContainerStyle,
}
#[derive(Copy, Clone, Deserialize, Default)]
#[derive(Clone, Deserialize, Default)]
pub struct AvatarStyle {
#[serde(flatten)]
pub image: ImageStyle,
@@ -215,8 +214,7 @@ pub struct Tab {
pub label: LabelStyle,
pub description: ContainedText,
pub spacing: f32,
pub close_icon_width: f32,
pub type_icon_width: f32,
pub icon_width: f32,
pub icon_close: Color,
pub icon_close_active: Color,
pub icon_dirty: Color,
@@ -259,6 +257,8 @@ pub struct Search {
pub match_background: Color,
pub match_index: ContainedText,
pub results_status: TextStyle,
pub tab_icon_width: f32,
pub tab_icon_spacing: f32,
pub dismiss_button: Interactive<IconButton>,
}
@@ -563,7 +563,6 @@ pub struct Editor {
pub invalid_hint_diagnostic: DiagnosticStyle,
pub autocomplete: AutocompleteStyle,
pub code_actions: CodeActions,
pub folds: Folds,
pub unnecessary_code_fade: f32,
pub hover_popover: HoverPopover,
pub link_definition: HighlightStyle,
@@ -633,35 +632,13 @@ pub struct FieldEditor {
pub selection: SelectionStyle,
}
#[derive(Clone, Deserialize, Default)]
pub struct InteractiveColor {
pub color: Color,
}
#[derive(Clone, Deserialize, Default)]
pub struct CodeActions {
#[serde(default)]
pub indicator: Interactive<InteractiveColor>,
pub indicator: Color,
pub vertical_scale: f32,
}
#[derive(Clone, Deserialize, Default)]
pub struct Folds {
pub indicator: Interactive<InteractiveColor>,
pub ellipses: FoldEllipses,
pub fold_background: Color,
pub icon_width: f32,
pub folded_icon: String,
pub foldable_icon: String,
}
#[derive(Clone, Deserialize, Default)]
pub struct FoldEllipses {
pub text_color: Color,
pub background: Interactive<InteractiveColor>,
pub corner_radius_factor: f32,
}
#[derive(Clone, Deserialize, Default)]
pub struct DiffStyle {
pub inserted: Color,
@@ -845,9 +822,7 @@ pub struct TerminalStyle {
pub struct FeedbackStyle {
pub submit_button: Interactive<ContainedText>,
pub button_margin: f32,
pub info_text_default: ContainedText,
pub link_text_default: ContainedText,
pub link_text_hover: ContainedText,
pub info_text: ContainedText,
}
#[derive(Clone, Deserialize, Default)]

View File

@@ -237,7 +237,7 @@ macro_rules! iife {
};
}
/// Async Immediately invoked function expression. Good for using the ? operator
/// Async lImmediately invoked function expression. Good for using the ? operator
/// in functions which do not return an Option or Result. Async version of above
#[macro_export]
macro_rules! async_iife {
@@ -262,7 +262,10 @@ impl<T: Ord + Clone> RangeExt<T> for Range<T> {
}
fn overlaps(&self, other: &Range<T>) -> bool {
self.start < other.end && other.start < self.end
self.contains(&other.start)
|| self.contains(&other.end)
|| other.contains(&self.start)
|| other.contains(&self.end)
}
}
@@ -276,7 +279,10 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
}
fn overlaps(&self, other: &Range<T>) -> bool {
self.start() < &other.end && &other.start <= self.end()
self.contains(&other.start)
|| self.contains(&other.end)
|| other.contains(&self.start())
|| other.contains(&self.end())
}
}

View File

@@ -36,7 +36,6 @@ pub enum Motion {
Matching,
FindForward { before: bool, text: Arc<str> },
FindBackward { after: bool, text: Arc<str> },
NextLineStart,
}
#[derive(Clone, Deserialize, PartialEq)]
@@ -75,7 +74,6 @@ actions!(
StartOfDocument,
EndOfDocument,
Matching,
NextLineStart,
]
);
impl_actions!(vim, [NextWordStart, NextWordEnd, PreviousWordStart]);
@@ -113,7 +111,6 @@ pub fn init(cx: &mut MutableAppContext) {
&PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
);
cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx))
}
pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) {
@@ -141,43 +138,15 @@ pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) {
impl Motion {
pub fn linewise(&self) -> bool {
use Motion::*;
match self {
Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart => true,
EndOfLine
| NextWordEnd { .. }
| Matching
| FindForward { .. }
| Left
| Backspace
| Right
| StartOfLine
| NextWordStart { .. }
| PreviousWordStart { .. }
| FirstNonWhitespace
| FindBackward { .. } => false,
}
matches!(
self,
Down | Up | StartOfDocument | EndOfDocument | CurrentLine
)
}
pub fn infallible(&self) -> bool {
use Motion::*;
match self {
StartOfDocument | EndOfDocument | CurrentLine => true,
Down
| Up
| EndOfLine
| NextWordEnd { .. }
| Matching
| FindForward { .. }
| Left
| Backspace
| Right
| StartOfLine
| NextWordStart { .. }
| PreviousWordStart { .. }
| FirstNonWhitespace
| FindBackward { .. }
| NextLineStart => false,
}
matches!(self, StartOfDocument | CurrentLine | EndOfDocument)
}
pub fn inclusive(&self) -> bool {
@@ -191,8 +160,7 @@ impl Motion {
| EndOfLine
| NextWordEnd { .. }
| Matching
| FindForward { .. }
| NextLineStart => true,
| FindForward { .. } => true,
Left
| Backspace
| Right
@@ -246,7 +214,6 @@ impl Motion {
find_backward(map, point, *after, text.clone(), times),
SelectionGoal::None,
),
NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
};
(new_point != point || infallible).then_some((new_point, goal))
@@ -576,8 +543,3 @@ fn find_backward(
})
.unwrap_or(from)
}
fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
let new_row = (point.row() + times as u32).min(map.max_buffer_row());
map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left)
}

View File

@@ -473,7 +473,6 @@ pub(crate) fn normal_replace(text: Arc<str>, cx: &mut MutableAppContext) {
#[cfg(test)]
mod test {
use gpui::TestAppContext;
use indoc::indoc;
use crate::{
@@ -516,15 +515,15 @@ mod test {
.await;
}
#[gpui::test]
async fn test_enter(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
cx.assert_all(indoc! {"
ˇThe qˇuick broˇwn
ˇfox jumps"
})
.await;
}
// #[gpui::test]
// async fn test_enter(cx: &mut gpui::TestAppContext) {
// let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
// cx.assert_all(indoc! {"
// ˇThe qˇuick broˇwn
// ˇfox jumps"
// })
// .await;
// }
#[gpui::test]
async fn test_k(cx: &mut gpui::TestAppContext) {
@@ -1031,7 +1030,7 @@ mod test {
}
#[gpui::test]
async fn test_percent(cx: &mut TestAppContext) {
async fn test_percent(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
cx.assert_all("ˇconsole.logˇ(ˇvaˇrˇ)ˇ;").await;
cx.assert_all("ˇconsole.logˇ(ˇ'var', ˇ[ˇ1, ˇ2, 3ˇ]ˇ)ˇ;")

View File

@@ -73,30 +73,34 @@ impl VimState {
pub fn keymap_context_layer(&self) -> KeymapContext {
let mut context = KeymapContext::default();
context.add_key(
"vim_mode",
context.map.insert(
"vim_mode".to_string(),
match self.mode {
Mode::Normal => "normal",
Mode::Visual { .. } => "visual",
Mode::Insert => "insert",
},
}
.to_string(),
);
if self.vim_controlled() {
context.add_identifier("VimControl");
context.set.insert("VimControl".to_string());
}
let active_operator = self.operator_stack.last();
if let Some(active_operator) = active_operator {
for context_flag in active_operator.context_flags().into_iter() {
context.add_identifier(*context_flag);
context.set.insert(context_flag.to_string());
}
}
context.add_key(
"vim_operator",
active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
context.map.insert(
"vim_operator".to_string(),
active_operator
.map(|op| op.id())
.unwrap_or_else(|| "none")
.to_string(),
);
context

View File

@@ -1 +0,0 @@
[{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"}]

View File

@@ -42,7 +42,6 @@ impl View for ToggleDockButton {
let workspace = workspace.unwrap();
let dock_position = workspace.read(cx).dock.position;
let dock_pane = workspace.read(cx.app).dock_pane().clone();
let theme = cx.global::<Settings>().theme.clone();
@@ -68,6 +67,7 @@ impl View for ToggleDockButton {
})
.with_cursor_style(CursorStyle::PointingHand)
.on_up(MouseButton::Left, move |event, cx| {
let dock_pane = workspace.read(cx.app).dock_pane();
let drop_index = dock_pane.read(cx.app).items_len() + 1;
handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
});

View File

@@ -21,7 +21,6 @@ use gpui::{
vector::{vec2f, Vector2F},
},
impl_actions, impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, NavigationDirection},
Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
@@ -1150,53 +1149,40 @@ impl Pane {
let tab_active = ix == self.active_item_index;
row.add_child({
enum TabDragReceiver {}
let mut receiver =
dragged_item_receiver::<TabDragReceiver, _>(ix, ix, true, None, cx, {
let item = item.clone();
let pane = pane.clone();
let detail = detail.clone();
enum Tab {}
let mut receiver = dragged_item_receiver::<Tab, _>(ix, ix, true, None, cx, {
let item = item.clone();
let pane = pane.clone();
let detail = detail.clone();
let theme = cx.global::<Settings>().theme.clone();
let theme = cx.global::<Settings>().theme.clone();
move |mouse_state, cx| {
let tab_style =
theme.workspace.tab_bar.tab_style(pane_active, tab_active);
let hovered = mouse_state.hovered();
enum Tab {}
MouseEventHandler::<Tab>::new(ix, cx, |_, cx| {
Self::render_tab(
&item,
pane.clone(),
ix == 0,
detail,
hovered,
tab_style,
cx,
)
})
.on_down(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ActivateItem(ix));
})
.on_click(MouseButton::Middle, {
let item = item.clone();
move |_, cx: &mut EventContext| {
cx.dispatch_action(CloseItem {
item_id: item.id(),
pane: pane.clone(),
})
}
})
.boxed()
}
});
move |mouse_state, cx| {
let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active);
let hovered = mouse_state.hovered();
Self::render_tab(&item, pane, ix == 0, detail, hovered, tab_style, cx)
}
});
if !pane_active || !tab_active {
receiver = receiver.with_cursor_style(CursorStyle::PointingHand);
}
receiver
.on_down(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ActivateItem(ix));
cx.propagate_event();
})
.on_click(MouseButton::Middle, {
let item = item.clone();
let pane = pane.clone();
move |_, cx: &mut EventContext| {
cx.dispatch_action(CloseItem {
item_id: item.id(),
pane: pane.clone(),
})
}
})
.as_draggable(
DraggedItem {
item,
@@ -1368,7 +1354,7 @@ impl Pane {
} else {
Empty::new().boxed()
})
.with_width(tab_style.close_icon_width)
.with_width(tab_style.icon_width)
.boxed(),
)
.boxed(),
@@ -1451,7 +1437,7 @@ impl View for Pane {
.with_style(theme.workspace.tab_bar.container)
.boxed()
})
.on_down(MouseButton::Left, move |_, cx| {
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ActivateItem(active_item_index));
})
.boxed(),
@@ -1564,14 +1550,6 @@ impl View for Pane {
}
}
}
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut keymap = Self::default_keymap_context();
if self.docked.is_some() {
keymap.add_identifier("docked");
}
keymap
}
}
fn tab_bar_button<A: Action>(

View File

@@ -108,7 +108,7 @@ impl Item for SharedScreen {
Svg::new("icons/disable_screen_sharing_12.svg")
.with_color(style.label.text.color)
.constrained()
.with_width(style.type_icon_width)
.with_width(style.icon_width)
.aligned()
.contained()
.with_margin_right(style.spacing)

View File

@@ -1,88 +0,0 @@
use gpui::{
elements::{Empty, MouseEventHandler, Svg},
CursorStyle, Element, ElementBox, Entity, MouseButton, RenderContext, View, ViewContext,
ViewHandle, WeakViewHandle,
};
use settings::Settings;
use crate::{dock::FocusDock, item::ItemHandle, StatusItemView, Workspace};
pub struct TerminalButton {
workspace: WeakViewHandle<Workspace>,
}
// TODO: Rename this to `DeployTerminalButton`
impl TerminalButton {
pub fn new(workspace: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
// When terminal moves, redraw so that the icon and toggle status matches.
cx.subscribe(&workspace, |_, _, _, cx| cx.notify()).detach();
Self {
workspace: workspace.downgrade(),
}
}
}
impl Entity for TerminalButton {
type Event = ();
}
impl View for TerminalButton {
fn ui_name() -> &'static str {
"TerminalButton"
}
fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
let workspace = self.workspace.upgrade(cx);
if workspace.is_none() {
return Empty::new().boxed();
}
// let workspace = workspace.unwrap();
let theme = cx.global::<Settings>().theme.clone();
MouseEventHandler::<Self>::new(0, cx, {
let theme = theme.clone();
move |state, _| {
let style = theme
.workspace
.status_bar
.sidebar_buttons
.item
.style_for(state, true);
Svg::new("icons/terminal_12.svg")
.with_color(style.icon_color)
.constrained()
.with_width(style.icon_size)
.with_height(style.icon_size)
.contained()
.with_style(style.container)
.boxed()
}
})
.with_cursor_style(CursorStyle::PointingHand)
.on_up(MouseButton::Left, move |_, _| {
// TODO: Do we need this stuff?
// let dock_pane = workspace.read(cx.app).dock_pane();
// let drop_index = dock_pane.read(cx.app).items_len() + 1;
// handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
})
.on_click(MouseButton::Left, |_, cx| {
cx.dispatch_action(FocusDock);
})
.with_tooltip::<Self, _>(
0,
"Show Terminal".into(),
Some(Box::new(FocusDock)),
theme.tooltip.clone(),
cx,
)
.boxed()
}
}
impl StatusItemView for TerminalButton {
fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
}

View File

@@ -12,7 +12,6 @@ pub mod searchable;
pub mod shared_screen;
pub mod sidebar;
mod status_bar;
pub mod terminal_button;
mod toolbar;
pub use smallvec;
@@ -57,7 +56,6 @@ use std::{
sync::Arc,
time::Duration,
};
use terminal_button::TerminalButton;
use crate::{
notifications::simple_message_notification::{MessageNotification, OsOpen},
@@ -586,7 +584,6 @@ impl Workspace {
let left_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Left));
let right_sidebar = cx.add_view(|_| Sidebar::new(SidebarSide::Right));
let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx));
let toggle_terminal = cx.add_view(|cx| TerminalButton::new(handle.clone(), cx));
let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(handle, cx));
let right_sidebar_buttons =
cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx));
@@ -595,7 +592,6 @@ impl Workspace {
status_bar.add_left_item(left_sidebar_buttons, cx);
status_bar.add_right_item(right_sidebar_buttons, cx);
status_bar.add_right_item(toggle_dock, cx);
status_bar.add_right_item(toggle_terminal, cx);
status_bar
});
@@ -2720,7 +2716,11 @@ impl View for Workspace {
}
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
Self::default_keymap_context()
let mut keymap = Self::default_keymap_context();
if self.active_pane() == self.dock_pane() {
keymap.set.insert("Dock".into());
}
keymap
}
}

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

After

Width:  |  Height:  |  Size: 275 KiB

View File

@@ -43,10 +43,8 @@
; Special identifiers
((identifier) @type
(#match? @type "^[A-Z]"))
(type_identifier) @type
(predefined_type) @type.builtin
((identifier) @constructor
(#match? @constructor "^[A-Z]"))
([
(identifier)
@@ -61,15 +59,12 @@
(super) @variable.special
[
(true)
(false)
(null)
(undefined)
] @constant.builtin
[
(true)
(false)
] @boolean
(comment) @comment
[
@@ -77,11 +72,15 @@
(template_string)
] @string
(regex) @string.regex
(regex) @string.special
(number) @number
; Tokens
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @embedded
[
";"
"?."
@@ -190,9 +189,13 @@
"yield"
] @keyword
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @embedded
; Types
(type_identifier) @type
(predefined_type) @type.builtin
((identifier) @type
(#match? @type "^[A-Z]"))
(type_arguments
"<" @punctuation.bracket

View File

@@ -137,7 +137,7 @@
;; Constants
((identifier) @constant
(#match? @constant "^[A-Z][A-Z_0-9]*$"))
(#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
(vararg_expression) @constant
@@ -164,17 +164,11 @@
(parameters (identifier) @parameter)
(function_call
name: [
(identifier) @function
(dot_index_expression field: (identifier) @function)
])
(function_call name: (identifier) @function.call)
(function_declaration name: (identifier) @function)
(function_declaration
name: [
(identifier) @function.definition
(dot_index_expression field: (identifier) @function.definition)
])
(function_call name: (dot_index_expression field: (identifier) @function.call))
(function_declaration name: (dot_index_expression field: (identifier) @function))
(method_index_expression method: (identifier) @method)

View File

@@ -3,7 +3,7 @@
[(string)
(here_string)
(byte_string)] @string
(regex) @string.regex
(regex) @string.special
(escape_sequence) @escape
[(comment)
@@ -19,7 +19,7 @@
(quote . (symbol)) @constant
(extension) @keyword
(lang_name) @variable.special
(lang_name) @variable.builtin
((symbol) @operator
(#match? @operator "^(\\+|-|\\*|/|=|>|<|>=|<=)$"))

View File

@@ -95,7 +95,7 @@
(bare_symbol)
] @string.special.symbol
(regex) @string.regex
(regex) @string.special.regex
(escape_sequence) @escape
[

View File

@@ -46,11 +46,6 @@
((identifier) @constructor
(#match? @constructor "^[A-Z]"))
((identifier) @type
(#match? @type "^[A-Z]"))
(type_identifier) @type
(predefined_type) @type.builtin
([
(identifier)
(shorthand_property_identifier)
@@ -64,15 +59,12 @@
(super) @variable.special
[
(true)
(false)
(null)
(undefined)
] @constant.builtin
[
(true)
(false)
] @boolean
(comment) @comment
[
@@ -80,11 +72,15 @@
(template_string)
] @string
(regex) @string.regex
(regex) @string.special
(number) @number
; Tokens
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @embedded
[
";"
"?."
@@ -194,9 +190,13 @@
"yield"
] @keyword
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @embedded
; Types
(type_identifier) @type
(predefined_type) @type.builtin
((identifier) @type
(#match? @type "^[A-Z]"))
(type_arguments
"<" @punctuation.bracket

View File

@@ -13,6 +13,7 @@ use client::{
http::{self, HttpClient},
UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN,
};
use futures::{
channel::{mpsc, oneshot},
FutureExt, SinkExt, StreamExt,
@@ -30,10 +31,8 @@ use settings::{
};
use simplelog::ConfigBuilder;
use smol::process::Command;
use std::{
env, ffi::OsStr, fs::OpenOptions, io::Write as _, os::unix::prelude::OsStrExt, panic,
path::PathBuf, sync::Arc, thread, time::Duration,
};
use std::{env, ffi::OsStr, panic, path::PathBuf, sync::Arc, thread, time::Duration};
use std::{fs::OpenOptions, os::unix::prelude::OsStrExt};
use terminal_view::{get_working_directory, TerminalView};
use fs::RealFs;
@@ -120,9 +119,7 @@ fn main() {
));
watch_settings_file(default_settings, settings_file_content, themes.clone(), cx);
if !stdout_is_a_pty() {
upload_previous_panics(http.clone(), cx);
}
upload_previous_panics(http.clone(), cx);
let client = client::Client::new(http.clone(), cx);
let mut languages = LanguageRegistry::new(login_shell_env_loaded);
@@ -333,22 +330,18 @@ fn init_panic_hook(app_version: String) {
),
};
let panic_filename = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
std::fs::write(
paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, panic_filename)),
&message,
)
.context("error writing panic to disk")
.log_err();
if is_pty {
eprintln!("{}", message);
return;
}
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
let panic_file_path =
paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, timestamp));
let panic_file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(&panic_file_path)
.log_err();
if let Some(mut panic_file) = panic_file {
write!(&mut panic_file, "{}", message).log_err();
panic_file.flush().log_err();
} else {
log::error!(target: "panic", "{}", message);
}
}));
}

View File

@@ -381,47 +381,7 @@ pub fn build_window_options(
}
fn restart(_: &Restart, cx: &mut gpui::MutableAppContext) {
let mut workspaces = cx
.window_ids()
.filter_map(|window_id| cx.root_view::<Workspace>(window_id))
.collect::<Vec<_>>();
// If multiple windows have unsaved changes, and need a save prompt,
// prompt in the active window before switching to a different window.
workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
let should_confirm = cx.global::<Settings>().confirm_quit;
cx.spawn(|mut cx| async move {
if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
let answer = cx
.prompt(
workspace.window_id(),
PromptLevel::Info,
"Are you sure you want to restart?",
&["Restart", "Cancel"],
)
.next()
.await;
if answer != Some(0) {
return Ok(());
}
}
// If the user cancels any save prompt, then keep the app open.
for workspace in workspaces {
if !workspace
.update(&mut cx, |workspace, cx| {
workspace.prepare_to_close(true, cx)
})
.await?
{
return Ok(());
}
}
cx.platform().restart();
anyhow::Ok(())
})
.detach_and_log_err(cx);
cx.platform().restart();
}
fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {

View File

@@ -1,2 +0,0 @@
package-lock.json
package.json

229
styles/package-lock.json generated
View File

@@ -9,83 +9,67 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@types/chroma-js": "^2.4.0",
"@types/node": "^18.14.1",
"bezier-easing": "^2.1.0",
"@types/chroma-js": "^2.1.3",
"@types/node": "^17.0.23",
"case-anything": "^2.1.10",
"chroma-js": "^2.4.2",
"deepmerge": "^4.3.0",
"toml": "^3.0.0",
"ts-node": "^10.9.1"
"ts-node": "^10.7.0"
}
},
"node_modules/@cspotcode/source-map-consumer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
"integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
"engines": {
"node": ">= 12"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
"integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
"@cspotcode/source-map-consumer": "0.8.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
"integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg=="
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
"integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw=="
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
"integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg=="
},
"node_modules/@tsconfig/node16": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ=="
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA=="
},
"node_modules/@types/chroma-js": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz",
"integrity": "sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
"integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
},
"node_modules/@types/node": {
"version": "18.14.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz",
"integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ=="
"version": "17.0.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz",
"integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw=="
},
"node_modules/acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
"bin": {
"acorn": "bin/acorn"
},
@@ -106,11 +90,6 @@
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
},
"node_modules/bezier-easing": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
"integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
},
"node_modules/case-anything": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz",
@@ -132,14 +111,6 @@
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
},
"node_modules/deepmerge": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
"integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -159,11 +130,11 @@
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"version": "10.7.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz",
"integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==",
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@cspotcode/source-map-support": "0.7.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
@@ -174,7 +145,7 @@
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"v8-compile-cache-lib": "^3.0.0",
"yn": "3.1.1"
},
"bin": {
@@ -201,9 +172,9 @@
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
"integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
@@ -214,9 +185,9 @@
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
"integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA=="
},
"node_modules/yn": {
"version": "3.1.1",
@@ -228,67 +199,53 @@
}
},
"dependencies": {
"@cspotcode/source-map-consumer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
"integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg=="
},
"@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
"integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
"requires": {
"@jridgewell/trace-mapping": "0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
"@cspotcode/source-map-consumer": "0.8.0"
}
},
"@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
"integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg=="
},
"@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
"integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw=="
},
"@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
"integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg=="
},
"@tsconfig/node16": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ=="
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA=="
},
"@types/chroma-js": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz",
"integrity": "sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw=="
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
"integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
},
"@types/node": {
"version": "18.14.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz",
"integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ=="
"version": "17.0.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz",
"integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw=="
},
"acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
},
"acorn-walk": {
"version": "8.2.0",
@@ -300,11 +257,6 @@
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
},
"bezier-easing": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
"integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
},
"case-anything": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz",
@@ -320,11 +272,6 @@
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
},
"deepmerge": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
"integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og=="
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -341,11 +288,11 @@
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="
},
"ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"version": "10.7.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz",
"integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==",
"requires": {
"@cspotcode/source-map-support": "^0.8.0",
"@cspotcode/source-map-support": "0.7.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
@@ -356,20 +303,20 @@
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"v8-compile-cache-lib": "^3.0.0",
"yn": "3.1.1"
}
},
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
"integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
"peer": true
},
"v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
"integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA=="
},
"yn": {
"version": "3.1.1",

View File

@@ -10,19 +10,11 @@
"author": "",
"license": "ISC",
"dependencies": {
"@types/chroma-js": "^2.4.0",
"@types/node": "^18.14.1",
"bezier-easing": "^2.1.0",
"@types/chroma-js": "^2.1.3",
"@types/node": "^17.0.23",
"case-anything": "^2.1.10",
"chroma-js": "^2.4.2",
"deepmerge": "^4.3.0",
"toml": "^3.0.0",
"ts-node": "^10.9.1"
},
"prettier": {
"semi": false,
"printWidth": 80,
"htmlWhitespaceSensitivity": "strict",
"tabWidth": 4
"ts-node": "^10.7.0"
}
}

View File

@@ -1,92 +1,73 @@
import * as fs from "fs"
import toml from "toml"
import { schemeMeta } from "./colorSchemes"
import { Meta, Verification } from "./themes/common/colorScheme"
import https from "https"
import crypto from "crypto"
import * as fs from "fs";
import toml from "toml";
import {
schemeMeta
} from "./colorSchemes";
import { Meta } from "./themes/common/colorScheme";
import https from "https";
import crypto from "crypto";
const accepted_licenses_file = `${__dirname}/../../script/licenses/zed-licenses.toml`
// Use the cargo-about configuration file as the source of truth for supported licenses.
function parseAcceptedToml(file: string): string[] {
let buffer = fs.readFileSync(file).toString()
let buffer = fs.readFileSync(file).toString();
let obj = toml.parse(buffer)
let obj = toml.parse(buffer);
if (!Array.isArray(obj.accepted)) {
throw Error("Accepted license source is malformed")
}
if (!Array.isArray(obj.accepted)) {
throw Error("Accepted license source is malformed")
}
return obj.accepted
return obj.accepted
}
function checkLicenses(schemeMeta: Meta[], licenses: string[]) {
for (let meta of schemeMeta) {
// FIXME: Add support for conjuctions and conditions
if (licenses.indexOf(meta.license.SPDX) < 0) {
throw Error(
`License for theme ${meta.name} (${meta.license.SPDX}) is not supported`
)
}
for (let meta of schemeMeta) {
// FIXME: Add support for conjuctions and conditions
if (licenses.indexOf(meta.license.SPDX) < 0) {
throw Error(`License for theme ${meta.name} (${meta.license.SPDX}) is not supported`)
}
}
}
function getLicenseText(
schemeMeta: Meta[],
callback: (meta: Meta, license_text: string) => void
) {
for (let meta of schemeMeta) {
if (typeof meta.license.license_text == "string") {
callback(meta, meta.license.license_text)
function getLicenseText(schemeMeta: Meta[], callback: (meta: Meta, license_text: string) => void) {
for (let meta of schemeMeta) {
// The following copied from the example code on nodejs.org:
// https://nodejs.org/api/http.html#httpgetoptions-callback
https.get(meta.license.https_url, (res) => {
const { statusCode } = res;
if (statusCode < 200 || statusCode >= 300) {
throw new Error(`Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}`);
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
const hash = crypto.createHash('sha256').update(rawData).digest('hex');
if (meta.license.license_checksum == hash) {
callback(meta, rawData)
} else {
let license_text_obj: Verification = meta.license.license_text;
// The following copied from the example code on nodejs.org:
// https://nodejs.org/api/http.html#httpgetoptions-callback
https
.get(license_text_obj.https_url, (res) => {
const { statusCode } = res
if (statusCode < 200 || statusCode >= 300) {
throw new Error(
`Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}`
)
}
res.setEncoding("utf8")
let rawData = ""
res.on("data", (chunk) => {
rawData += chunk
})
res.on("end", () => {
const hash = crypto
.createHash("sha256")
.update(rawData)
.digest("hex")
if (license_text_obj.license_checksum == hash) {
callback(meta, rawData)
} else {
throw Error(
`Checksum for ${meta.name} did not match file downloaded from ${license_text_obj.https_url}`
)
}
})
})
.on("error", (e) => {
throw e
})
throw Error(`Checksum for ${meta.name} did not match file downloaded from ${meta.license.https_url}`)
}
}
});
}).on('error', (e) => {
throw e
});
}
}
function writeLicense(schemeMeta: Meta, text: String) {
process.stdout.write(
`## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n`
)
process.stdout.write(`## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n`)
}
const accepted_licenses = parseAcceptedToml(accepted_licenses_file)
const accepted_licenses = parseAcceptedToml(accepted_licenses_file);
checkLicenses(schemeMeta, accepted_licenses)
getLicenseText(schemeMeta, (meta, text) => {
writeLicense(meta, text)
})
writeLicense(meta, text)
});

View File

@@ -1,52 +1,50 @@
import * as fs from "fs"
import { tmpdir } from "os"
import * as path from "path"
import colorSchemes, { staffColorSchemes } from "./colorSchemes"
import app from "./styleTree/app"
import { ColorScheme } from "./themes/common/colorScheme"
import snakeCase from "./utils/snakeCase"
import * as fs from "fs";
import { tmpdir } from "os";
import * as path from "path";
import colorSchemes, {
staffColorSchemes,
} from "./colorSchemes";
import app from "./styleTree/app";
import { ColorScheme } from "./themes/common/colorScheme";
import snakeCase from "./utils/snakeCase";
const assetsDirectory = `${__dirname}/../../assets`
const themeDirectory = `${assetsDirectory}/themes`
const staffDirectory = `${themeDirectory}/staff`
const themeDirectory = `${assetsDirectory}/themes`;
const staffDirectory = `${themeDirectory}/staff`;
const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes"))
const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes"));
// Clear existing themes
function clearThemes(themeDirectory: string) {
if (!fs.existsSync(themeDirectory)) {
fs.mkdirSync(themeDirectory, { recursive: true })
} else {
for (const file of fs.readdirSync(themeDirectory)) {
if (file.endsWith(".json")) {
const name = file.replace(/\.json$/, "")
if (
!colorSchemes.find(
(colorScheme) => colorScheme.name === name
)
) {
fs.unlinkSync(path.join(themeDirectory, file))
}
}
if (!fs.existsSync(themeDirectory)) {
fs.mkdirSync(themeDirectory, { recursive: true });
} else {
for (const file of fs.readdirSync(themeDirectory)) {
if (file.endsWith(".json")) {
const name = file.replace(/\.json$/, "");
if (!colorSchemes.find((colorScheme) => colorScheme.name === name)) {
fs.unlinkSync(path.join(themeDirectory, file));
}
}
}
}
}
clearThemes(themeDirectory)
clearThemes(staffDirectory)
clearThemes(themeDirectory);
clearThemes(staffDirectory);
function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) {
for (let colorScheme of colorSchemes) {
let styleTree = snakeCase(app(colorScheme))
let styleTreeJSON = JSON.stringify(styleTree, null, 2)
let tempPath = path.join(tempDirectory, `${colorScheme.name}.json`)
let outPath = path.join(outputDirectory, `${colorScheme.name}.json`)
fs.writeFileSync(tempPath, styleTreeJSON)
fs.renameSync(tempPath, outPath)
console.log(`- ${outPath} created`)
}
for (let colorScheme of colorSchemes) {
let styleTree = snakeCase(app(colorScheme));
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
let tempPath = path.join(tempDirectory, `${colorScheme.name}.json`);
let outPath = path.join(outputDirectory, `${colorScheme.name}.json`);
fs.writeFileSync(tempPath, styleTreeJSON);
fs.renameSync(tempPath, outPath);
console.log(`- ${outPath} created`);
}
}
// Write new themes to theme directory
writeThemes(colorSchemes, themeDirectory)
writeThemes(staffColorSchemes, staffDirectory)
writeThemes(colorSchemes, themeDirectory);
writeThemes(staffColorSchemes, staffDirectory);

View File

@@ -1,54 +1,54 @@
import fs from "fs"
import path from "path"
import { ColorScheme, Meta } from "./themes/common/colorScheme"
import fs from "fs";
import path from "path";
import { ColorScheme, Meta } from "./themes/common/colorScheme";
const colorSchemes: ColorScheme[] = []
export default colorSchemes
const colorSchemes: ColorScheme[] = [];
export default colorSchemes;
const schemeMeta: Meta[] = []
export { schemeMeta }
const schemeMeta: Meta[] = [];
export { schemeMeta };
const staffColorSchemes: ColorScheme[] = []
export { staffColorSchemes }
const staffColorSchemes: ColorScheme[] = [];
export { staffColorSchemes };
const experimentalColorSchemes: ColorScheme[] = []
export { experimentalColorSchemes }
const experimentalColorSchemes: ColorScheme[] = [];
export { experimentalColorSchemes };
const themes_directory = path.resolve(`${__dirname}/themes`)
const themes_directory = path.resolve(`${__dirname}/themes`);
function for_all_color_schemes_in(
themesPath: string,
callback: (module: any, path: string) => void
) {
for (const fileName of fs.readdirSync(themesPath)) {
if (fileName == "template.ts") continue
const filePath = path.join(themesPath, fileName)
function for_all_color_schemes_in(themesPath: string, callback: (module: any, path: string) => void) {
for (const fileName of fs.readdirSync(themesPath)) {
if (fileName == "template.ts") continue;
const filePath = path.join(themesPath, fileName);
if (fs.statSync(filePath).isFile()) {
const colorScheme = require(filePath)
callback(colorScheme, path.basename(filePath))
}
if (fs.statSync(filePath).isFile()) {
const colorScheme = require(filePath);
callback(colorScheme, path.basename(filePath));
}
}
}
function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) {
for_all_color_schemes_in(themesPath, (colorScheme, _path) => {
if (colorScheme.dark) colorSchemes.push(colorScheme.dark)
if (colorScheme.light) colorSchemes.push(colorScheme.light)
})
for_all_color_schemes_in(themesPath, (colorScheme, _path) => {
if (colorScheme.dark) colorSchemes.push(colorScheme.dark);
if (colorScheme.light) colorSchemes.push(colorScheme.light);
})
}
fillColorSchemes(themes_directory, colorSchemes)
fillColorSchemes(path.resolve(`${themes_directory}/staff`), staffColorSchemes)
fillColorSchemes(themes_directory, colorSchemes);
fillColorSchemes(
path.resolve(`${themes_directory}/staff`),
staffColorSchemes
);
function fillMeta(themesPath: string, meta: Meta[]) {
for_all_color_schemes_in(themesPath, (colorScheme, path) => {
if (colorScheme.meta) {
meta.push(colorScheme.meta)
} else {
throw Error(`Public theme ${path} must have a meta field`)
}
})
for_all_color_schemes_in(themesPath, (colorScheme, path) => {
if (colorScheme.meta) {
meta.push(colorScheme.meta)
} else {
throw Error(`Public theme ${path} must have a meta field`)
}
})
}
fillMeta(themes_directory, schemeMeta)
fillMeta(themes_directory, schemeMeta);

View File

@@ -1,45 +1,66 @@
export const fontFamilies = {
sans: "Zed Sans",
mono: "Zed Mono",
}
sans: "Zed Sans",
mono: "Zed Mono",
};
export const fontSizes = {
"3xs": 8,
"2xs": 10,
xs: 12,
sm: 14,
md: 16,
lg: 18,
xl: 20,
}
"3xs": 8,
"2xs": 10,
xs: 12,
sm: 14,
md: 16,
lg: 18,
xl: 20,
};
export type FontWeight =
| "thin"
| "extra_light"
| "light"
| "normal"
| "medium"
| "semibold"
| "bold"
| "extra_bold"
| "black"
| "thin"
| "extra_light"
| "light"
| "normal"
| "medium"
| "semibold"
| "bold"
| "extra_bold"
| "black";
export const fontWeights: { [key: string]: FontWeight } = {
thin: "thin",
extra_light: "extra_light",
light: "light",
normal: "normal",
medium: "medium",
semibold: "semibold",
bold: "bold",
extra_bold: "extra_bold",
black: "black",
}
thin: "thin",
extra_light: "extra_light",
light: "light",
normal: "normal",
medium: "medium",
semibold: "semibold",
bold: "bold",
extra_bold: "extra_bold",
black: "black",
};
export const sizes = {
px: 1,
xs: 2,
sm: 4,
md: 6,
lg: 8,
xl: 12,
}
px: 1,
xs: 2,
sm: 4,
md: 6,
lg: 8,
xl: 12,
};
// export const colors = {
// neutral: colorRamp(["white", "black"], { steps: 37, increment: 25 }), // (900/25) + 1
// rose: colorRamp("#F43F5EFF"),
// red: colorRamp("#EF4444FF"),
// orange: colorRamp("#F97316FF"),
// amber: colorRamp("#F59E0BFF"),
// yellow: colorRamp("#EAB308FF"),
// lime: colorRamp("#84CC16FF"),
// green: colorRamp("#22C55EFF"),
// emerald: colorRamp("#10B981FF"),
// teal: colorRamp("#14B8A6FF"),
// cyan: colorRamp("#06BBD4FF"),
// sky: colorRamp("#0EA5E9FF"),
// blue: colorRamp("#3B82F6FF"),
// indigo: colorRamp("#6366F1FF"),
// violet: colorRamp("#8B5CF6FF"),
// purple: colorRamp("#A855F7FF"),
// fuschia: colorRamp("#D946E4FF"),
// pink: colorRamp("#EC4899FF"),
// }

View File

@@ -1,72 +1,72 @@
import { text } from "./components"
import contactFinder from "./contactFinder"
import contactsPopover from "./contactsPopover"
import commandPalette from "./commandPalette"
import editor from "./editor"
import projectPanel from "./projectPanel"
import search from "./search"
import picker from "./picker"
import workspace from "./workspace"
import contextMenu from "./contextMenu"
import sharedScreen from "./sharedScreen"
import projectDiagnostics from "./projectDiagnostics"
import contactNotification from "./contactNotification"
import updateNotification from "./updateNotification"
import simpleMessageNotification from "./simpleMessageNotification"
import projectSharedNotification from "./projectSharedNotification"
import tooltip from "./tooltip"
import terminal from "./terminal"
import contactList from "./contactList"
import incomingCallNotification from "./incomingCallNotification"
import { ColorScheme } from "../themes/common/colorScheme"
import feedback from "./feedback"
import { text } from "./components";
import contactFinder from "./contactFinder";
import contactsPopover from "./contactsPopover";
import commandPalette from "./commandPalette";
import editor from "./editor";
import projectPanel from "./projectPanel";
import search from "./search";
import picker from "./picker";
import workspace from "./workspace";
import contextMenu from "./contextMenu";
import sharedScreen from "./sharedScreen";
import projectDiagnostics from "./projectDiagnostics";
import contactNotification from "./contactNotification";
import updateNotification from "./updateNotification";
import simpleMessageNotification from "./simpleMessageNotification";
import projectSharedNotification from "./projectSharedNotification";
import tooltip from "./tooltip";
import terminal from "./terminal";
import contactList from "./contactList";
import incomingCallNotification from "./incomingCallNotification";
import { ColorScheme } from "../themes/common/colorScheme";
import feedback from "./feedback";
export default function app(colorScheme: ColorScheme): Object {
return {
meta: {
name: colorScheme.name,
isLight: colorScheme.isLight,
},
commandPalette: commandPalette(colorScheme),
contactNotification: contactNotification(colorScheme),
projectSharedNotification: projectSharedNotification(colorScheme),
incomingCallNotification: incomingCallNotification(colorScheme),
picker: picker(colorScheme),
workspace: workspace(colorScheme),
contextMenu: contextMenu(colorScheme),
editor: editor(colorScheme),
projectDiagnostics: projectDiagnostics(colorScheme),
projectPanel: projectPanel(colorScheme),
contactsPopover: contactsPopover(colorScheme),
contactFinder: contactFinder(colorScheme),
contactList: contactList(colorScheme),
search: search(colorScheme),
sharedScreen: sharedScreen(colorScheme),
breadcrumbs: {
...text(colorScheme.highest, "sans", "variant"),
padding: {
left: 6,
},
},
updateNotification: updateNotification(colorScheme),
simpleMessageNotification: simpleMessageNotification(colorScheme),
tooltip: tooltip(colorScheme),
terminal: terminal(colorScheme),
feedback: feedback(colorScheme),
colorScheme: {
...colorScheme,
players: Object.values(colorScheme.players),
ramps: {
neutral: colorScheme.ramps.neutral.colors(100, "hex"),
red: colorScheme.ramps.red.colors(100, "hex"),
orange: colorScheme.ramps.orange.colors(100, "hex"),
yellow: colorScheme.ramps.yellow.colors(100, "hex"),
green: colorScheme.ramps.green.colors(100, "hex"),
cyan: colorScheme.ramps.cyan.colors(100, "hex"),
blue: colorScheme.ramps.blue.colors(100, "hex"),
violet: colorScheme.ramps.violet.colors(100, "hex"),
magenta: colorScheme.ramps.magenta.colors(100, "hex"),
},
},
}
return {
meta: {
name: colorScheme.name,
isLight: colorScheme.isLight,
},
commandPalette: commandPalette(colorScheme),
contactNotification: contactNotification(colorScheme),
projectSharedNotification: projectSharedNotification(colorScheme),
incomingCallNotification: incomingCallNotification(colorScheme),
picker: picker(colorScheme),
workspace: workspace(colorScheme),
contextMenu: contextMenu(colorScheme),
editor: editor(colorScheme),
projectDiagnostics: projectDiagnostics(colorScheme),
projectPanel: projectPanel(colorScheme),
contactsPopover: contactsPopover(colorScheme),
contactFinder: contactFinder(colorScheme),
contactList: contactList(colorScheme),
search: search(colorScheme),
sharedScreen: sharedScreen(colorScheme),
breadcrumbs: {
...text(colorScheme.highest, "sans", "variant"),
padding: {
left: 6,
},
},
updateNotification: updateNotification(colorScheme),
simpleMessageNotification: simpleMessageNotification(colorScheme),
tooltip: tooltip(colorScheme),
terminal: terminal(colorScheme),
feedback: feedback(colorScheme),
colorScheme: {
...colorScheme,
players: Object.values(colorScheme.players),
ramps: {
neutral: colorScheme.ramps.neutral.colors(100, "hex"),
red: colorScheme.ramps.red.colors(100, "hex"),
orange: colorScheme.ramps.orange.colors(100, "hex"),
yellow: colorScheme.ramps.yellow.colors(100, "hex"),
green: colorScheme.ramps.green.colors(100, "hex"),
cyan: colorScheme.ramps.cyan.colors(100, "hex"),
blue: colorScheme.ramps.blue.colors(100, "hex"),
violet: colorScheme.ramps.violet.colors(100, "hex"),
magenta: colorScheme.ramps.magenta.colors(100, "hex"),
},
},
};
}

View File

@@ -1,30 +1,30 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { withOpacity } from "../utils/color"
import { text, background } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { withOpacity } from "../utils/color";
import { text, background } from "./components";
export default function commandPalette(colorScheme: ColorScheme) {
let layer = colorScheme.highest
return {
keystrokeSpacing: 8,
key: {
text: text(layer, "mono", "variant", "default", { size: "xs" }),
cornerRadius: 2,
background: background(layer, "on"),
padding: {
top: 1,
bottom: 1,
left: 6,
right: 6,
},
margin: {
top: 1,
bottom: 1,
left: 2,
},
active: {
text: text(layer, "mono", "on", "default", { size: "xs" }),
background: withOpacity(background(layer, "on"), 0.2),
},
},
}
let layer = colorScheme.highest;
return {
keystrokeSpacing: 8,
key: {
text: text(layer, "mono", "variant", "default", { size: "xs" }),
cornerRadius: 2,
background: background(layer, "on"),
padding: {
top: 1,
bottom: 1,
left: 6,
right: 6,
},
margin: {
top: 1,
bottom: 1,
left: 2,
},
active: {
text: text(layer, "mono", "on", "default", { size: "xs" }),
background: withOpacity(background(layer, "on"), 0.2),
},
},
};
}

View File

@@ -1,210 +1,210 @@
import { fontFamilies, fontSizes, FontWeight } from "../common"
import { Layer, Styles, StyleSets, Style } from "../themes/common/colorScheme"
import { fontFamilies, fontSizes, FontWeight } from "../common";
import { Layer, Styles, StyleSets, Style } from "../themes/common/colorScheme";
function isStyleSet(key: any): key is StyleSets {
return [
"base",
"variant",
"on",
"accent",
"positive",
"warning",
"negative",
].includes(key)
return [
"base",
"variant",
"on",
"accent",
"positive",
"warning",
"negative",
].includes(key);
}
function isStyle(key: any): key is Styles {
return [
"default",
"active",
"disabled",
"hovered",
"pressed",
"inverted",
].includes(key)
return [
"default",
"active",
"disabled",
"hovered",
"pressed",
"inverted",
].includes(key);
}
function getStyle(
layer: Layer,
possibleStyleSetOrStyle?: any,
possibleStyle?: any
layer: Layer,
possibleStyleSetOrStyle?: any,
possibleStyle?: any
): Style {
let styleSet: StyleSets = "base"
let style: Styles = "default"
if (isStyleSet(possibleStyleSetOrStyle)) {
styleSet = possibleStyleSetOrStyle
} else if (isStyle(possibleStyleSetOrStyle)) {
style = possibleStyleSetOrStyle
}
let styleSet: StyleSets = "base";
let style: Styles = "default";
if (isStyleSet(possibleStyleSetOrStyle)) {
styleSet = possibleStyleSetOrStyle;
} else if (isStyle(possibleStyleSetOrStyle)) {
style = possibleStyleSetOrStyle;
}
if (isStyle(possibleStyle)) {
style = possibleStyle
}
if (isStyle(possibleStyle)) {
style = possibleStyle;
}
return layer[styleSet][style]
return layer[styleSet][style];
}
export function background(layer: Layer, style?: Styles): string
export function background(layer: Layer, style?: Styles): string;
export function background(
layer: Layer,
styleSet?: StyleSets,
style?: Styles
): string
layer: Layer,
styleSet?: StyleSets,
style?: Styles
): string;
export function background(
layer: Layer,
styleSetOrStyles?: StyleSets | Styles,
style?: Styles
layer: Layer,
styleSetOrStyles?: StyleSets | Styles,
style?: Styles
): string {
return getStyle(layer, styleSetOrStyles, style).background
return getStyle(layer, styleSetOrStyles, style).background;
}
export function borderColor(layer: Layer, style?: Styles): string
export function borderColor(layer: Layer, style?: Styles): string;
export function borderColor(
layer: Layer,
styleSet?: StyleSets,
style?: Styles
): string
layer: Layer,
styleSet?: StyleSets,
style?: Styles
): string;
export function borderColor(
layer: Layer,
styleSetOrStyles?: StyleSets | Styles,
style?: Styles
layer: Layer,
styleSetOrStyles?: StyleSets | Styles,
style?: Styles
): string {
return getStyle(layer, styleSetOrStyles, style).border
return getStyle(layer, styleSetOrStyles, style).border;
}
export function foreground(layer: Layer, style?: Styles): string
export function foreground(layer: Layer, style?: Styles): string;
export function foreground(
layer: Layer,
styleSet?: StyleSets,
style?: Styles
): string
layer: Layer,
styleSet?: StyleSets,
style?: Styles
): string;
export function foreground(
layer: Layer,
styleSetOrStyles?: StyleSets | Styles,
style?: Styles
layer: Layer,
styleSetOrStyles?: StyleSets | Styles,
style?: Styles
): string {
return getStyle(layer, styleSetOrStyles, style).foreground
return getStyle(layer, styleSetOrStyles, style).foreground;
}
interface Text {
family: keyof typeof fontFamilies
color: string
size: number
weight?: FontWeight
underline?: boolean
family: keyof typeof fontFamilies;
color: string;
size: number;
weight?: FontWeight;
underline?: boolean;
}
interface TextProperties {
size?: keyof typeof fontSizes
weight?: FontWeight
underline?: boolean
color?: string
size?: keyof typeof fontSizes;
weight?: FontWeight;
underline?: boolean;
color?: string;
}
export function text(
layer: Layer,
fontFamily: keyof typeof fontFamilies,
styleSet: StyleSets,
style: Styles,
properties?: TextProperties
): Text
layer: Layer,
fontFamily: keyof typeof fontFamilies,
styleSet: StyleSets,
style: Styles,
properties?: TextProperties
): Text;
export function text(
layer: Layer,
fontFamily: keyof typeof fontFamilies,
styleSet: StyleSets,
properties?: TextProperties
): Text
layer: Layer,
fontFamily: keyof typeof fontFamilies,
styleSet: StyleSets,
properties?: TextProperties
): Text;
export function text(
layer: Layer,
fontFamily: keyof typeof fontFamilies,
style: Styles,
properties?: TextProperties
): Text
layer: Layer,
fontFamily: keyof typeof fontFamilies,
style: Styles,
properties?: TextProperties
): Text;
export function text(
layer: Layer,
fontFamily: keyof typeof fontFamilies,
properties?: TextProperties
): Text
layer: Layer,
fontFamily: keyof typeof fontFamilies,
properties?: TextProperties
): Text;
export function text(
layer: Layer,
fontFamily: keyof typeof fontFamilies,
styleSetStyleOrProperties?: StyleSets | Styles | TextProperties,
styleOrProperties?: Styles | TextProperties,
properties?: TextProperties
layer: Layer,
fontFamily: keyof typeof fontFamilies,
styleSetStyleOrProperties?: StyleSets | Styles | TextProperties,
styleOrProperties?: Styles | TextProperties,
properties?: TextProperties
) {
let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties)
let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties);
if (typeof styleSetStyleOrProperties === "object") {
properties = styleSetStyleOrProperties
}
if (typeof styleOrProperties === "object") {
properties = styleOrProperties
}
if (typeof styleSetStyleOrProperties === "object") {
properties = styleSetStyleOrProperties;
}
if (typeof styleOrProperties === "object") {
properties = styleOrProperties;
}
let size = fontSizes[properties?.size || "sm"]
let color = properties?.color || style.foreground
let size = fontSizes[properties?.size || "sm"];
let color = properties?.color || style.foreground;
return {
family: fontFamilies[fontFamily],
...properties,
color,
size,
}
return {
family: fontFamilies[fontFamily],
...properties,
color,
size,
};
}
export interface Border {
color: string
width: number
top?: boolean
bottom?: boolean
left?: boolean
right?: boolean
overlay?: boolean
color: string;
width: number;
top?: boolean;
bottom?: boolean;
left?: boolean;
right?: boolean;
overlay?: boolean;
}
export interface BorderProperties {
width?: number
top?: boolean
bottom?: boolean
left?: boolean
right?: boolean
overlay?: boolean
width?: number;
top?: boolean;
bottom?: boolean;
left?: boolean;
right?: boolean;
overlay?: boolean;
}
export function border(
layer: Layer,
styleSet: StyleSets,
style: Styles,
properties?: BorderProperties
): Border
layer: Layer,
styleSet: StyleSets,
style: Styles,
properties?: BorderProperties
): Border;
export function border(
layer: Layer,
styleSet: StyleSets,
properties?: BorderProperties
): Border
layer: Layer,
styleSet: StyleSets,
properties?: BorderProperties
): Border;
export function border(
layer: Layer,
style: Styles,
properties?: BorderProperties
): Border
export function border(layer: Layer, properties?: BorderProperties): Border
layer: Layer,
style: Styles,
properties?: BorderProperties
): Border;
export function border(layer: Layer, properties?: BorderProperties): Border;
export function border(
layer: Layer,
styleSetStyleOrProperties?: StyleSets | Styles | BorderProperties,
styleOrProperties?: Styles | BorderProperties,
properties?: BorderProperties
layer: Layer,
styleSetStyleOrProperties?: StyleSets | Styles | BorderProperties,
styleOrProperties?: Styles | BorderProperties,
properties?: BorderProperties
): Border {
let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties)
let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties);
if (typeof styleSetStyleOrProperties === "object") {
properties = styleSetStyleOrProperties
}
if (typeof styleOrProperties === "object") {
properties = styleOrProperties
}
if (typeof styleSetStyleOrProperties === "object") {
properties = styleSetStyleOrProperties;
}
if (typeof styleOrProperties === "object") {
properties = styleOrProperties;
}
return {
color: style.border,
width: 1,
...properties,
}
return {
color: style.border,
width: 1,
...properties,
};
}

View File

@@ -1,70 +1,70 @@
import picker from "./picker"
import { ColorScheme } from "../themes/common/colorScheme"
import { background, border, foreground, text } from "./components"
import picker from "./picker";
import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, foreground, text } from "./components";
export default function contactFinder(colorScheme: ColorScheme) {
let layer = colorScheme.middle
let layer = colorScheme.middle;
const sideMargin = 6
const contactButton = {
background: background(layer, "variant"),
color: foreground(layer, "variant"),
iconWidth: 8,
buttonWidth: 16,
cornerRadius: 8,
}
const sideMargin = 6;
const contactButton = {
background: background(layer, "variant"),
color: foreground(layer, "variant"),
iconWidth: 8,
buttonWidth: 16,
cornerRadius: 8,
};
const pickerStyle = picker(colorScheme)
const pickerInput = {
background: background(layer, "on"),
cornerRadius: 6,
text: text(layer, "mono"),
placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
selection: colorScheme.players[0],
border: border(layer),
padding: {
bottom: 4,
left: 8,
right: 8,
top: 4,
},
margin: {
left: sideMargin,
right: sideMargin,
},
const pickerStyle = picker(colorScheme);
const pickerInput = {
background: background(layer, "on"),
cornerRadius: 6,
text: text(layer, "mono",),
placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
selection: colorScheme.players[0],
border: border(layer),
padding: {
bottom: 4,
left: 8,
right: 8,
top: 4,
},
margin: {
left: sideMargin,
right: sideMargin,
}
};
return {
picker: {
emptyContainer: {},
item: {
...pickerStyle.item,
margin: { left: sideMargin, right: sideMargin },
},
noMatches: pickerStyle.noMatches,
inputEditor: pickerInput,
emptyInputEditor: pickerInput,
},
rowHeight: 28,
contactAvatar: {
cornerRadius: 10,
width: 18,
},
contactUsername: {
padding: {
left: 8,
},
},
contactButton: {
...contactButton,
hover: {
background: background(layer, "variant", "hovered"),
},
},
disabledContactButton: {
...contactButton,
background: background(layer, "disabled"),
color: foreground(layer, "disabled"),
},
}
return {
picker: {
emptyContainer: {},
item: {
...pickerStyle.item,
margin: { left: sideMargin, right: sideMargin },
},
noMatches: pickerStyle.noMatches,
inputEditor: pickerInput,
emptyInputEditor: pickerInput
},
rowHeight: 28,
contactAvatar: {
cornerRadius: 10,
width: 18,
},
contactUsername: {
padding: {
left: 8,
},
},
contactButton: {
...contactButton,
hover: {
background: background(layer, "variant", "hovered"),
},
},
disabledContactButton: {
...contactButton,
background: background(layer, "disabled"),
color: foreground(layer, "disabled"),
},
};
}

View File

@@ -1,182 +1,186 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, border, borderColor, foreground, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import {
background,
border,
borderColor,
foreground,
text,
} from "./components";
export default function contactsPanel(colorScheme: ColorScheme) {
const nameMargin = 8
const sidePadding = 12
const nameMargin = 8;
const sidePadding = 12;
let layer = colorScheme.middle
let layer = colorScheme.middle;
const contactButton = {
background: background(layer, "on"),
color: foreground(layer, "on"),
iconWidth: 8,
buttonWidth: 16,
cornerRadius: 8,
}
const projectRow = {
guestAvatarSpacing: 4,
height: 24,
guestAvatar: {
cornerRadius: 8,
width: 14,
},
name: {
...text(layer, "mono", { size: "sm" }),
margin: {
left: nameMargin,
right: 6,
},
},
guests: {
margin: {
left: nameMargin,
right: nameMargin,
},
},
padding: {
left: sidePadding,
right: sidePadding,
},
}
const contactButton = {
background: background(layer, "on"),
color: foreground(layer, "on"),
iconWidth: 8,
buttonWidth: 16,
cornerRadius: 8,
};
const projectRow = {
guestAvatarSpacing: 4,
height: 24,
guestAvatar: {
cornerRadius: 8,
width: 14,
},
name: {
...text(layer, "mono", { size: "sm" }),
margin: {
left: nameMargin,
right: 6,
},
},
guests: {
margin: {
left: nameMargin,
right: nameMargin,
},
},
padding: {
left: sidePadding,
right: sidePadding,
},
};
return {
background: background(layer),
padding: { top: 12, bottom: 0 },
userQueryEditor: {
background: background(layer, "on"),
cornerRadius: 6,
text: text(layer, "mono", "on"),
placeholderText: text(layer, "mono", "on", "disabled", {
size: "xs",
}),
selection: colorScheme.players[0],
border: border(layer, "on"),
padding: {
bottom: 4,
left: 8,
right: 8,
top: 4,
},
margin: {
left: 6,
},
},
userQueryEditorHeight: 33,
addContactButton: {
margin: { left: 6, right: 12 },
color: foreground(layer, "on"),
buttonWidth: 28,
iconWidth: 16,
},
rowHeight: 28,
sectionIconSize: 8,
headerRow: {
...text(layer, "mono", { size: "sm" }),
margin: { top: 14 },
padding: {
left: sidePadding,
right: sidePadding,
},
active: {
...text(layer, "mono", "active", { size: "sm" }),
background: background(layer, "active"),
},
},
leaveCall: {
background: background(layer),
border: border(layer),
cornerRadius: 6,
margin: {
top: 1,
},
padding: {
top: 1,
bottom: 1,
left: 7,
right: 7,
},
...text(layer, "sans", "variant", { size: "xs" }),
hover: {
...text(layer, "sans", "hovered", { size: "xs" }),
background: background(layer, "hovered"),
border: border(layer, "hovered"),
},
},
contactRow: {
padding: {
left: sidePadding,
right: sidePadding,
},
active: {
background: background(layer, "active"),
},
},
contactAvatar: {
cornerRadius: 10,
width: 18,
},
contactStatusFree: {
cornerRadius: 4,
padding: 4,
margin: { top: 12, left: 12 },
background: foreground(layer, "positive"),
},
contactStatusBusy: {
cornerRadius: 4,
padding: 4,
margin: { top: 12, left: 12 },
background: foreground(layer, "negative"),
},
contactUsername: {
...text(layer, "mono", { size: "sm" }),
margin: {
left: nameMargin,
},
},
contactButtonSpacing: nameMargin,
contactButton: {
...contactButton,
hover: {
background: background(layer, "hovered"),
},
},
disabledButton: {
...contactButton,
background: background(layer, "on"),
color: foreground(layer, "on"),
},
callingIndicator: {
...text(layer, "mono", "variant", { size: "xs" }),
},
treeBranch: {
color: borderColor(layer),
width: 1,
hover: {
color: borderColor(layer),
},
active: {
color: borderColor(layer),
},
},
projectRow: {
...projectRow,
background: background(layer),
icon: {
margin: { left: nameMargin },
color: foreground(layer, "variant"),
width: 12,
},
name: {
...projectRow.name,
...text(layer, "mono", { size: "sm" }),
},
hover: {
background: background(layer, "hovered"),
},
active: {
background: background(layer, "active"),
},
},
}
return {
background: background(layer),
padding: { top: 12, bottom: 0 },
userQueryEditor: {
background: background(layer, "on"),
cornerRadius: 6,
text: text(layer, "mono", "on"),
placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
selection: colorScheme.players[0],
border: border(layer, "on"),
padding: {
bottom: 4,
left: 8,
right: 8,
top: 4,
},
margin: {
left: 6,
},
},
userQueryEditorHeight: 33,
addContactButton: {
margin: { left: 6, right: 12 },
color: foreground(layer, "on"),
buttonWidth: 28,
iconWidth: 16,
},
rowHeight: 28,
sectionIconSize: 8,
headerRow: {
...text(layer, "mono", { size: "sm" }),
margin: { top: 14 },
padding: {
left: sidePadding,
right: sidePadding,
},
active: {
...text(layer, "mono", "active", { size: "sm" }),
background: background(layer, "active"),
},
},
leaveCall: {
background: background(layer),
border: border(layer),
cornerRadius: 6,
margin: {
top: 1,
},
padding: {
top: 1,
bottom: 1,
left: 7,
right: 7,
},
...text(layer, "sans", "variant", { size: "xs" }),
hover: {
...text(layer, "sans", "hovered", { size: "xs" }),
background: background(layer, "hovered"),
border: border(layer, "hovered"),
},
},
contactRow: {
padding: {
left: sidePadding,
right: sidePadding,
},
active: {
background: background(layer, "active"),
},
},
contactAvatar: {
cornerRadius: 10,
width: 18,
},
contactStatusFree: {
cornerRadius: 4,
padding: 4,
margin: { top: 12, left: 12 },
background: foreground(layer, "positive"),
},
contactStatusBusy: {
cornerRadius: 4,
padding: 4,
margin: { top: 12, left: 12 },
background: foreground(layer, "negative"),
},
contactUsername: {
...text(layer, "mono", { size: "sm" }),
margin: {
left: nameMargin,
},
},
contactButtonSpacing: nameMargin,
contactButton: {
...contactButton,
hover: {
background: background(layer, "hovered"),
},
},
disabledButton: {
...contactButton,
background: background(layer, "on"),
color: foreground(layer, "on"),
},
callingIndicator: {
...text(layer, "mono", "variant", { size: "xs" }),
},
treeBranch: {
color: borderColor(layer),
width: 1,
hover: {
color: borderColor(layer),
},
active: {
color: borderColor(layer),
},
},
projectRow: {
...projectRow,
background: background(layer),
icon: {
margin: { left: nameMargin },
color: foreground(layer, "variant"),
width: 12,
},
name: {
...projectRow.name,
...text(layer, "mono", { size: "sm" }),
},
hover: {
background: background(layer, "hovered"),
},
active: {
background: background(layer, "active"),
},
},
};
}

View File

@@ -1,45 +1,45 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, foreground, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { background, foreground, text } from "./components";
const avatarSize = 12
const headerPadding = 8
const avatarSize = 12;
const headerPadding = 8;
export default function contactNotification(colorScheme: ColorScheme): Object {
let layer = colorScheme.lowest
return {
headerAvatar: {
height: avatarSize,
width: avatarSize,
cornerRadius: 6,
},
headerMessage: {
...text(layer, "sans", { size: "xs" }),
margin: { left: headerPadding, right: headerPadding },
},
headerHeight: 18,
bodyMessage: {
...text(layer, "sans", { size: "xs" }),
margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
},
button: {
...text(layer, "sans", "on", { size: "xs" }),
background: background(layer, "on"),
padding: 4,
cornerRadius: 6,
margin: { left: 6 },
hover: {
background: background(layer, "on", "hovered"),
},
},
dismissButton: {
color: foreground(layer, "variant"),
iconWidth: 8,
iconHeight: 8,
buttonWidth: 8,
buttonHeight: 8,
hover: {
color: foreground(layer, "hovered"),
},
},
}
let layer = colorScheme.lowest;
return {
headerAvatar: {
height: avatarSize,
width: avatarSize,
cornerRadius: 6,
},
headerMessage: {
...text(layer, "sans", { size: "xs" }),
margin: { left: headerPadding, right: headerPadding },
},
headerHeight: 18,
bodyMessage: {
...text(layer, "sans", { size: "xs" }),
margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
},
button: {
...text(layer, "sans", "on", { size: "xs" }),
background: background(layer, "on"),
padding: 4,
cornerRadius: 6,
margin: { left: 6 },
hover: {
background: background(layer, "on", "hovered"),
},
},
dismissButton: {
color: foreground(layer, "variant"),
iconWidth: 8,
iconHeight: 8,
buttonWidth: 8,
buttonHeight: 8,
hover: {
color: foreground(layer, "hovered"),
},
},
};
}

View File

@@ -1,29 +1,29 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, border, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, text } from "./components";
export default function contactsPopover(colorScheme: ColorScheme) {
let layer = colorScheme.middle
const sidePadding = 12
return {
background: background(layer),
cornerRadius: 6,
padding: { top: 6 },
margin: { top: -6 },
shadow: colorScheme.popoverShadow,
border: border(layer),
width: 300,
height: 400,
inviteRowHeight: 28,
inviteRow: {
padding: {
left: sidePadding,
right: sidePadding,
},
border: border(layer, { top: true }),
text: text(layer, "sans", "variant", { size: "sm" }),
hover: {
text: text(layer, "sans", "hovered", { size: "sm" }),
},
},
}
let layer = colorScheme.middle;
const sidePadding = 12;
return {
background: background(layer),
cornerRadius: 6,
padding: { top: 6 },
margin: { top: -6 },
shadow: colorScheme.popoverShadow,
border: border(layer),
width: 300,
height: 400,
inviteRowHeight: 28,
inviteRow: {
padding: {
left: sidePadding,
right: sidePadding,
},
border: border(layer, { top: true }),
text: text(layer, "sans", "variant", { size: "sm" }),
hover: {
text: text(layer, "sans", "hovered", { size: "sm" }),
},
},
}
}

View File

@@ -1,44 +1,41 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, border, borderColor, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, borderColor, text } from "./components";
export default function contextMenu(colorScheme: ColorScheme) {
let layer = colorScheme.middle
return {
background: background(layer),
cornerRadius: 10,
padding: 4,
shadow: colorScheme.popoverShadow,
border: border(layer),
keystrokeMargin: 30,
item: {
iconSpacing: 8,
iconWidth: 14,
padding: { left: 6, right: 6, top: 2, bottom: 2 },
cornerRadius: 6,
label: text(layer, "sans", { size: "sm" }),
keystroke: {
...text(layer, "sans", "variant", {
size: "sm",
weight: "bold",
}),
padding: { left: 3, right: 3 },
},
hover: {
background: background(layer, "hovered"),
label: text(layer, "sans", "hovered", { size: "sm" }),
},
active: {
background: background(layer, "active"),
label: text(layer, "sans", "active", { size: "sm" }),
},
activeHover: {
background: background(layer, "active"),
label: text(layer, "sans", "active", { size: "sm" }),
},
},
separator: {
background: borderColor(layer),
margin: { top: 2, bottom: 2 },
},
}
let layer = colorScheme.middle;
return {
background: background(layer),
cornerRadius: 10,
padding: 4,
shadow: colorScheme.popoverShadow,
border: border(layer),
keystrokeMargin: 30,
item: {
iconSpacing: 8,
iconWidth: 14,
padding: { left: 6, right: 6, top: 2, bottom: 2 },
cornerRadius: 6,
label: text(layer, "sans", { size: "sm" }),
keystroke: {
...text(layer, "sans", "variant", { size: "sm", weight: "bold" }),
padding: { left: 3, right: 3 },
},
hover: {
background: background(layer, "hovered"),
label: text(layer, "sans", "hovered", { size: "sm" }),
},
active: {
background: background(layer, "active"),
label: text(layer, "sans", "active", { size: "sm" }),
},
activeHover: {
background: background(layer, "active"),
label: text(layer, "sans", "active", { size: "sm" }),
},
},
separator: {
background: borderColor(layer),
margin: { top: 2, bottom: 2 },
},
};
}

View File

@@ -1,246 +1,285 @@
import { withOpacity } from "../utils/color"
import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme"
import { background, border, borderColor, foreground, text } from "./components"
import hoverPopover from "./hoverPopover"
import { buildSyntax } from "../themes/common/syntax"
import { fontWeights } from "../common";
import { withOpacity } from "../utils/color";
import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme";
import {
background,
border,
borderColor,
foreground,
text,
} from "./components";
import hoverPopover from "./hoverPopover";
export default function editor(colorScheme: ColorScheme) {
let layer = colorScheme.highest
let layer = colorScheme.highest;
const autocompleteItem = {
cornerRadius: 6,
padding: {
bottom: 2,
left: 6,
right: 6,
top: 2,
},
}
function diagnostic(layer: Layer, styleSet: StyleSets) {
return {
textScaleFactor: 0.857,
header: {
border: border(layer, {
top: true,
}),
},
message: {
text: text(layer, "sans", styleSet, "default", { size: "sm" }),
highlightText: text(layer, "sans", styleSet, "default", {
size: "sm",
weight: "bold",
}),
},
}
}
const syntax = buildSyntax(colorScheme)
const autocompleteItem = {
cornerRadius: 6,
padding: {
bottom: 2,
left: 6,
right: 6,
top: 2,
},
};
function diagnostic(layer: Layer, styleSet: StyleSets) {
return {
textColor: syntax.primary.color,
background: background(layer),
activeLineBackground: withOpacity(background(layer, "on"), 0.75),
highlightedLineBackground: background(layer, "on"),
codeActions: {
indicator: {
color: foreground(layer, "variant"),
textScaleFactor: 0.857,
header: {
border: border(layer, {
top: true,
}),
},
message: {
text: text(layer, "sans", styleSet, "default", { size: "sm" }),
highlightText: text(layer, "sans", styleSet, "default", {
size: "sm",
weight: "bold",
}),
},
};
}
clicked: {
color: foreground(layer, "base"),
},
hover: {
color: foreground(layer, "on"),
},
active: {
color: foreground(layer, "on"),
},
},
verticalScale: 0.55,
},
folds: {
iconWidth: 8,
foldedIcon: "icons/chevron_right_8.svg",
foldableIcon: "icons/chevron_down_8.svg",
indicator: {
color: foreground(layer, "variant"),
const syntax = {
primary: {
color: colorScheme.ramps.neutral(1).hex(),
weight: fontWeights.normal,
},
"variable.special": {
// Highlights for self, this, etc
color: colorScheme.ramps.blue(0.7).hex(),
weight: fontWeights.normal,
},
comment: {
color: colorScheme.ramps.neutral(0.71).hex(),
weight: fontWeights.normal,
},
punctuation: {
color: colorScheme.ramps.neutral(0.86).hex(),
weight: fontWeights.normal,
},
constant: {
color: colorScheme.ramps.green(0.5).hex(),
weight: fontWeights.normal,
},
keyword: {
color: colorScheme.ramps.blue(0.5).hex(),
weight: fontWeights.normal,
},
function: {
color: colorScheme.ramps.yellow(0.5).hex(),
weight: fontWeights.normal,
},
type: {
color: colorScheme.ramps.cyan(0.5).hex(),
weight: fontWeights.normal,
},
constructor: {
color: colorScheme.ramps.blue(0.5).hex(),
weight: fontWeights.normal,
},
variant: {
color: colorScheme.ramps.blue(0.5).hex(),
weight: fontWeights.normal,
},
property: {
color: colorScheme.ramps.blue(0.5).hex(),
weight: fontWeights.normal,
},
enum: {
color: colorScheme.ramps.orange(0.5).hex(),
weight: fontWeights.normal,
},
operator: {
color: colorScheme.ramps.orange(0.5).hex(),
weight: fontWeights.normal,
},
string: {
color: colorScheme.ramps.orange(0.5).hex(),
weight: fontWeights.normal,
},
number: {
color: colorScheme.ramps.green(0.5).hex(),
weight: fontWeights.normal,
},
boolean: {
color: colorScheme.ramps.green(0.5).hex(),
weight: fontWeights.normal,
},
predictive: {
color: colorScheme.ramps.neutral(0.57).hex(),
weight: fontWeights.normal,
},
title: {
color: colorScheme.ramps.yellow(0.5).hex(),
weight: fontWeights.bold,
},
emphasis: {
color: colorScheme.ramps.blue(0.5).hex(),
weight: fontWeights.normal,
},
"emphasis.strong": {
color: colorScheme.ramps.blue(0.5).hex(),
weight: fontWeights.bold,
},
linkUri: {
color: colorScheme.ramps.green(0.5).hex(),
weight: fontWeights.normal,
underline: true,
},
linkText: {
color: colorScheme.ramps.orange(0.5).hex(),
weight: fontWeights.normal,
italic: true,
},
};
clicked: {
color: foreground(layer, "base"),
},
hover: {
color: foreground(layer, "on"),
},
active: {
color: foreground(layer, "on"),
},
},
ellipses: {
textColor: colorScheme.ramps.neutral(0.71).hex(),
cornerRadiusFactor: 0.15,
background: {
// Copied from hover_popover highlight
color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(),
hover: {
color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(),
},
clicked: {
color: colorScheme.ramps.neutral(0.5).alpha(0.7).hex(),
},
}
},
foldBackground: foreground(layer, "variant"),
return {
textColor: syntax.primary.color,
background: background(layer),
activeLineBackground: withOpacity(background(layer, "on"), 0.75),
highlightedLineBackground: background(layer, "on"),
codeActions: {
indicator: foreground(layer, "variant"),
verticalScale: 0.55,
},
diff: {
deleted: foreground(layer, "negative"),
modified: foreground(layer, "warning"),
inserted: foreground(layer, "positive"),
removedWidthEm: 0.275,
widthEm: 0.16,
cornerRadius: 0.05,
},
/** Highlights matching occurences of what is under the cursor
* as well as matched brackets
*/
documentHighlightReadBackground: withOpacity(foreground(layer, "accent"), 0.1),
documentHighlightWriteBackground: colorScheme.ramps
.neutral(0.5)
.alpha(0.4)
.hex(), // TODO: This was blend * 2
errorColor: background(layer, "negative"),
gutterBackground: background(layer),
gutterPaddingFactor: 3.5,
lineNumber: withOpacity(foreground(layer), 0.35),
lineNumberActive: foreground(layer),
renameFade: 0.6,
unnecessaryCodeFade: 0.5,
selection: colorScheme.players[0],
guestSelections: [
colorScheme.players[1],
colorScheme.players[2],
colorScheme.players[3],
colorScheme.players[4],
colorScheme.players[5],
colorScheme.players[6],
colorScheme.players[7],
],
autocomplete: {
background: background(colorScheme.middle),
cornerRadius: 8,
padding: 4,
margin: {
left: -14,
},
border: border(colorScheme.middle),
shadow: colorScheme.popoverShadow,
matchHighlight: foreground(colorScheme.middle, "accent"),
item: autocompleteItem,
hoveredItem: {
...autocompleteItem,
matchHighlight: foreground(colorScheme.middle, "accent", "hovered"),
background: background(colorScheme.middle, "hovered"),
},
selectedItem: {
...autocompleteItem,
matchHighlight: foreground(colorScheme.middle, "accent", "active"),
background: background(colorScheme.middle, "active"),
},
},
diagnosticHeader: {
background: background(colorScheme.middle),
iconWidthFactor: 1.5,
textScaleFactor: 0.857,
border: border(colorScheme.middle, {
bottom: true,
top: true,
}),
code: {
...text(colorScheme.middle, "mono", { size: "sm" }),
margin: {
left: 10,
},
diff: {
deleted: foreground(layer, "negative"),
modified: foreground(layer, "warning"),
inserted: foreground(layer, "positive"),
removedWidthEm: 0.275,
widthEm: 0.16,
cornerRadius: 0.05,
},
message: {
highlightText: text(colorScheme.middle, "sans", {
size: "sm",
weight: "bold",
}),
text: text(colorScheme.middle, "sans", { size: "sm" }),
},
},
diagnosticPathHeader: {
background: background(colorScheme.middle),
textScaleFactor: 0.857,
filename: text(colorScheme.middle, "mono", { size: "sm" }),
path: {
...text(colorScheme.middle, "mono", { size: "sm" }),
margin: {
left: 12,
},
/** Highlights matching occurences of what is under the cursor
* as well as matched brackets
*/
documentHighlightReadBackground: withOpacity(
foreground(layer, "accent"),
0.1
),
documentHighlightWriteBackground: colorScheme.ramps
.neutral(0.5)
.alpha(0.4)
.hex(), // TODO: This was blend * 2
errorColor: background(layer, "negative"),
gutterBackground: background(layer),
gutterPaddingFactor: 3.5,
lineNumber: withOpacity(foreground(layer), 0.35),
lineNumberActive: foreground(layer),
renameFade: 0.6,
unnecessaryCodeFade: 0.5,
selection: colorScheme.players[0],
guestSelections: [
colorScheme.players[1],
colorScheme.players[2],
colorScheme.players[3],
colorScheme.players[4],
colorScheme.players[5],
colorScheme.players[6],
colorScheme.players[7],
],
autocomplete: {
background: background(colorScheme.middle),
cornerRadius: 8,
padding: 4,
margin: {
left: -14,
},
border: border(colorScheme.middle),
shadow: colorScheme.popoverShadow,
matchHighlight: foreground(colorScheme.middle, "accent"),
item: autocompleteItem,
hoveredItem: {
...autocompleteItem,
matchHighlight: foreground(
colorScheme.middle,
"accent",
"hovered"
),
background: background(colorScheme.middle, "hovered"),
},
selectedItem: {
...autocompleteItem,
matchHighlight: foreground(
colorScheme.middle,
"accent",
"active"
),
background: background(colorScheme.middle, "active"),
},
},
},
errorDiagnostic: diagnostic(colorScheme.middle, "negative"),
warningDiagnostic: diagnostic(colorScheme.middle, "warning"),
informationDiagnostic: diagnostic(colorScheme.middle, "accent"),
hintDiagnostic: diagnostic(colorScheme.middle, "warning"),
invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"),
invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"),
invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"),
invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"),
hoverPopover: hoverPopover(colorScheme),
linkDefinition: {
color: syntax.linkUri.color,
underline: syntax.linkUri.underline,
},
jumpIcon: {
color: foreground(layer, "on"),
iconWidth: 20,
buttonWidth: 20,
cornerRadius: 6,
padding: {
top: 6,
bottom: 6,
left: 6,
right: 6,
},
hover: {
background: background(layer, "on", "hovered"),
},
},
scrollbar: {
width: 12,
minHeightFactor: 1.0,
track: {
border: border(layer, "variant", { left: true }),
},
thumb: {
background: withOpacity(background(layer, "inverted"), 0.4),
border: {
width: 1,
color: borderColor(layer, "variant"),
},
diagnosticHeader: {
background: background(colorScheme.middle),
iconWidthFactor: 1.5,
textScaleFactor: 0.857,
border: border(colorScheme.middle, {
bottom: true,
top: true,
}),
code: {
...text(colorScheme.middle, "mono", { size: "sm" }),
margin: {
left: 10,
},
},
message: {
highlightText: text(colorScheme.middle, "sans", {
size: "sm",
weight: "bold",
}),
text: text(colorScheme.middle, "sans", { size: "sm" }),
},
},
diagnosticPathHeader: {
background: background(colorScheme.middle),
textScaleFactor: 0.857,
filename: text(colorScheme.middle, "mono", { size: "sm" }),
path: {
...text(colorScheme.middle, "mono", { size: "sm" }),
margin: {
left: 12,
},
},
},
errorDiagnostic: diagnostic(colorScheme.middle, "negative"),
warningDiagnostic: diagnostic(colorScheme.middle, "warning"),
informationDiagnostic: diagnostic(colorScheme.middle, "accent"),
hintDiagnostic: diagnostic(colorScheme.middle, "warning"),
invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"),
invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"),
invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"),
invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"),
hoverPopover: hoverPopover(colorScheme),
linkDefinition: {
color: syntax.linkUri.color,
underline: syntax.linkUri.underline,
},
jumpIcon: {
color: foreground(layer, "on"),
iconWidth: 20,
buttonWidth: 20,
cornerRadius: 6,
padding: {
top: 6,
bottom: 6,
left: 6,
right: 6,
},
hover: {
background: background(layer, "on", "hovered"),
},
},
scrollbar: {
width: 12,
minHeightFactor: 1.0,
track: {
border: border(layer, "variant", { left: true }),
},
thumb: {
background: withOpacity(background(layer, "inverted"), 0.4),
border: {
width: 1,
color: borderColor(layer, "variant"),
},
},
},
compositionMark: {
underline: {
thickness: 1.0,
color: borderColor(layer),
},
},
syntax,
}
},
},
compositionMark: {
underline: {
thickness: 1.0,
color: borderColor(layer),
},
},
syntax,
};
}

View File

@@ -1,44 +1,37 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, border, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, text } from "./components";
export default function feedback(colorScheme: ColorScheme) {
let layer = colorScheme.highest
let layer = colorScheme.highest;
return {
submit_button: {
...text(layer, "mono", "on"),
background: background(layer, "on"),
cornerRadius: 6,
border: border(layer, "on"),
margin: {
right: 4,
},
padding: {
bottom: 2,
left: 10,
right: 10,
top: 2,
},
clicked: {
...text(layer, "mono", "on", "pressed"),
background: background(layer, "on", "pressed"),
border: border(layer, "on", "pressed"),
},
hover: {
...text(layer, "mono", "on", "hovered"),
background: background(layer, "on", "hovered"),
border: border(layer, "on", "hovered"),
},
},
button_margin: 8,
info_text_default: text(layer, "sans", "default", { size: "xs" }),
link_text_default: text(layer, "sans", "default", {
size: "xs",
underline: true,
}),
link_text_hover: text(layer, "sans", "hovered", {
size: "xs",
underline: true,
}),
}
return {
submit_button: {
...text(layer, "mono", "on"),
background: background(layer, "on"),
cornerRadius: 6,
border: border(layer, "on"),
margin: {
right: 4,
},
padding: {
bottom: 2,
left: 10,
right: 10,
top: 2,
},
clicked: {
...text(layer, "mono", "on", "pressed"),
background: background(layer, "on", "pressed"),
border: border(layer, "on", "pressed"),
},
hover: {
...text(layer, "mono", "on", "hovered"),
background: background(layer, "on", "hovered"),
border: border(layer, "on", "hovered"),
},
},
button_margin: 8,
info_text: text(layer, "sans", "default", { size: "xs" }),
};
}

View File

@@ -1,45 +1,45 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, border, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, text } from "./components";
export default function HoverPopover(colorScheme: ColorScheme) {
let layer = colorScheme.middle
let baseContainer = {
background: background(layer),
cornerRadius: 8,
padding: {
left: 8,
right: 8,
top: 4,
bottom: 4,
},
shadow: colorScheme.popoverShadow,
border: border(layer),
margin: {
left: -8,
},
}
let layer = colorScheme.middle;
let baseContainer = {
background: background(layer),
cornerRadius: 8,
padding: {
left: 8,
right: 8,
top: 4,
bottom: 4,
},
shadow: colorScheme.popoverShadow,
border: border(layer),
margin: {
left: -8,
},
};
return {
container: baseContainer,
infoContainer: {
...baseContainer,
background: background(layer, "accent"),
border: border(layer, "accent"),
},
warningContainer: {
...baseContainer,
background: background(layer, "warning"),
border: border(layer, "warning"),
},
errorContainer: {
...baseContainer,
background: background(layer, "negative"),
border: border(layer, "negative"),
},
block_style: {
padding: { top: 4 },
},
prose: text(layer, "sans", { size: "sm" }),
highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better
}
return {
container: baseContainer,
infoContainer: {
...baseContainer,
background: background(layer, "accent"),
border: border(layer, "accent"),
},
warningContainer: {
...baseContainer,
background: background(layer, "warning"),
border: border(layer, "warning"),
},
errorContainer: {
...baseContainer,
background: background(layer, "negative"),
border: border(layer, "negative"),
},
block_style: {
padding: { top: 4 },
},
prose: text(layer, "sans", { size: "sm" }),
highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better
};
}

View File

@@ -1,53 +1,45 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, border, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, text } from "./components";
export default function incomingCallNotification(
colorScheme: ColorScheme
): Object {
let layer = colorScheme.middle
const avatarSize = 48
return {
windowHeight: 74,
windowWidth: 380,
background: background(layer),
callerContainer: {
padding: 12,
},
callerAvatar: {
height: avatarSize,
width: avatarSize,
cornerRadius: avatarSize / 2,
},
callerMetadata: {
margin: { left: 10 },
},
callerUsername: {
...text(layer, "sans", { size: "sm", weight: "bold" }),
margin: { top: -3 },
},
callerMessage: {
...text(layer, "sans", "variant", { size: "xs" }),
margin: { top: -3 },
},
worktreeRoots: {
...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
margin: { top: -3 },
},
buttonWidth: 96,
acceptButton: {
background: background(layer, "accent"),
border: border(layer, { left: true, bottom: true }),
...text(layer, "sans", "positive", {
size: "xs",
weight: "extra_bold",
}),
},
declineButton: {
border: border(layer, { left: true }),
...text(layer, "sans", "negative", {
size: "xs",
weight: "extra_bold",
}),
},
}
export default function incomingCallNotification(colorScheme: ColorScheme): Object {
let layer = colorScheme.middle;
const avatarSize = 48;
return {
windowHeight: 74,
windowWidth: 380,
background: background(layer),
callerContainer: {
padding: 12,
},
callerAvatar: {
height: avatarSize,
width: avatarSize,
cornerRadius: avatarSize / 2,
},
callerMetadata: {
margin: { left: 10 },
},
callerUsername: {
...text(layer, "sans", { size: "sm", weight: "bold" }),
margin: { top: -3 },
},
callerMessage: {
...text(layer, "sans", "variant", { size: "xs" }),
margin: { top: -3 },
},
worktreeRoots: {
...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
margin: { top: -3 },
},
buttonWidth: 96,
acceptButton: {
background: background(layer, "accent"),
border: border(layer, { left: true, bottom: true }),
...text(layer, "sans", "positive", { size: "xs", weight: "extra_bold" })
},
declineButton: {
border: border(layer, { left: true }),
...text(layer, "sans", "negative", { size: "xs", weight: "extra_bold" })
},
};
}

View File

@@ -1,78 +1,78 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, border, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, text } from "./components";
export default function picker(colorScheme: ColorScheme) {
let layer = colorScheme.lowest
const container = {
background: background(layer),
border: border(layer),
shadow: colorScheme.modalShadow,
cornerRadius: 12,
padding: {
bottom: 4,
},
let layer = colorScheme.lowest;
const container = {
background: background(layer),
border: border(layer),
shadow: colorScheme.modalShadow,
cornerRadius: 12,
padding: {
bottom: 4,
}
const inputEditor = {
placeholderText: text(layer, "sans", "on", "disabled"),
selection: colorScheme.players[0],
text: text(layer, "mono", "on"),
border: border(layer, { bottom: true }),
padding: {
bottom: 8,
left: 16,
right: 16,
top: 8,
},
margin: {
bottom: 4,
},
}
const emptyInputEditor = { ...inputEditor }
delete emptyInputEditor.border
delete emptyInputEditor.margin
};
const inputEditor = {
placeholderText: text(layer, "sans", "on", "disabled"),
selection: colorScheme.players[0],
text: text(layer, "mono", "on"),
border: border(layer, { bottom: true }),
padding: {
bottom: 8,
left: 16,
right: 16,
top: 8,
},
margin: {
bottom: 4,
},
};
const emptyInputEditor = { ...inputEditor };
delete emptyInputEditor.border;
delete emptyInputEditor.margin;
return {
...container,
emptyContainer: {
...container,
padding: {},
},
item: {
padding: {
bottom: 4,
left: 12,
right: 12,
top: 4,
},
margin: {
top: 1,
left: 4,
right: 4,
},
cornerRadius: 8,
text: text(layer, "sans", "variant"),
highlightText: text(layer, "sans", "accent", { weight: "bold" }),
active: {
background: background(layer, "base", "active"),
text: text(layer, "sans", "base", "active"),
highlightText: text(layer, "sans", "accent", {
weight: "bold",
}),
},
hover: {
background: background(layer, "hovered"),
},
},
inputEditor,
emptyInputEditor,
noMatches: {
text: text(layer, "sans", "variant"),
padding: {
bottom: 8,
left: 16,
right: 16,
top: 8,
},
},
}
return {
...container,
emptyContainer: {
...container,
padding: {}
},
item: {
padding: {
bottom: 4,
left: 12,
right: 12,
top: 4,
},
margin: {
top: 1,
left: 4,
right: 4,
},
cornerRadius: 8,
text: text(layer, "sans", "variant"),
highlightText: text(layer, "sans", "accent", { weight: "bold" }),
active: {
background: background(layer, "base", "active"),
text: text(layer, "sans", "base", "active"),
highlightText: text(layer, "sans", "accent", {
weight: "bold",
}),
},
hover: {
background: background(layer, "hovered"),
},
},
inputEditor,
emptyInputEditor,
noMatches: {
text: text(layer, "sans", "variant"),
padding: {
bottom: 8,
left: 16,
right: 16,
top: 8,
},
},
};
}

View File

@@ -1,13 +1,13 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { background, text } from "./components"
import { ColorScheme } from "../themes/common/colorScheme";
import { background, text } from "./components";
export default function projectDiagnostics(colorScheme: ColorScheme) {
let layer = colorScheme.highest
return {
background: background(layer),
tabIconSpacing: 4,
tabIconWidth: 13,
tabSummarySpacing: 10,
emptyMessage: text(layer, "sans", "variant", { size: "md" }),
}
let layer = colorScheme.highest;
return {
background: background(layer),
tabIconSpacing: 4,
tabIconWidth: 13,
tabSummarySpacing: 10,
emptyMessage: text(layer, "sans", "variant", { size: "md" }),
};
}

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