Merge branch 'main' into lsp-log-messages

This commit is contained in:
Max Brunsfeld 2023-06-08 08:46:05 -07:00
commit afaff7f9a9
67 changed files with 1002 additions and 730 deletions

1
Cargo.lock generated
View File

@ -2440,6 +2440,7 @@ dependencies = [
"parking_lot 0.11.2",
"regex",
"rope",
"rpc",
"serde",
"serde_derive",
"serde_json",

View File

@ -66,6 +66,7 @@ impl<'a> AddAssign<&'a Local> for Local {
}
}
/// A vector clock
#[derive(Clone, Default, Hash, Eq, PartialEq)]
pub struct Global(SmallVec<[u32; 8]>);

View File

@ -76,6 +76,7 @@ CREATE TABLE "worktree_entries" (
"is_symlink" BOOL NOT NULL,
"is_ignored" BOOL NOT NULL,
"is_deleted" BOOL NOT NULL,
"git_status" INTEGER,
PRIMARY KEY(project_id, worktree_id, id),
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
);
@ -96,22 +97,6 @@ CREATE TABLE "worktree_repositories" (
CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id");
CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id");
CREATE TABLE "worktree_repository_statuses" (
"project_id" INTEGER NOT NULL,
"worktree_id" INTEGER NOT NULL,
"work_directory_id" INTEGER NOT NULL,
"repo_path" VARCHAR NOT NULL,
"status" INTEGER NOT NULL,
"scan_id" INTEGER NOT NULL,
"is_deleted" BOOL NOT NULL,
PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path),
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
);
CREATE INDEX "index_worktree_repository_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id");
CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id_and_work_directory_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
CREATE TABLE "worktree_settings_files" (
"project_id" INTEGER NOT NULL,
"worktree_id" INTEGER NOT NULL,

View File

@ -0,0 +1,4 @@
DROP TABLE "worktree_repository_statuses";
ALTER TABLE "worktree_entries"
ADD "git_status" INT8;

View File

@ -1539,6 +1539,7 @@ impl Database {
}),
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
git_status: db_entry.git_status.map(|status| status as i32),
});
}
}
@ -1573,54 +1574,6 @@ impl Database {
worktree.updated_repositories.push(proto::RepositoryEntry {
work_directory_id: db_repository.work_directory_id as u64,
branch: db_repository.branch,
removed_repo_paths: Default::default(),
updated_statuses: Default::default(),
});
}
}
}
// Repository Status Entries
for repository in worktree.updated_repositories.iter_mut() {
let repository_status_entry_filter =
if let Some(rejoined_worktree) = rejoined_worktree {
worktree_repository_statuses::Column::ScanId
.gt(rejoined_worktree.scan_id)
} else {
worktree_repository_statuses::Column::IsDeleted.eq(false)
};
let mut db_repository_statuses =
worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(
worktree_repository_statuses::Column::ProjectId
.eq(project.id),
)
.add(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree.id),
)
.add(
worktree_repository_statuses::Column::WorkDirectoryId
.eq(repository.work_directory_id),
)
.add(repository_status_entry_filter),
)
.stream(&*tx)
.await?;
while let Some(db_status_entry) = db_repository_statuses.next().await {
let db_status_entry = db_status_entry?;
if db_status_entry.is_deleted {
repository
.removed_repo_paths
.push(db_status_entry.repo_path);
} else {
repository.updated_statuses.push(proto::StatusEntry {
repo_path: db_status_entry.repo_path,
status: db_status_entry.status as i32,
});
}
}
@ -2396,6 +2349,7 @@ impl Database {
mtime_nanos: ActiveValue::set(mtime.nanos as i32),
is_symlink: ActiveValue::set(entry.is_symlink),
is_ignored: ActiveValue::set(entry.is_ignored),
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
is_deleted: ActiveValue::set(false),
scan_id: ActiveValue::set(update.scan_id as i64),
}
@ -2414,6 +2368,7 @@ impl Database {
worktree_entry::Column::MtimeNanos,
worktree_entry::Column::IsSymlink,
worktree_entry::Column::IsIgnored,
worktree_entry::Column::GitStatus,
worktree_entry::Column::ScanId,
])
.to_owned(),
@ -2467,68 +2422,6 @@ impl Database {
)
.exec(&*tx)
.await?;
for repository in update.updated_repositories.iter() {
if !repository.updated_statuses.is_empty() {
worktree_repository_statuses::Entity::insert_many(
repository.updated_statuses.iter().map(|status_entry| {
worktree_repository_statuses::ActiveModel {
project_id: ActiveValue::set(project_id),
worktree_id: ActiveValue::set(worktree_id),
work_directory_id: ActiveValue::set(
repository.work_directory_id as i64,
),
repo_path: ActiveValue::set(status_entry.repo_path.clone()),
status: ActiveValue::set(status_entry.status as i64),
scan_id: ActiveValue::set(update.scan_id as i64),
is_deleted: ActiveValue::set(false),
}
}),
)
.on_conflict(
OnConflict::columns([
worktree_repository_statuses::Column::ProjectId,
worktree_repository_statuses::Column::WorktreeId,
worktree_repository_statuses::Column::WorkDirectoryId,
worktree_repository_statuses::Column::RepoPath,
])
.update_columns([
worktree_repository_statuses::Column::ScanId,
worktree_repository_statuses::Column::Status,
worktree_repository_statuses::Column::IsDeleted,
])
.to_owned(),
)
.exec(&*tx)
.await?;
}
if !repository.removed_repo_paths.is_empty() {
worktree_repository_statuses::Entity::update_many()
.filter(
worktree_repository_statuses::Column::ProjectId
.eq(project_id)
.and(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree_id),
)
.and(
worktree_repository_statuses::Column::WorkDirectoryId
.eq(repository.work_directory_id as i64),
)
.and(worktree_repository_statuses::Column::RepoPath.is_in(
repository.removed_repo_paths.iter().map(String::as_str),
)),
)
.set(worktree_repository_statuses::ActiveModel {
is_deleted: ActiveValue::Set(true),
scan_id: ActiveValue::Set(update.scan_id as i64),
..Default::default()
})
.exec(&*tx)
.await?;
}
}
}
if !update.removed_repositories.is_empty() {
@ -2812,6 +2705,7 @@ impl Database {
}),
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
git_status: db_entry.git_status.map(|status| status as i32),
});
}
}
@ -2837,41 +2731,12 @@ impl Database {
proto::RepositoryEntry {
work_directory_id: db_repository_entry.work_directory_id as u64,
branch: db_repository_entry.branch,
removed_repo_paths: Default::default(),
updated_statuses: Default::default(),
},
);
}
}
}
{
let mut db_status_entries = worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(worktree_repository_statuses::Column::ProjectId.eq(project_id))
.add(worktree_repository_statuses::Column::IsDeleted.eq(false)),
)
.stream(&*tx)
.await?;
while let Some(db_status_entry) = db_status_entries.next().await {
let db_status_entry = db_status_entry?;
if let Some(worktree) = worktrees.get_mut(&(db_status_entry.worktree_id as u64))
{
if let Some(repository_entry) = worktree
.repository_entries
.get_mut(&(db_status_entry.work_directory_id as u64))
{
repository_entry.updated_statuses.push(proto::StatusEntry {
repo_path: db_status_entry.repo_path,
status: db_status_entry.status as i32,
});
}
}
}
}
// Populate worktree diagnostic summaries.
{
let mut db_summaries = worktree_diagnostic_summary::Entity::find()

View File

@ -15,6 +15,7 @@ pub struct Model {
pub inode: i64,
pub mtime_seconds: i64,
pub mtime_nanos: i32,
pub git_status: Option<i64>,
pub is_symlink: bool,
pub is_ignored: bool,
pub is_deleted: bool,

View File

@ -2415,14 +2415,10 @@ async fn test_git_diff_base_change(
"
.unindent();
client_a
.fs
.as_fake()
.set_index_for_repo(
Path::new("/dir/.git"),
&[(Path::new("a.txt"), diff_base.clone())],
)
.await;
client_a.fs.as_fake().set_index_for_repo(
Path::new("/dir/.git"),
&[(Path::new("a.txt"), diff_base.clone())],
);
// Create the buffer
let buffer_local_a = project_local
@ -2464,14 +2460,10 @@ async fn test_git_diff_base_change(
);
});
client_a
.fs
.as_fake()
.set_index_for_repo(
Path::new("/dir/.git"),
&[(Path::new("a.txt"), new_diff_base.clone())],
)
.await;
client_a.fs.as_fake().set_index_for_repo(
Path::new("/dir/.git"),
&[(Path::new("a.txt"), new_diff_base.clone())],
);
// Wait for buffer_local_a to receive it
deterministic.run_until_parked();
@ -2513,14 +2505,10 @@ async fn test_git_diff_base_change(
"
.unindent();
client_a
.fs
.as_fake()
.set_index_for_repo(
Path::new("/dir/sub/.git"),
&[(Path::new("b.txt"), diff_base.clone())],
)
.await;
client_a.fs.as_fake().set_index_for_repo(
Path::new("/dir/sub/.git"),
&[(Path::new("b.txt"), diff_base.clone())],
);
// Create the buffer
let buffer_local_b = project_local
@ -2562,14 +2550,10 @@ async fn test_git_diff_base_change(
);
});
client_a
.fs
.as_fake()
.set_index_for_repo(
Path::new("/dir/sub/.git"),
&[(Path::new("b.txt"), new_diff_base.clone())],
)
.await;
client_a.fs.as_fake().set_index_for_repo(
Path::new("/dir/sub/.git"),
&[(Path::new("b.txt"), new_diff_base.clone())],
);
// Wait for buffer_local_b to receive it
deterministic.run_until_parked();
@ -2646,8 +2630,7 @@ async fn test_git_branch_name(
client_a
.fs
.as_fake()
.set_branch_name(Path::new("/dir/.git"), Some("branch-1"))
.await;
.set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
// Wait for it to catch up to the new branch
deterministic.run_until_parked();
@ -2673,8 +2656,7 @@ async fn test_git_branch_name(
client_a
.fs
.as_fake()
.set_branch_name(Path::new("/dir/.git"), Some("branch-2"))
.await;
.set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
// Wait for buffer_local_a to receive it
deterministic.run_until_parked();
@ -2726,17 +2708,13 @@ async fn test_git_status_sync(
const A_TXT: &'static str = "a.txt";
const B_TXT: &'static str = "b.txt";
client_a
.fs
.as_fake()
.set_status_for_repo(
Path::new("/dir/.git"),
&[
(&Path::new(A_TXT), GitFileStatus::Added),
(&Path::new(B_TXT), GitFileStatus::Added),
],
)
.await;
client_a.fs.as_fake().set_status_for_repo_via_git_operation(
Path::new("/dir/.git"),
&[
(&Path::new(A_TXT), GitFileStatus::Added),
(&Path::new(B_TXT), GitFileStatus::Added),
],
);
let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
let project_id = active_call_a
@ -2763,8 +2741,7 @@ async fn test_git_status_sync(
assert_eq!(worktrees.len(), 1);
let worktree = worktrees[0].clone();
let snapshot = worktree.read(cx).snapshot();
let root_entry = snapshot.root_git_entry().unwrap();
assert_eq!(root_entry.status_for_file(&snapshot, file), status);
assert_eq!(snapshot.status_for_file(file), status);
}
// Smoke test status reading
@ -2780,14 +2757,13 @@ async fn test_git_status_sync(
client_a
.fs
.as_fake()
.set_status_for_repo(
.set_status_for_repo_via_working_copy_change(
Path::new("/dir/.git"),
&[
(&Path::new(A_TXT), GitFileStatus::Modified),
(&Path::new(B_TXT), GitFileStatus::Modified),
],
)
.await;
);
// Wait for buffer_local_a to receive it
deterministic.run_until_parked();

View File

@ -422,7 +422,7 @@ async fn apply_client_operation(
);
ensure_project_shared(&project, client, cx).await;
if !client.fs.paths().contains(&new_root_path) {
if !client.fs.paths(false).contains(&new_root_path) {
client.fs.create_dir(&new_root_path).await.unwrap();
}
project
@ -628,12 +628,13 @@ async fn apply_client_operation(
ensure_project_shared(&project, client, cx).await;
let requested_version = buffer.read_with(cx, |buffer, _| buffer.version());
let save = project.update(cx, |project, cx| project.save_buffer(buffer, cx));
let save = cx.background().spawn(async move {
let (saved_version, _, _) = save
.await
let save = project.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx));
let save = cx.spawn(|cx| async move {
save.await
.map_err(|err| anyhow!("save request failed: {:?}", err))?;
assert!(saved_version.observed_all(&requested_version));
assert!(buffer
.read_with(&cx, |buffer, _| { buffer.saved_version().to_owned() })
.observed_all(&requested_version));
anyhow::Ok(())
});
if detach {
@ -743,7 +744,7 @@ async fn apply_client_operation(
} => {
if !client
.fs
.directories()
.directories(false)
.contains(&path.parent().unwrap().to_owned())
{
return Err(TestError::Inapplicable);
@ -770,10 +771,16 @@ async fn apply_client_operation(
repo_path,
contents,
} => {
if !client.fs.directories().contains(&repo_path) {
if !client.fs.directories(false).contains(&repo_path) {
return Err(TestError::Inapplicable);
}
for (path, _) in contents.iter() {
if !client.fs.files().contains(&repo_path.join(path)) {
return Err(TestError::Inapplicable);
}
}
log::info!(
"{}: writing git index for repo {:?}: {:?}",
client.username,
@ -789,13 +796,13 @@ async fn apply_client_operation(
if client.fs.metadata(&dot_git_dir).await?.is_none() {
client.fs.create_dir(&dot_git_dir).await?;
}
client.fs.set_index_for_repo(&dot_git_dir, &contents).await;
client.fs.set_index_for_repo(&dot_git_dir, &contents);
}
GitOperation::WriteGitBranch {
repo_path,
new_branch,
} => {
if !client.fs.directories().contains(&repo_path) {
if !client.fs.directories(false).contains(&repo_path) {
return Err(TestError::Inapplicable);
}
@ -810,15 +817,21 @@ async fn apply_client_operation(
if client.fs.metadata(&dot_git_dir).await?.is_none() {
client.fs.create_dir(&dot_git_dir).await?;
}
client.fs.set_branch_name(&dot_git_dir, new_branch).await;
client.fs.set_branch_name(&dot_git_dir, new_branch);
}
GitOperation::WriteGitStatuses {
repo_path,
statuses,
git_operation,
} => {
if !client.fs.directories().contains(&repo_path) {
if !client.fs.directories(false).contains(&repo_path) {
return Err(TestError::Inapplicable);
}
for (path, _) in statuses.iter() {
if !client.fs.files().contains(&repo_path.join(path)) {
return Err(TestError::Inapplicable);
}
}
log::info!(
"{}: writing git statuses for repo {:?}: {:?}",
@ -838,10 +851,16 @@ async fn apply_client_operation(
client.fs.create_dir(&dot_git_dir).await?;
}
client
.fs
.set_status_for_repo(&dot_git_dir, statuses.as_slice())
.await;
if git_operation {
client
.fs
.set_status_for_repo_via_git_operation(&dot_git_dir, statuses.as_slice());
} else {
client.fs.set_status_for_repo_via_working_copy_change(
&dot_git_dir,
statuses.as_slice(),
);
}
}
},
}
@ -913,9 +932,10 @@ fn check_consistency_between_clients(clients: &[(Rc<TestClient>, TestAppContext)
assert_eq!(
guest_snapshot.entries(false).collect::<Vec<_>>(),
host_snapshot.entries(false).collect::<Vec<_>>(),
"{} has different snapshot than the host for worktree {:?} and project {:?}",
"{} has different snapshot than the host for worktree {:?} ({:?}) and project {:?}",
client.username,
host_snapshot.abs_path(),
id,
guest_project.remote_id(),
);
assert_eq!(guest_snapshot.repositories().collect::<Vec<_>>(), host_snapshot.repositories().collect::<Vec<_>>(),
@ -1230,6 +1250,7 @@ enum GitOperation {
WriteGitStatuses {
repo_path: PathBuf,
statuses: Vec<(PathBuf, GitFileStatus)>,
git_operation: bool,
},
}
@ -1575,7 +1596,7 @@ impl TestPlan {
.choose(&mut self.rng)
.cloned() else { continue };
let project_root_name = root_name_for_project(&project, cx);
let mut paths = client.fs.paths();
let mut paths = client.fs.paths(false);
paths.remove(0);
let new_root_path = if paths.is_empty() || self.rng.gen() {
Path::new("/").join(&self.next_root_dir_name(user_id))
@ -1755,7 +1776,7 @@ impl TestPlan {
let is_dir = self.rng.gen::<bool>();
let content;
let mut path;
let dir_paths = client.fs.directories();
let dir_paths = client.fs.directories(false);
if is_dir {
content = String::new();
@ -1809,7 +1830,7 @@ impl TestPlan {
let repo_path = client
.fs
.directories()
.directories(false)
.choose(&mut self.rng)
.unwrap()
.clone();
@ -1855,9 +1876,12 @@ impl TestPlan {
})
.collect::<Vec<_>>();
let git_operation = self.rng.gen::<bool>();
GitOperation::WriteGitStatuses {
repo_path,
statuses,
git_operation,
}
}
_ => unreachable!(),

View File

@ -14,6 +14,8 @@ lsp = { path = "../lsp" }
rope = { path = "../rope" }
util = { path = "../util" }
sum_tree = { path = "../sum_tree" }
rpc = { path = "../rpc" }
anyhow.workspace = true
async-trait.workspace = true
futures.workspace = true

View File

@ -29,6 +29,8 @@ use collections::{btree_map, BTreeMap};
#[cfg(any(test, feature = "test-support"))]
use repository::{FakeGitRepositoryState, GitFileStatus};
#[cfg(any(test, feature = "test-support"))]
use std::ffi::OsStr;
#[cfg(any(test, feature = "test-support"))]
use std::sync::Weak;
lazy_static! {
@ -501,6 +503,11 @@ impl FakeFsState {
}
}
#[cfg(any(test, feature = "test-support"))]
lazy_static! {
pub static ref FS_DOT_GIT: &'static OsStr = OsStr::new(".git");
}
#[cfg(any(test, feature = "test-support"))]
impl FakeFs {
pub fn new(executor: Arc<gpui::executor::Background>) -> Arc<Self> {
@ -619,7 +626,7 @@ impl FakeFs {
.boxed()
}
pub fn with_git_state<F>(&self, dot_git: &Path, f: F)
pub fn with_git_state<F>(&self, dot_git: &Path, emit_git_event: bool, f: F)
where
F: FnOnce(&mut FakeGitRepositoryState),
{
@ -633,18 +640,22 @@ impl FakeFs {
f(&mut repo_state);
state.emit_event([dot_git]);
if emit_git_event {
state.emit_event([dot_git]);
}
} else {
panic!("not a directory");
}
}
pub async fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
self.with_git_state(dot_git, |state| state.branch_name = branch.map(Into::into))
pub fn set_branch_name(&self, dot_git: &Path, branch: Option<impl Into<String>>) {
self.with_git_state(dot_git, true, |state| {
state.branch_name = branch.map(Into::into)
})
}
pub async fn set_index_for_repo(&self, dot_git: &Path, head_state: &[(&Path, String)]) {
self.with_git_state(dot_git, |state| {
pub fn set_index_for_repo(&self, dot_git: &Path, head_state: &[(&Path, String)]) {
self.with_git_state(dot_git, true, |state| {
state.index_contents.clear();
state.index_contents.extend(
head_state
@ -654,8 +665,32 @@ impl FakeFs {
});
}
pub async fn set_status_for_repo(&self, dot_git: &Path, statuses: &[(&Path, GitFileStatus)]) {
self.with_git_state(dot_git, |state| {
pub fn set_status_for_repo_via_working_copy_change(
&self,
dot_git: &Path,
statuses: &[(&Path, GitFileStatus)],
) {
self.with_git_state(dot_git, false, |state| {
state.worktree_statuses.clear();
state.worktree_statuses.extend(
statuses
.iter()
.map(|(path, content)| ((**path).into(), content.clone())),
);
});
self.state.lock().emit_event(
statuses
.iter()
.map(|(path, _)| dot_git.parent().unwrap().join(path)),
);
}
pub fn set_status_for_repo_via_git_operation(
&self,
dot_git: &Path,
statuses: &[(&Path, GitFileStatus)],
) {
self.with_git_state(dot_git, true, |state| {
state.worktree_statuses.clear();
state.worktree_statuses.extend(
statuses
@ -665,7 +700,7 @@ impl FakeFs {
});
}
pub fn paths(&self) -> Vec<PathBuf> {
pub fn paths(&self, include_dot_git: bool) -> Vec<PathBuf> {
let mut result = Vec::new();
let mut queue = collections::VecDeque::new();
queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
@ -675,12 +710,18 @@ impl FakeFs {
queue.push_back((path.join(name), entry.clone()));
}
}
result.push(path);
if include_dot_git
|| !path
.components()
.any(|component| component.as_os_str() == *FS_DOT_GIT)
{
result.push(path);
}
}
result
}
pub fn directories(&self) -> Vec<PathBuf> {
pub fn directories(&self, include_dot_git: bool) -> Vec<PathBuf> {
let mut result = Vec::new();
let mut queue = collections::VecDeque::new();
queue.push_back((PathBuf::from("/"), self.state.lock().root.clone()));
@ -689,7 +730,13 @@ impl FakeFs {
for (name, entry) in entries {
queue.push_back((path.join(name), entry.clone()));
}
result.push(path);
if include_dot_git
|| !path
.components()
.any(|component| component.as_os_str() == *FS_DOT_GIT)
{
result.push(path);
}
}
}
result

View File

@ -1,6 +1,8 @@
use anyhow::Result;
use collections::HashMap;
use git2::ErrorCode;
use parking_lot::Mutex;
use rpc::proto;
use serde_derive::{Deserialize, Serialize};
use std::{
cmp::Ordering,
@ -24,7 +26,7 @@ pub trait GitRepository: Send {
fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>>;
fn status(&self, path: &RepoPath) -> Option<GitFileStatus>;
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>>;
}
impl std::fmt::Debug for dyn GitRepository {
@ -91,9 +93,18 @@ impl GitRepository for LibGitRepository {
Some(map)
}
fn status(&self, path: &RepoPath) -> Option<GitFileStatus> {
let status = self.status_file(path).log_err()?;
read_status(status)
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>> {
let status = self.status_file(path);
match status {
Ok(status) => Ok(read_status(status)),
Err(e) => {
if e.code() == ErrorCode::NotFound {
Ok(None)
} else {
Err(e.into())
}
}
}
}
}
@ -155,9 +166,9 @@ impl GitRepository for FakeGitRepository {
Some(map)
}
fn status(&self, path: &RepoPath) -> Option<GitFileStatus> {
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>> {
let state = self.state.lock();
state.worktree_statuses.get(path).cloned()
Ok(state.worktree_statuses.get(path).cloned())
}
}
@ -197,8 +208,51 @@ pub enum GitFileStatus {
Conflict,
}
impl GitFileStatus {
pub fn merge(
this: Option<GitFileStatus>,
other: Option<GitFileStatus>,
prefer_other: bool,
) -> Option<GitFileStatus> {
if prefer_other {
return other;
} else {
match (this, other) {
(Some(GitFileStatus::Conflict), _) | (_, Some(GitFileStatus::Conflict)) => {
Some(GitFileStatus::Conflict)
}
(Some(GitFileStatus::Modified), _) | (_, Some(GitFileStatus::Modified)) => {
Some(GitFileStatus::Modified)
}
(Some(GitFileStatus::Added), _) | (_, Some(GitFileStatus::Added)) => {
Some(GitFileStatus::Added)
}
_ => None,
}
}
}
pub fn from_proto(git_status: Option<i32>) -> Option<GitFileStatus> {
git_status.and_then(|status| {
proto::GitStatus::from_i32(status).map(|status| match status {
proto::GitStatus::Added => GitFileStatus::Added,
proto::GitStatus::Modified => GitFileStatus::Modified,
proto::GitStatus::Conflict => GitFileStatus::Conflict,
})
})
}
pub fn to_proto(self) -> i32 {
match self {
GitFileStatus::Added => proto::GitStatus::Added as i32,
GitFileStatus::Modified => proto::GitStatus::Modified as i32,
GitFileStatus::Conflict => proto::GitStatus::Conflict as i32,
}
}
}
#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)]
pub struct RepoPath(PathBuf);
pub struct RepoPath(pub PathBuf);
impl RepoPath {
pub fn new(path: PathBuf) -> Self {

View File

@ -37,8 +37,8 @@ use language::{
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel,
Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, File as _,
Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, Operation, Patch,
PendingLanguageServer, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16,
Transaction, Unclipped,
PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
Unclipped,
};
use log::error;
use lsp::{
@ -69,7 +69,7 @@ use std::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
time::{Duration, Instant, SystemTime},
time::{Duration, Instant},
};
use terminals::Terminals;
use util::{
@ -1618,7 +1618,7 @@ impl Project {
&self,
buffer: ModelHandle<Buffer>,
cx: &mut ModelContext<Self>,
) -> Task<Result<(clock::Global, RopeFingerprint, SystemTime)>> {
) -> Task<Result<()>> {
let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
return Task::ready(Err(anyhow!("buffer doesn't have a file")));
};
@ -5161,9 +5161,9 @@ impl Project {
return None;
}
let path = &project_path.path;
changed_repos.iter().find(|(work_dir, change)| {
path.starts_with(work_dir) && change.git_dir_changed
})?;
changed_repos
.iter()
.find(|(work_dir, _)| path.starts_with(work_dir))?;
let receiver = receiver.clone();
let path = path.clone();
Some(async move {
@ -5186,9 +5186,9 @@ impl Project {
return None;
}
let path = file.path();
changed_repos.iter().find(|(work_dir, change)| {
path.starts_with(work_dir) && change.git_dir_changed
})?;
changed_repos
.iter()
.find(|(work_dir, _)| path.starts_with(work_dir))?;
Some((buffer, path.clone()))
})
.collect::<Vec<_>>();
@ -5989,16 +5989,15 @@ impl Project {
.await?;
let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id());
let (saved_version, fingerprint, mtime) = this
.update(&mut cx, |this, cx| this.save_buffer(buffer, cx))
this.update(&mut cx, |this, cx| this.save_buffer(buffer.clone(), cx))
.await?;
Ok(proto::BufferSaved {
Ok(buffer.read_with(&cx, |buffer, _| proto::BufferSaved {
project_id,
buffer_id,
version: serialize_version(&saved_version),
mtime: Some(mtime.into()),
fingerprint: language::proto::serialize_fingerprint(fingerprint),
})
version: serialize_version(buffer.saved_version()),
mtime: Some(buffer.saved_mtime().into()),
fingerprint: language::proto::serialize_fingerprint(buffer.saved_version_fingerprint()),
}))
}
async fn handle_reload_buffers(

File diff suppressed because it is too large Load Diff

View File

@ -1002,6 +1002,7 @@ impl ProjectPanel {
mtime: entry.mtime,
is_symlink: false,
is_ignored: false,
git_status: entry.git_status,
});
}
if expanded_dir_ids.binary_search(&entry.id).is_err()
@ -1011,6 +1012,9 @@ impl ProjectPanel {
}
entry_iter.advance();
}
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
visible_worktree_entries.sort_by(|entry_a, entry_b| {
let mut components_a = entry_a.path.components().peekable();
let mut components_b = entry_b.path.components().peekable();
@ -1108,14 +1112,8 @@ impl ProjectPanel {
.unwrap_or(&[]);
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
for (entry, repo) in
snapshot.entries_with_repositories(visible_worktree_entries[entry_range].iter())
{
let status = (git_status_setting
&& entry.path.parent().is_some()
&& !entry.is_ignored)
.then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path)))
.flatten();
for entry in visible_worktree_entries[entry_range].iter() {
let status = git_status_setting.then(|| entry.git_status).flatten();
let mut details = EntryDetails {
filename: entry

View File

@ -1005,13 +1005,12 @@ message Entry {
Timestamp mtime = 5;
bool is_symlink = 6;
bool is_ignored = 7;
optional GitStatus git_status = 8;
}
message RepositoryEntry {
uint64 work_directory_id = 1;
optional string branch = 2;
repeated string removed_repo_paths = 3;
repeated StatusEntry updated_statuses = 4;
}
message StatusEntry {

View File

@ -480,6 +480,11 @@ impl<T: Item> SumTree<T> {
} => child_trees.last().unwrap().rightmost_leaf(),
}
}
#[cfg(debug_assertions)]
pub fn _debug_entries(&self) -> Vec<&T> {
self.iter().collect::<Vec<_>>()
}
}
impl<T: Item + PartialEq> PartialEq for SumTree<T> {

View File

@ -57,7 +57,7 @@ pub fn post_inc<T: From<u8> + AddAssign<T> + Copy>(value: &mut T) -> T {
}
/// Extend a sorted vector with a sorted sequence of items, maintaining the vector's sort order and
/// enforcing a maximum length. Sort the items according to the given callback. Before calling this,
/// enforcing a maximum length. This also de-duplicates items. Sort the items according to the given callback. Before calling this,
/// both `vec` and `new_items` should already be sorted according to the `cmp` comparator.
pub fn extend_sorted<T, I, F>(vec: &mut Vec<T>, new_items: I, limit: usize, mut cmp: F)
where

View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@tokens-studio/types": "^0.2.3",
"@types/chroma-js": "^2.4.0",
"@types/node": "^18.14.1",
"ayu": "^8.0.1",
@ -53,6 +54,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@tokens-studio/types": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.2.3.tgz",
"integrity": "sha512-2KN3V0JPf+Zh8aoVMwykJq29Lsi7vYgKGYBQ/zQ+FbDEmrH6T/Vwn8kG7cvbTmW1JAAvgxVxMIivgC9PmFelNA=="
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
@ -271,6 +277,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@tokens-studio/types": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.2.3.tgz",
"integrity": "sha512-2KN3V0JPf+Zh8aoVMwykJq29Lsi7vYgKGYBQ/zQ+FbDEmrH6T/Vwn8kG7cvbTmW1JAAvgxVxMIivgC9PmFelNA=="
},
"@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",

View File

@ -5,11 +5,13 @@
"main": "index.js",
"scripts": {
"build": "ts-node ./src/buildThemes.ts",
"build-licenses": "ts-node ./src/buildLicenses.ts"
"build-licenses": "ts-node ./src/buildLicenses.ts",
"build-tokens": "ts-node ./src/buildTokens.ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"@tokens-studio/types": "^0.2.3",
"@types/chroma-js": "^2.4.0",
"@types/node": "^18.14.1",
"ayu": "^8.0.1",

View File

@ -30,17 +30,19 @@ function generateLicenseFile(themes: ThemeConfig[]) {
checkLicenses(themes)
for (const theme of themes) {
const licenseText = fs.readFileSync(theme.licenseFile).toString()
writeLicense(theme.name, theme.licenseUrl, licenseText)
writeLicense(theme.name, licenseText, theme.licenseUrl)
}
}
function writeLicense(
themeName: string,
licenseUrl: string,
licenseText: String
licenseText: string,
licenseUrl?: string
) {
process.stdout.write(
`## [${themeName}](${licenseUrl})\n\n${licenseText}\n********************************************************************************\n\n`
licenseUrl
? `## [${themeName}](${licenseUrl})\n\n${licenseText}\n********************************************************************************\n\n`
: `## ${themeName}\n\n${licenseText}\n********************************************************************************\n\n`
)
}

View File

@ -2,7 +2,7 @@ import * as fs from "fs"
import { tmpdir } from "os"
import * as path from "path"
import app from "./styleTree/app"
import { ColorScheme, createColorScheme } from "./themes/common/colorScheme"
import { ColorScheme, createColorScheme } from "./theme/colorScheme"
import snakeCase from "./utils/snakeCase"
import { themes } from "./themes"
@ -35,7 +35,9 @@ function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) {
}
}
const colorSchemes: ColorScheme[] = themes.map((theme) => createColorScheme(theme))
const colorSchemes: ColorScheme[] = themes.map((theme) =>
createColorScheme(theme)
)
// Write new themes to theme directory
writeThemes(colorSchemes, `${assetsDirectory}/themes`)

39
styles/src/buildTokens.ts Normal file
View File

@ -0,0 +1,39 @@
import * as fs from "fs"
import * as path from "path"
import { ColorScheme, createColorScheme } from "./common"
import { themes } from "./themes"
import { slugify } from "./utils/slugify"
import { colorSchemeTokens } from "./theme/tokens/colorScheme"
const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens")
function clearTokens(tokensDirectory: string) {
if (!fs.existsSync(tokensDirectory)) {
fs.mkdirSync(tokensDirectory, { recursive: true })
} else {
for (const file of fs.readdirSync(tokensDirectory)) {
if (file.endsWith(".json")) {
fs.unlinkSync(path.join(tokensDirectory, file))
}
}
}
}
function writeTokens(colorSchemes: ColorScheme[], tokensDirectory: string) {
clearTokens(tokensDirectory)
for (const colorScheme of colorSchemes) {
const fileName = slugify(colorScheme.name)
const tokens = colorSchemeTokens(colorScheme)
const tokensJSON = JSON.stringify(tokens, null, 2)
const outPath = path.join(tokensDirectory, `${fileName}.json`)
fs.writeFileSync(outPath, tokensJSON)
console.log(`- ${outPath} created`)
}
}
const colorSchemes: ColorScheme[] = themes.map((theme) =>
createColorScheme(theme)
)
writeTokens(colorSchemes, TOKENS_DIRECTORY)

View File

@ -1,5 +1,5 @@
import chroma from "chroma-js"
export * from "./themes/common"
export * from "./theme"
export { chroma }
export const fontFamilies = {
@ -27,7 +27,7 @@ export type FontWeight =
| "bold"
| "extra_bold"
| "black"
export const fontWeights: { [key: string]: FontWeight } = {
thin: "thin",
extra_light: "extra_light",

View File

@ -19,7 +19,7 @@ import terminal from "./terminal"
import contactList from "./contactList"
import lspLogMenu from "./lspLogMenu"
import incomingCallNotification from "./incomingCallNotification"
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import feedback from "./feedback"
import welcome from "./welcome"
import copilot from "./copilot"

View File

@ -1,85 +1,85 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { text, border, background, foreground } from "./components"
import editor from "./editor"
export default function assistant(colorScheme: ColorScheme) {
const layer = colorScheme.highest;
const layer = colorScheme.highest
return {
container: {
background: editor(colorScheme).background,
padding: { left: 12 }
},
header: {
border: border(layer, "default", { bottom: true, top: true }),
margin: { bottom: 6, top: 6 },
background: editor(colorScheme).background
},
userSender: {
...text(layer, "sans", "default", { size: "sm", weight: "bold" }),
},
assistantSender: {
...text(layer, "sans", "accent", { size: "sm", weight: "bold" }),
},
systemSender: {
...text(layer, "sans", "variant", { size: "sm", weight: "bold" }),
},
sentAt: {
margin: { top: 2, left: 8 },
...text(layer, "sans", "default", { size: "2xs" }),
},
modelInfoContainer: {
margin: { right: 16, top: 4 },
},
model: {
background: background(layer, "on"),
border: border(layer, "on", { overlay: true }),
padding: 4,
cornerRadius: 4,
...text(layer, "sans", "default", { size: "xs" }),
hover: {
background: background(layer, "on", "hovered"),
}
},
remainingTokens: {
background: background(layer, "on"),
border: border(layer, "on", { overlay: true }),
padding: 4,
margin: { left: 4 },
cornerRadius: 4,
...text(layer, "sans", "positive", { size: "xs" }),
},
noRemainingTokens: {
background: background(layer, "on"),
border: border(layer, "on", { overlay: true }),
padding: 4,
margin: { left: 4 },
cornerRadius: 4,
...text(layer, "sans", "negative", { size: "xs" }),
},
errorIcon: {
margin: { left: 8 },
color: foreground(layer, "negative"),
width: 12,
},
apiKeyEditor: {
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,
},
},
apiKeyPrompt: {
padding: 10,
...text(layer, "sans", "default", { size: "xs" }),
}
container: {
background: editor(colorScheme).background,
padding: { left: 12 },
},
header: {
border: border(layer, "default", { bottom: true, top: true }),
margin: { bottom: 6, top: 6 },
background: editor(colorScheme).background,
},
userSender: {
...text(layer, "sans", "default", { size: "sm", weight: "bold" }),
},
assistantSender: {
...text(layer, "sans", "accent", { size: "sm", weight: "bold" }),
},
systemSender: {
...text(layer, "sans", "variant", { size: "sm", weight: "bold" }),
},
sentAt: {
margin: { top: 2, left: 8 },
...text(layer, "sans", "default", { size: "2xs" }),
},
modelInfoContainer: {
margin: { right: 16, top: 4 },
},
model: {
background: background(layer, "on"),
border: border(layer, "on", { overlay: true }),
padding: 4,
cornerRadius: 4,
...text(layer, "sans", "default", { size: "xs" }),
hover: {
background: background(layer, "on", "hovered"),
},
},
remainingTokens: {
background: background(layer, "on"),
border: border(layer, "on", { overlay: true }),
padding: 4,
margin: { left: 4 },
cornerRadius: 4,
...text(layer, "sans", "positive", { size: "xs" }),
},
noRemainingTokens: {
background: background(layer, "on"),
border: border(layer, "on", { overlay: true }),
padding: 4,
margin: { left: 4 },
cornerRadius: 4,
...text(layer, "sans", "negative", { size: "xs" }),
},
errorIcon: {
margin: { left: 8 },
color: foreground(layer, "negative"),
width: 12,
},
apiKeyEditor: {
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,
},
},
apiKeyPrompt: {
padding: 10,
...text(layer, "sans", "default", { size: "xs" }),
},
}
}

View File

@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { withOpacity } from "../utils/color"
import { ColorScheme } from "../theme/colorScheme"
import { withOpacity } from "../theme/color"
import { text, background } from "./components"
export default function commandPalette(colorScheme: ColorScheme) {

View File

@ -1,5 +1,5 @@
import { fontFamilies, fontSizes, FontWeight } from "../common"
import { Layer, Styles, StyleSets, Style } from "../themes/common/colorScheme"
import { Layer, Styles, StyleSets, Style } from "../theme/colorScheme"
function isStyleSet(key: any): key is StyleSets {
return [

View File

@ -1,5 +1,5 @@
import picker from "./picker"
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, foreground, text } from "./components"
export default function contactFinder(colorScheme: ColorScheme): any {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, borderColor, foreground, text } from "./components"
export default function contactsPanel(colorScheme: ColorScheme) {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, foreground, text } from "./components"
const avatarSize = 12

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, text } from "./components"
export default function contactsPopover(colorScheme: ColorScheme) {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, borderColor, text } from "./components"
export default function contextMenu(colorScheme: ColorScheme) {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, foreground, svg, text } from "./components"
export default function copilot(colorScheme: ColorScheme) {

View File

@ -1,9 +1,9 @@
import { withOpacity } from "../utils/color"
import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme"
import { withOpacity } from "../theme/color"
import { ColorScheme, Layer, StyleSets } from "../theme/colorScheme"
import { background, border, borderColor, foreground, text } from "./components"
import hoverPopover from "./hoverPopover"
import { buildSyntax } from "../themes/common/syntax"
import { buildSyntax } from "../theme/syntax"
export default function editor(colorScheme: ColorScheme) {
const { isLight } = colorScheme
@ -186,7 +186,10 @@ export default function editor(colorScheme: ColorScheme) {
},
},
source: {
text: text(colorScheme.middle, "sans", { size: "sm", weight: "bold", }),
text: text(colorScheme.middle, "sans", {
size: "sm",
weight: "bold",
}),
},
message: {
highlightText: text(colorScheme.middle, "sans", {
@ -250,7 +253,7 @@ export default function editor(colorScheme: ColorScheme) {
right: true,
left: true,
bottom: false,
}
},
},
git: {
deleted: isLight
@ -262,7 +265,7 @@ export default function editor(colorScheme: ColorScheme) {
inserted: isLight
? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8)
: withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8),
}
},
},
compositionMark: {
underline: {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, text } from "./components"
export default function feedback(colorScheme: ColorScheme) {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, foreground, text } from "./components"
export default function HoverPopover(colorScheme: ColorScheme) {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, text } from "./components"
export default function incomingCallNotification(

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, text } from "./components"
export default function contactsPanel(colorScheme: ColorScheme) {

View File

@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { withOpacity } from "../utils/color"
import { ColorScheme } from "../theme/colorScheme"
import { withOpacity } from "../theme/color"
import { background, border, text } from "./components"
export default function picker(colorScheme: ColorScheme): any {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, text } from "./components"
export default function projectDiagnostics(colorScheme: ColorScheme) {

View File

@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { withOpacity } from "../utils/color"
import { ColorScheme } from "../theme/colorScheme"
import { withOpacity } from "../theme/color"
import { background, border, foreground, text } from "./components"
export default function projectPanel(colorScheme: ColorScheme) {
@ -24,8 +24,8 @@ export default function projectPanel(colorScheme: ColorScheme) {
: colorScheme.ramps.green(0.5).hex(),
conflict: isLight
? colorScheme.ramps.red(0.6).hex()
: colorScheme.ramps.red(0.5).hex()
}
: colorScheme.ramps.red(0.5).hex(),
},
}
let entry = {
@ -44,7 +44,7 @@ export default function projectPanel(colorScheme: ColorScheme) {
background: background(layer, "active"),
text: text(layer, "mono", "active", { size: "sm" }),
},
status
status,
}
return {
@ -79,7 +79,7 @@ export default function projectPanel(colorScheme: ColorScheme) {
text: text(layer, "mono", "on", { size: "sm" }),
background: withOpacity(background(layer, "on"), 0.9),
border: border(layer),
status
status,
},
ignoredEntry: {
...entry,
@ -88,7 +88,7 @@ export default function projectPanel(colorScheme: ColorScheme) {
active: {
...entry.active,
iconColor: foreground(layer, "variant"),
}
},
},
cutEntry: {
...entry,

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, text } from "./components"
export default function projectSharedNotification(

View File

@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { withOpacity } from "../utils/color"
import { ColorScheme } from "../theme/colorScheme"
import { withOpacity } from "../theme/color"
import { background, border, foreground, text } from "./components"
export default function search(colorScheme: ColorScheme) {
@ -30,7 +30,7 @@ export default function search(colorScheme: ColorScheme) {
...editor,
minWidth: 100,
maxWidth: 250,
};
}
return {
// TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background } from "./components"
export default function sharedScreen(colorScheme: ColorScheme) {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, foreground, text } from "./components"
const headerPadding = 8

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, foreground, text } from "./components"
export default function statusBar(colorScheme: ColorScheme) {

View File

@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { withOpacity } from "../utils/color"
import { ColorScheme } from "../theme/colorScheme"
import { withOpacity } from "../theme/color"
import { text, border, background, foreground } from "./components"
export default function tabBar(colorScheme: ColorScheme) {
@ -96,7 +96,7 @@ export default function tabBar(colorScheme: ColorScheme) {
},
active: {
color: foreground(layer, "accent"),
}
},
},
paneButtonContainer: {
background: tab.background,

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
export default function terminal(colorScheme: ColorScheme) {
/**

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { background, border, text } from "./components"
export default function tooltip(colorScheme: ColorScheme) {

View File

@ -1,4 +1,4 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { ColorScheme } from "../theme/colorScheme"
import { foreground, text } from "./components"
const headerPadding = 8

View File

@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { withOpacity } from "../utils/color"
import { ColorScheme } from "../theme/colorScheme"
import { withOpacity } from "../theme/color"
import {
border,
background,

View File

@ -1,5 +1,5 @@
import { ColorScheme } from "../themes/common/colorScheme"
import { withOpacity } from "../utils/color"
import { ColorScheme } from "../theme/colorScheme"
import { withOpacity } from "../theme/color"
import {
background,
border,
@ -123,7 +123,7 @@ export default function workspace(colorScheme: ColorScheme) {
cursor: "Arrow",
background: isLight
? withOpacity(background(colorScheme.lowest), 0.8)
: withOpacity(background(colorScheme.highest), 0.6)
: withOpacity(background(colorScheme.highest), 0.6),
},
zoomedPaneForeground: {
margin: 16,
@ -143,7 +143,7 @@ export default function workspace(colorScheme: ColorScheme) {
},
right: {
border: border(layer, { left: true }),
}
},
},
paneDivider: {
color: borderColor(layer),

View File

@ -5,7 +5,7 @@ import {
ThemeConfig,
ThemeAppearance,
ThemeConfigInputColors,
} from "../../themeConfig"
} from "./themeConfig"
import { getRamps } from "./ramps"
export interface ColorScheme {

View File

@ -1,4 +1,4 @@
export * from "./colorScheme"
export * from "./ramps"
export * from "./syntax"
export * from "../../themeConfig"
export * from "./themeConfig"

View File

@ -3,7 +3,7 @@ import { RampSet } from "./colorScheme"
import {
ThemeConfigInputColors,
ThemeConfigInputColorsKeys,
} from "../../themeConfig"
} from "./themeConfig"
export function colorRamp(color: Color): Scale {
let endColor = color.desaturate(1).brighten(5)

View File

@ -1,5 +1,5 @@
import deepmerge from "deepmerge"
import { FontWeight, fontWeights } from "../../common"
import { FontWeight, fontWeights } from "../common"
import { ColorScheme } from "./colorScheme"
import chroma from "chroma-js"

View File

@ -1,5 +1,5 @@
import { Scale, Color } from "chroma-js"
import { Syntax } from "./themes/common/syntax"
import { Syntax } from "./syntax"
interface ThemeMeta {
/** The name of the theme */
@ -23,6 +23,11 @@ interface ThemeMeta {
themeUrl?: string
}
export type ThemeFamilyMeta = Pick<
ThemeMeta,
"name" | "author" | "licenseType" | "licenseUrl"
>
export interface ThemeConfigInputColors {
neutral: Scale<Color>
red: Scale<Color>

View File

@ -0,0 +1,12 @@
import { ColorScheme } from "../colorScheme"
import { PlayerTokens, players } from "./players"
interface ColorSchemeTokens {
players: PlayerTokens
}
export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens {
return {
players: players(colorScheme),
}
}

View File

@ -0,0 +1,28 @@
import { SingleColorToken } from "@tokens-studio/types"
import { ColorScheme, Players } from "../../common"
import { colorToken } from "./token"
export type PlayerToken = Record<"selection" | "cursor", SingleColorToken>
export type PlayerTokens = Record<keyof Players, PlayerToken>
function buildPlayerToken(colorScheme: ColorScheme, index: number): PlayerToken {
const playerNumber = index.toString() as keyof Players
return {
selection: colorToken(`player${index}Selection`, colorScheme.players[playerNumber].selection),
cursor: colorToken(`player${index}Cursor`, colorScheme.players[playerNumber].cursor),
}
}
export const players = (colorScheme: ColorScheme): PlayerTokens => ({
"0": buildPlayerToken(colorScheme, 0),
"1": buildPlayerToken(colorScheme, 1),
"2": buildPlayerToken(colorScheme, 2),
"3": buildPlayerToken(colorScheme, 3),
"4": buildPlayerToken(colorScheme, 4),
"5": buildPlayerToken(colorScheme, 5),
"6": buildPlayerToken(colorScheme, 6),
"7": buildPlayerToken(colorScheme, 7)
})

View File

@ -0,0 +1,14 @@
import { SingleColorToken, TokenTypes } from "@tokens-studio/types"
export function colorToken(name: string, value: string, description?: string): SingleColorToken {
const token: SingleColorToken = {
name,
type: TokenTypes.COLOR,
value,
description,
}
if (!token.value || token.value === '') throw new Error("Color token must have a value")
return token
}

View File

@ -1,4 +1,4 @@
import { ThemeLicenseType, ThemeConfig, ThemeSyntax } from "../../common"
import { ThemeLicenseType, ThemeSyntax, ThemeFamilyMeta } from "../../common"
export interface Variant {
colors: {
@ -21,7 +21,7 @@ export interface Variant {
}
}
export const meta: Partial<ThemeConfig> = {
export const meta: ThemeFamilyMeta = {
name: "Atelier",
author: "Bram de Haan (http://atelierbramdehaan.nl)",
licenseType: ThemeLicenseType.MIT,

View File

@ -3,8 +3,8 @@ import {
chroma,
colorRamp,
ThemeLicenseType,
ThemeConfig,
ThemeSyntax,
ThemeFamilyMeta,
} from "../../common"
export const ayu = {
@ -77,7 +77,7 @@ export const buildSyntax = (t: typeof dark): ThemeSyntax => {
}
}
export const meta: Partial<ThemeConfig> = {
export const meta: ThemeFamilyMeta = {
name: "Ayu",
author: "dempfi",
licenseType: ThemeLicenseType.MIT,

View File

@ -5,9 +5,10 @@ import {
ThemeLicenseType,
ThemeConfig,
ThemeSyntax,
ThemeFamilyMeta,
} from "../../common"
const meta: Partial<ThemeConfig> = {
const meta: ThemeFamilyMeta = {
name: "Gruvbox",
author: "morhetz <morhetz@gmail.com>",
licenseType: ThemeLicenseType.MIT,

View File

@ -1,4 +1,4 @@
import { ThemeConfig } from "./common"
import { ThemeConfig } from "../theme"
import { darkDefault as gruvboxDark } from "./gruvbox/gruvbox-dark"
import { darkHard as gruvboxDarkHard } from "./gruvbox/gruvbox-dark-hard"
import { darkSoft as gruvboxDarkSoft } from "./gruvbox/gruvbox-dark-soft"

View File

@ -0,0 +1 @@
export function slugify(t: string): string { return t.toString().toLowerCase().replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-\-+/g, '-').replace(/^-+/, '').replace(/-+$/, '') }

View File

@ -6,7 +6,21 @@
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true
"sourceMap": true,
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"skipLibCheck": true
},
"exclude": ["node_modules"]
}