Compare commits
11 Commits
remove-d2d
...
more-retri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2ce038352 | ||
|
|
47af878ebb | ||
|
|
5488398986 | ||
|
|
b1a7993544 | ||
|
|
b90fd4287f | ||
|
|
e1e2775b80 | ||
|
|
ed104ec5e0 | ||
|
|
67a491df50 | ||
|
|
f003036aec | ||
|
|
fbc784d323 | ||
|
|
296bb66b65 |
8
.github/actions/build_docs/action.yml
vendored
8
.github/actions/build_docs/action.yml
vendored
@@ -19,7 +19,7 @@ runs:
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: ./script/linux
|
||||
|
||||
- name: Check for broken links
|
||||
- name: Check for broken links (in MD)
|
||||
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
|
||||
with:
|
||||
args: --no-progress --exclude '^http' './docs/src/**/*'
|
||||
@@ -30,3 +30,9 @@ runs:
|
||||
run: |
|
||||
mkdir -p target/deploy
|
||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||
|
||||
- name: Check for broken links (in HTML)
|
||||
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
|
||||
with:
|
||||
args: --no-progress --exclude '^http' 'target/deploy/docs/'
|
||||
fail: true
|
||||
|
||||
174
Cargo.lock
generated
174
Cargo.lock
generated
@@ -3049,7 +3049,11 @@ dependencies = [
|
||||
name = "cloud_api_types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"cloud_llm_client",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
@@ -4291,41 +4295,6 @@ dependencies = [
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.3"
|
||||
@@ -4541,37 +4510,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.19"
|
||||
@@ -5950,7 +5888,7 @@ dependencies = [
|
||||
"ignore",
|
||||
"libc",
|
||||
"log",
|
||||
"notify",
|
||||
"notify 8.0.0",
|
||||
"objc",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
@@ -7507,18 +7445,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "6.3.2"
|
||||
version = "5.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098"
|
||||
checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b"
|
||||
dependencies = [
|
||||
"derive_builder",
|
||||
"log",
|
||||
"num-order",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8189,12 +8125,6 @@ version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.0.3"
|
||||
@@ -8413,6 +8343,17 @@ dependencies = [
|
||||
"zeta",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.11.0"
|
||||
@@ -8566,7 +8507,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"tempfile",
|
||||
@@ -10006,9 +9947,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mdbook"
|
||||
version = "0.4.48"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b6fbb4ac2d9fd7aa987c3510309ea3c80004a968d063c42f0d34fea070817c1"
|
||||
checksum = "b45a38e19bd200220ef07c892b0157ad3d2365e5b5a267ca01ad12182491eea5"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -10018,12 +9959,11 @@ dependencies = [
|
||||
"elasticlunr-rs",
|
||||
"env_logger 0.11.8",
|
||||
"futures-util",
|
||||
"handlebars 6.3.2",
|
||||
"hex",
|
||||
"handlebars 5.1.2",
|
||||
"ignore",
|
||||
"log",
|
||||
"memchr",
|
||||
"notify",
|
||||
"notify 6.1.1",
|
||||
"notify-debouncer-mini",
|
||||
"once_cell",
|
||||
"opener",
|
||||
@@ -10032,7 +9972,6 @@ dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"shlex",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
@@ -10175,6 +10114,18 @@ version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.3"
|
||||
@@ -10521,6 +10472,25 @@ dependencies = [
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys 4.1.0",
|
||||
"inotify 0.9.6",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio 0.8.11",
|
||||
"walkdir",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "8.0.0"
|
||||
@@ -10529,11 +10499,11 @@ dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"filetime",
|
||||
"fsevent-sys 4.1.0",
|
||||
"inotify",
|
||||
"inotify 0.11.0",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -10541,14 +10511,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "notify-debouncer-mini"
|
||||
version = "0.6.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a689eb4262184d9a1727f9087cd03883ea716682ab03ed24efec57d7716dccb8"
|
||||
checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"log",
|
||||
"notify",
|
||||
"notify-types",
|
||||
"tempfile",
|
||||
"notify 6.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10688,21 +10657,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-modular"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f"
|
||||
|
||||
[[package]]
|
||||
name = "num-order"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
|
||||
dependencies = [
|
||||
"num-modular",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
@@ -14759,6 +14713,7 @@ dependencies = [
|
||||
name = "settings_profile_selector"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client",
|
||||
"editor",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
@@ -14768,6 +14723,7 @@ dependencies = [
|
||||
"project",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
@@ -16616,7 +16572,7 @@ dependencies = [
|
||||
"backtrace",
|
||||
"bytes 1.10.1",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
@@ -19793,7 +19749,7 @@ dependencies = [
|
||||
"md-5",
|
||||
"memchr",
|
||||
"miniz_oxide",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"naga",
|
||||
"nix 0.29.0",
|
||||
"nom",
|
||||
|
||||
@@ -1663,47 +1663,68 @@ async fn retry_on_rate_limit<R>(mut request: impl AsyncFnMut() -> Result<R>) ->
|
||||
attempt += 1;
|
||||
match request().await {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(err) => match err.downcast::<LanguageModelCompletionError>() {
|
||||
Ok(err) => match &err {
|
||||
LanguageModelCompletionError::RateLimitExceeded { retry_after, .. }
|
||||
| LanguageModelCompletionError::ServerOverloaded { retry_after, .. } => {
|
||||
let retry_after = retry_after.unwrap_or(Duration::from_secs(5));
|
||||
// Wait for the duration supplied, with some jitter to avoid all requests being made at the same time.
|
||||
let jitter = retry_after.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
eprintln!(
|
||||
"Attempt #{attempt}: {err}. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
);
|
||||
Timer::after(retry_after + jitter).await;
|
||||
continue;
|
||||
}
|
||||
LanguageModelCompletionError::UpstreamProviderError {
|
||||
status,
|
||||
retry_after,
|
||||
..
|
||||
} => {
|
||||
// Only retry for specific status codes
|
||||
let should_retry = matches!(
|
||||
*status,
|
||||
StatusCode::TOO_MANY_REQUESTS | StatusCode::SERVICE_UNAVAILABLE
|
||||
) || status.as_u16() == 529;
|
||||
Err(err) => {
|
||||
if attempt > 20 {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if !should_retry {
|
||||
return Err(err.into());
|
||||
match err.downcast::<LanguageModelCompletionError>() {
|
||||
Ok(err) => match &err {
|
||||
LanguageModelCompletionError::RateLimitExceeded { retry_after, .. }
|
||||
| LanguageModelCompletionError::ServerOverloaded { retry_after, .. } => {
|
||||
let retry_after = retry_after.unwrap_or(Duration::from_secs(5));
|
||||
// Wait for the duration supplied, with some jitter to avoid all requests being made at the same time.
|
||||
let jitter =
|
||||
retry_after.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
eprintln!(
|
||||
"Attempt #{attempt}: {err}. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
);
|
||||
Timer::after(retry_after + jitter).await;
|
||||
continue;
|
||||
}
|
||||
LanguageModelCompletionError::UpstreamProviderError {
|
||||
status,
|
||||
retry_after,
|
||||
..
|
||||
} => {
|
||||
// Only retry for specific status codes
|
||||
let should_retry = matches!(
|
||||
*status,
|
||||
StatusCode::TOO_MANY_REQUESTS | StatusCode::SERVICE_UNAVAILABLE
|
||||
) || status.as_u16() == 529;
|
||||
|
||||
// Use server-provided retry_after if available, otherwise use default
|
||||
let retry_after = retry_after.unwrap_or(Duration::from_secs(5));
|
||||
let jitter = retry_after.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
eprintln!(
|
||||
"Attempt #{attempt}: {err}. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
);
|
||||
Timer::after(retry_after + jitter).await;
|
||||
continue;
|
||||
}
|
||||
_ => return Err(err.into()),
|
||||
},
|
||||
Err(err) => return Err(err),
|
||||
},
|
||||
if !should_retry {
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
// Use server-provided retry_after if available, otherwise use default
|
||||
let retry_after = retry_after.unwrap_or(Duration::from_secs(5));
|
||||
let jitter =
|
||||
retry_after.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
eprintln!(
|
||||
"Attempt #{attempt}: {err}. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
);
|
||||
Timer::after(retry_after + jitter).await;
|
||||
continue;
|
||||
}
|
||||
LanguageModelCompletionError::ApiInternalServerError { .. }
|
||||
| LanguageModelCompletionError::ApiReadResponseError { .. }
|
||||
| LanguageModelCompletionError::DeserializeResponse { .. }
|
||||
| LanguageModelCompletionError::HttpSend { .. } => {
|
||||
let retry_after = Duration::from_secs(attempt);
|
||||
let jitter =
|
||||
retry_after.mul_f64(rand::thread_rng().gen_range(0.0..1.0));
|
||||
eprintln!(
|
||||
"Attempt #{attempt}: {err}. Retry after {retry_after:?} + jitter of {jitter:?}"
|
||||
);
|
||||
Timer::after(retry_after + jitter).await;
|
||||
continue;
|
||||
}
|
||||
_ => return Err(err.into()),
|
||||
},
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1491,6 +1491,7 @@ impl Client {
|
||||
|
||||
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncApp) {
|
||||
self.state.write().credentials = None;
|
||||
self.cloud_client.clear_credentials();
|
||||
self.disconnect(cx);
|
||||
|
||||
if self.has_credentials(cx).await {
|
||||
|
||||
@@ -7,35 +7,63 @@ use gpui::{Context, Task};
|
||||
use util::{ResultExt as _, maybe};
|
||||
|
||||
pub struct CloudUserStore {
|
||||
authenticated_user: Option<AuthenticatedUser>,
|
||||
_fetch_authenticated_user_task: Task<()>,
|
||||
authenticated_user: Option<Arc<AuthenticatedUser>>,
|
||||
_maintain_authenticated_user_task: Task<()>,
|
||||
}
|
||||
|
||||
impl CloudUserStore {
|
||||
pub fn new(cloud_client: Arc<CloudApiClient>, cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
authenticated_user: None,
|
||||
_fetch_authenticated_user_task: cx.spawn(async move |this, cx| {
|
||||
_maintain_authenticated_user_task: cx.spawn(async move |this, cx| {
|
||||
maybe!(async move {
|
||||
loop {
|
||||
let Some(this) = this.upgrade() else {
|
||||
return anyhow::Ok(());
|
||||
};
|
||||
|
||||
if cloud_client.has_credentials() {
|
||||
break;
|
||||
let already_fetched_authenticated_user = this
|
||||
.read_with(cx, |this, _cx| this.authenticated_user().is_some())
|
||||
.unwrap_or(false);
|
||||
|
||||
if already_fetched_authenticated_user {
|
||||
// We already fetched the authenticated user; nothing to do.
|
||||
} else {
|
||||
let authenticated_user_result = cloud_client
|
||||
.get_authenticated_user()
|
||||
.await
|
||||
.context("failed to fetch authenticated user");
|
||||
if let Some(response) = authenticated_user_result.log_err() {
|
||||
this.update(cx, |this, _cx| {
|
||||
this.authenticated_user = Some(Arc::new(response.user));
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.update(cx, |this, _cx| {
|
||||
this.authenticated_user = None;
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(100))
|
||||
.await;
|
||||
}
|
||||
|
||||
let response = cloud_client.get_authenticated_user().await?;
|
||||
this.update(cx, |this, _cx| {
|
||||
this.authenticated_user = Some(response.user);
|
||||
})
|
||||
})
|
||||
.await
|
||||
.context("failed to fetch authenticated user")
|
||||
.log_err();
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_authenticated(&self) -> bool {
|
||||
self.authenticated_user.is_some()
|
||||
}
|
||||
|
||||
pub fn authenticated_user(&self) -> Option<Arc<AuthenticatedUser>> {
|
||||
self.authenticated_user.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ impl CloudApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clear_credentials(&self) {
|
||||
*self.credentials.write() = None;
|
||||
}
|
||||
|
||||
fn authorization_header(&self) -> Result<String> {
|
||||
let guard = self.credentials.read();
|
||||
let credentials = guard
|
||||
|
||||
@@ -12,5 +12,11 @@ workspace = true
|
||||
path = "src/cloud_api_types.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono.workspace = true
|
||||
cloud_llm_client.workspace = true
|
||||
serde.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
mod timestamp;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use crate::timestamp::Timestamp;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct GetAuthenticatedUserResponse {
|
||||
pub user: AuthenticatedUser,
|
||||
pub feature_flags: Vec<String>,
|
||||
pub plan: PlanInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AuthenticatedUser {
|
||||
pub id: i32,
|
||||
pub metrics_id: String,
|
||||
pub avatar_url: String,
|
||||
pub github_login: String,
|
||||
pub name: Option<String>,
|
||||
pub is_staff: bool,
|
||||
pub accepted_tos_at: Option<Timestamp>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PlanInfo {
|
||||
pub plan: cloud_llm_client::Plan,
|
||||
pub subscription_period: Option<SubscriptionPeriod>,
|
||||
pub usage: cloud_llm_client::CurrentUsage,
|
||||
pub trial_started_at: Option<Timestamp>,
|
||||
pub is_usage_based_billing_enabled: bool,
|
||||
pub is_account_too_young: bool,
|
||||
pub has_overdue_invoices: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct SubscriptionPeriod {
|
||||
pub started_at: Timestamp,
|
||||
pub ended_at: Timestamp,
|
||||
}
|
||||
|
||||
166
crates/cloud_api_types/src/timestamp.rs
Normal file
166
crates/cloud_api_types/src/timestamp.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// A timestamp with a serialized representation in RFC 3339 format.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub struct Timestamp(pub DateTime<Utc>);
|
||||
|
||||
impl Timestamp {
|
||||
pub fn new(datetime: DateTime<Utc>) -> Self {
|
||||
Self(datetime)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DateTime<Utc>> for Timestamp {
|
||||
fn from(value: DateTime<Utc>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NaiveDateTime> for Timestamp {
|
||||
fn from(value: NaiveDateTime) -> Self {
|
||||
Self(value.and_utc())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Timestamp {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let rfc3339_string = self.0.to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||
serializer.serialize_str(&rfc3339_string)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Timestamp {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = String::deserialize(deserializer)?;
|
||||
let datetime = DateTime::parse_from_rfc3339(&value)
|
||||
.map_err(serde::de::Error::custom)?
|
||||
.to_utc();
|
||||
Ok(Self(datetime))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::NaiveDate;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_serialization() {
|
||||
let datetime = DateTime::parse_from_rfc3339("2023-12-25T14:30:45.123Z")
|
||||
.unwrap()
|
||||
.to_utc();
|
||||
let timestamp = Timestamp::new(datetime);
|
||||
|
||||
let json = serde_json::to_string(×tamp).unwrap();
|
||||
assert_eq!(json, "\"2023-12-25T14:30:45.123Z\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_deserialization() {
|
||||
let json = "\"2023-12-25T14:30:45.123Z\"";
|
||||
let timestamp: Timestamp = serde_json::from_str(json).unwrap();
|
||||
|
||||
let expected = DateTime::parse_from_rfc3339("2023-12-25T14:30:45.123Z")
|
||||
.unwrap()
|
||||
.to_utc();
|
||||
|
||||
assert_eq!(timestamp.0, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_roundtrip() {
|
||||
let original = DateTime::parse_from_rfc3339("2023-12-25T14:30:45.123Z")
|
||||
.unwrap()
|
||||
.to_utc();
|
||||
|
||||
let timestamp = Timestamp::new(original);
|
||||
let json = serde_json::to_string(×tamp).unwrap();
|
||||
let deserialized: Timestamp = serde_json::from_str(&json).unwrap();
|
||||
|
||||
assert_eq!(deserialized.0, original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_from_datetime_utc() {
|
||||
let datetime = DateTime::parse_from_rfc3339("2023-12-25T14:30:45.123Z")
|
||||
.unwrap()
|
||||
.to_utc();
|
||||
|
||||
let timestamp = Timestamp::from(datetime);
|
||||
assert_eq!(timestamp.0, datetime);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_from_naive_datetime() {
|
||||
let naive_dt = NaiveDate::from_ymd_opt(2023, 12, 25)
|
||||
.unwrap()
|
||||
.and_hms_milli_opt(14, 30, 45, 123)
|
||||
.unwrap();
|
||||
|
||||
let timestamp = Timestamp::from(naive_dt);
|
||||
let expected = naive_dt.and_utc();
|
||||
|
||||
assert_eq!(timestamp.0, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_serialization_with_microseconds() {
|
||||
// Test that microseconds are truncated to milliseconds
|
||||
let datetime = NaiveDate::from_ymd_opt(2023, 12, 25)
|
||||
.unwrap()
|
||||
.and_hms_micro_opt(14, 30, 45, 123456)
|
||||
.unwrap()
|
||||
.and_utc();
|
||||
|
||||
let timestamp = Timestamp::new(datetime);
|
||||
let json = serde_json::to_string(×tamp).unwrap();
|
||||
|
||||
// Should be truncated to milliseconds
|
||||
assert_eq!(json, "\"2023-12-25T14:30:45.123Z\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_deserialization_without_milliseconds() {
|
||||
let json = "\"2023-12-25T14:30:45Z\"";
|
||||
let timestamp: Timestamp = serde_json::from_str(json).unwrap();
|
||||
|
||||
let expected = NaiveDate::from_ymd_opt(2023, 12, 25)
|
||||
.unwrap()
|
||||
.and_hms_opt(14, 30, 45)
|
||||
.unwrap()
|
||||
.and_utc();
|
||||
|
||||
assert_eq!(timestamp.0, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_deserialization_with_timezone() {
|
||||
let json = "\"2023-12-25T14:30:45.123+05:30\"";
|
||||
let timestamp: Timestamp = serde_json::from_str(json).unwrap();
|
||||
|
||||
// Should be converted to UTC
|
||||
let expected = NaiveDate::from_ymd_opt(2023, 12, 25)
|
||||
.unwrap()
|
||||
.and_hms_milli_opt(9, 0, 45, 123) // 14:30:45 + 5:30 = 20:00:45, but we want UTC so subtract 5:30
|
||||
.unwrap()
|
||||
.and_utc();
|
||||
|
||||
assert_eq!(timestamp.0, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_deserialization_with_invalid_format() {
|
||||
let json = "\"invalid-date\"";
|
||||
let result: Result<Timestamp, _> = serde_json::from_str(json);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@@ -308,13 +308,13 @@ pub struct GetSubscriptionResponse {
|
||||
pub usage: Option<CurrentUsage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CurrentUsage {
|
||||
pub model_requests: UsageData,
|
||||
pub edit_predictions: UsageData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct UsageData {
|
||||
pub used: u32,
|
||||
pub limit: UsageLimit,
|
||||
|
||||
@@ -9,7 +9,9 @@ license = "GPL-3.0-or-later"
|
||||
anyhow.workspace = true
|
||||
command_palette.workspace = true
|
||||
gpui.workspace = true
|
||||
mdbook = "0.4.40"
|
||||
# We are specifically pinning this version of mdbook, as later versions introduce issues with double-nested subdirectories.
|
||||
# Ask @maxdeviant about this before bumping.
|
||||
mdbook = "= 0.4.40"
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -310,18 +310,6 @@ mod windows {
|
||||
&rust_binding_path,
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||
.join("src/platform/windows/color_text_raster.hlsl");
|
||||
compile_shader_for_module(
|
||||
"emoji_rasterization",
|
||||
&out_dir,
|
||||
&fxc_path,
|
||||
shader_path.to_str().unwrap(),
|
||||
&rust_binding_path,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
|
||||
|
||||
@@ -198,7 +198,7 @@ impl RenderOnce for CharacterGrid {
|
||||
"χ", "ψ", "∂", "а", "в", "Ж", "ж", "З", "з", "К", "к", "л", "м", "Н", "н", "Р", "р",
|
||||
"У", "у", "ф", "ч", "ь", "ы", "Э", "э", "Я", "я", "ij", "öẋ", ".,", "⣝⣑", "~", "*",
|
||||
"_", "^", "`", "'", "(", "{", "«", "#", "&", "@", "$", "¢", "%", "|", "?", "¶", "µ",
|
||||
"❮", "<=", "!=", "==", "--", "++", "=>", "->", "🏀", "🎊", "😍", "❤️", "👍", "👎",
|
||||
"❮", "<=", "!=", "==", "--", "++", "=>", "->",
|
||||
];
|
||||
|
||||
let columns = 11;
|
||||
|
||||
@@ -35,7 +35,6 @@ pub(crate) fn swap_rgba_pa_to_bgra(color: &mut [u8]) {
|
||||
|
||||
/// An RGBA color
|
||||
#[derive(PartialEq, Clone, Copy, Default)]
|
||||
#[repr(C)]
|
||||
pub struct Rgba {
|
||||
/// The red component of the color, in the range 0.0 to 1.0
|
||||
pub r: f32,
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
struct RasterVertexOutput {
|
||||
float4 position : SV_Position;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
};
|
||||
|
||||
RasterVertexOutput emoji_rasterization_vertex(uint vertexID : SV_VERTEXID)
|
||||
{
|
||||
RasterVertexOutput output;
|
||||
output.texcoord = float2((vertexID << 1) & 2, vertexID & 2);
|
||||
output.position = float4(output.texcoord * 2.0f - 1.0f, 0.0f, 1.0f);
|
||||
output.position.y = -output.position.y;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
struct PixelInput {
|
||||
float4 position: SV_Position;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct Bounds {
|
||||
int2 origin;
|
||||
int2 size;
|
||||
};
|
||||
|
||||
Texture2D<float4> t_layer : register(t0);
|
||||
SamplerState s_layer : register(s0);
|
||||
|
||||
cbuffer GlyphLayerTextureParams : register(b0) {
|
||||
Bounds bounds;
|
||||
float4 run_color;
|
||||
};
|
||||
|
||||
float4 emoji_rasterization_fragment(PixelInput input): SV_Target {
|
||||
float3 sampled = t_layer.Sample(s_layer, input.texcoord.xy).rgb;
|
||||
float alpha = (sampled.r + sampled.g + sampled.b) / 3;
|
||||
|
||||
return float4(run_color.rgb, alpha);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ use windows::Win32::Graphics::{
|
||||
D3D11_USAGE_DEFAULT, ID3D11Device, ID3D11DeviceContext, ID3D11ShaderResourceView,
|
||||
ID3D11Texture2D,
|
||||
},
|
||||
Dxgi::Common::*,
|
||||
Dxgi::Common::{DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -167,7 +167,7 @@ impl DirectXAtlasState {
|
||||
let bytes_per_pixel;
|
||||
match kind {
|
||||
AtlasTextureKind::Monochrome => {
|
||||
pixel_format = DXGI_FORMAT_R8_UNORM;
|
||||
pixel_format = DXGI_FORMAT_A8_UNORM;
|
||||
bind_flag = D3D11_BIND_SHADER_RESOURCE;
|
||||
bytes_per_pixel = 1;
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ pub(crate) struct DirectXRenderer {
|
||||
pub(crate) struct DirectXDevices {
|
||||
adapter: IDXGIAdapter1,
|
||||
dxgi_factory: IDXGIFactory6,
|
||||
pub(crate) device: ID3D11Device,
|
||||
pub(crate) device_context: ID3D11DeviceContext,
|
||||
device: ID3D11Device,
|
||||
device_context: ID3D11DeviceContext,
|
||||
dxgi_device: Option<IDXGIDevice>,
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ impl DirectXRenderer {
|
||||
self.resources.viewport[0].Width,
|
||||
self.resources.viewport[0].Height,
|
||||
],
|
||||
_pad: 0,
|
||||
..Default::default()
|
||||
}],
|
||||
)?;
|
||||
unsafe {
|
||||
@@ -1423,7 +1423,7 @@ fn report_live_objects(device: &ID3D11Device) -> Result<()> {
|
||||
|
||||
const BUFFER_COUNT: usize = 3;
|
||||
|
||||
pub(crate) mod shader_resources {
|
||||
mod shader_resources {
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -1436,7 +1436,7 @@ pub(crate) mod shader_resources {
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum ShaderModule {
|
||||
pub(super) enum ShaderModule {
|
||||
Quad,
|
||||
Shadow,
|
||||
Underline,
|
||||
@@ -1444,16 +1444,15 @@ pub(crate) mod shader_resources {
|
||||
PathSprite,
|
||||
MonochromeSprite,
|
||||
PolychromeSprite,
|
||||
EmojiRasterization,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum ShaderTarget {
|
||||
pub(super) enum ShaderTarget {
|
||||
Vertex,
|
||||
Fragment,
|
||||
}
|
||||
|
||||
pub(crate) struct RawShaderBytes<'t> {
|
||||
pub(super) struct RawShaderBytes<'t> {
|
||||
inner: &'t [u8],
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -1461,7 +1460,7 @@ pub(crate) mod shader_resources {
|
||||
}
|
||||
|
||||
impl<'t> RawShaderBytes<'t> {
|
||||
pub(crate) fn new(module: ShaderModule, target: ShaderTarget) -> Result<Self> {
|
||||
pub(super) fn new(module: ShaderModule, target: ShaderTarget) -> Result<Self> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
Ok(Self::from_bytes(module, target))
|
||||
@@ -1479,7 +1478,7 @@ pub(crate) mod shader_resources {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_bytes(&'t self) -> &'t [u8] {
|
||||
pub(super) fn as_bytes(&'t self) -> &'t [u8] {
|
||||
self.inner
|
||||
}
|
||||
|
||||
@@ -1514,10 +1513,6 @@ pub(crate) mod shader_resources {
|
||||
ShaderTarget::Vertex => POLYCHROME_SPRITE_VERTEX_BYTES,
|
||||
ShaderTarget::Fragment => POLYCHROME_SPRITE_FRAGMENT_BYTES,
|
||||
},
|
||||
ShaderModule::EmojiRasterization => match target {
|
||||
ShaderTarget::Vertex => EMOJI_RASTERIZATION_VERTEX_BYTES,
|
||||
ShaderTarget::Fragment => EMOJI_RASTERIZATION_FRAGMENT_BYTES,
|
||||
},
|
||||
};
|
||||
Self { inner: bytes }
|
||||
}
|
||||
@@ -1526,12 +1521,6 @@ pub(crate) mod shader_resources {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(super) fn build_shader_blob(entry: ShaderModule, target: ShaderTarget) -> Result<ID3DBlob> {
|
||||
unsafe {
|
||||
let shader_name = if matches!(entry, ShaderModule::EmojiRasterization) {
|
||||
"color_text_raster.hlsl"
|
||||
} else {
|
||||
"shaders.hlsl"
|
||||
};
|
||||
|
||||
let entry = format!(
|
||||
"{}_{}\0",
|
||||
entry.as_str(),
|
||||
@@ -1548,7 +1537,7 @@ pub(crate) mod shader_resources {
|
||||
let mut compile_blob = None;
|
||||
let mut error_blob = None;
|
||||
let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join(&format!("src/platform/windows/{}", shader_name))
|
||||
.join("src/platform/windows/shaders.hlsl")
|
||||
.canonicalize()?;
|
||||
|
||||
let entry_point = PCSTR::from_raw(entry.as_ptr());
|
||||
@@ -1594,7 +1583,6 @@ pub(crate) mod shader_resources {
|
||||
ShaderModule::PathSprite => "path_sprite",
|
||||
ShaderModule::MonochromeSprite => "monochrome_sprite",
|
||||
ShaderModule::PolychromeSprite => "polychrome_sprite",
|
||||
ShaderModule::EmojiRasterization => "emoji_rasterization",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ pub(crate) struct WindowsPlatform {
|
||||
drop_target_helper: IDropTargetHelper,
|
||||
validation_number: usize,
|
||||
main_thread_id_win32: u32,
|
||||
disable_direct_composition: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct WindowsPlatformState {
|
||||
@@ -94,18 +93,14 @@ impl WindowsPlatform {
|
||||
main_thread_id_win32,
|
||||
validation_number,
|
||||
));
|
||||
let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION)
|
||||
.is_ok_and(|value| value == "true" || value == "1");
|
||||
let background_executor = BackgroundExecutor::new(dispatcher.clone());
|
||||
let foreground_executor = ForegroundExecutor::new(dispatcher);
|
||||
let directx_devices = DirectXDevices::new(disable_direct_composition)
|
||||
.context("Unable to init directx devices.")?;
|
||||
let bitmap_factory = ManuallyDrop::new(unsafe {
|
||||
CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
|
||||
.context("Error creating bitmap factory.")?
|
||||
});
|
||||
let text_system = Arc::new(
|
||||
DirectWriteTextSystem::new(&directx_devices, &bitmap_factory)
|
||||
DirectWriteTextSystem::new(&bitmap_factory)
|
||||
.context("Error creating DirectWriteTextSystem")?,
|
||||
);
|
||||
let drop_target_helper: IDropTargetHelper = unsafe {
|
||||
@@ -125,7 +120,6 @@ impl WindowsPlatform {
|
||||
background_executor,
|
||||
foreground_executor,
|
||||
text_system,
|
||||
disable_direct_composition,
|
||||
windows_version,
|
||||
bitmap_factory,
|
||||
drop_target_helper,
|
||||
@@ -190,7 +184,6 @@ impl WindowsPlatform {
|
||||
validation_number: self.validation_number,
|
||||
main_receiver: self.main_receiver.clone(),
|
||||
main_thread_id_win32: self.main_thread_id_win32,
|
||||
disable_direct_composition: self.disable_direct_composition,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,7 +715,6 @@ pub(crate) struct WindowCreationInfo {
|
||||
pub(crate) validation_number: usize,
|
||||
pub(crate) main_receiver: flume::Receiver<Runnable>,
|
||||
pub(crate) main_thread_id_win32: u32,
|
||||
pub(crate) disable_direct_composition: bool,
|
||||
}
|
||||
|
||||
fn open_target(target: &str) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cbuffer GlobalParams: register(b0) {
|
||||
float2 global_viewport_size;
|
||||
uint2 _pad;
|
||||
uint2 _global_pad;
|
||||
};
|
||||
|
||||
Texture2D<float4> t_sprite: register(t0);
|
||||
@@ -1069,7 +1069,6 @@ struct MonochromeSpriteFragmentInput {
|
||||
float4 position: SV_Position;
|
||||
float2 tile_position: POSITION;
|
||||
nointerpolation float4 color: COLOR;
|
||||
float4 clip_distance: SV_ClipDistance;
|
||||
};
|
||||
|
||||
StructuredBuffer<MonochromeSprite> mono_sprites: register(t1);
|
||||
@@ -1092,8 +1091,10 @@ MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexI
|
||||
}
|
||||
|
||||
float4 monochrome_sprite_fragment(MonochromeSpriteFragmentInput input): SV_Target {
|
||||
float sample = t_sprite.Sample(s_sprite, input.tile_position).r;
|
||||
return float4(input.color.rgb, input.color.a * sample);
|
||||
float4 sample = t_sprite.Sample(s_sprite, input.tile_position);
|
||||
float4 color = input.color;
|
||||
color.a *= sample.a;
|
||||
return color;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -360,7 +360,6 @@ impl WindowsWindow {
|
||||
validation_number,
|
||||
main_receiver,
|
||||
main_thread_id_win32,
|
||||
disable_direct_composition,
|
||||
} = creation_info;
|
||||
let classname = register_wnd_class(icon);
|
||||
let hide_title_bar = params
|
||||
@@ -376,6 +375,8 @@ impl WindowsWindow {
|
||||
.map(|title| title.as_ref())
|
||||
.unwrap_or(""),
|
||||
);
|
||||
let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION)
|
||||
.is_ok_and(|value| value == "true" || value == "1");
|
||||
|
||||
let (mut dwexstyle, dwstyle) = if params.kind == WindowKind::PopUp {
|
||||
(WS_EX_TOOLWINDOW, WINDOW_STYLE(0x0))
|
||||
|
||||
@@ -1015,7 +1015,7 @@ impl Render for LspTool {
|
||||
.anchor(Corner::BottomLeft)
|
||||
.with_handle(self.popover_menu_handle.clone())
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("zed-lsp-tool-button", IconName::BoltFilledAlt)
|
||||
IconButton::new("zed-lsp-tool-button", IconName::Bolt)
|
||||
.when_some(indicator, IconButton::indicator)
|
||||
.icon_size(IconSize::Small)
|
||||
.indicator_border_color(Some(cx.theme().colors().status_bar_background)),
|
||||
|
||||
103
crates/onboarding/src/basics_page.rs
Normal file
103
crates/onboarding/src/basics_page.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use fs::Fs;
|
||||
use gpui::{App, IntoElement, Window};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use theme::{ThemeMode, ThemeSettings};
|
||||
use ui::{SwitchField, ToggleButtonGroup, ToggleButtonSimple, ToggleButtonWithIcon, prelude::*};
|
||||
|
||||
fn read_theme_selection(cx: &App) -> ThemeMode {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
settings
|
||||
.theme_selection
|
||||
.as_ref()
|
||||
.and_then(|selection| selection.mode())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn write_theme_selection(theme_mode: ThemeMode, cx: &App) {
|
||||
let fs = <dyn Fs>::global(cx);
|
||||
|
||||
update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
|
||||
settings.set_mode(theme_mode);
|
||||
});
|
||||
}
|
||||
|
||||
fn render_theme_section(cx: &mut App) -> impl IntoElement {
|
||||
let theme_mode = read_theme_selection(cx);
|
||||
|
||||
h_flex().justify_between().child(Label::new("Theme")).child(
|
||||
ToggleButtonGroup::single_row(
|
||||
"theme-selector-onboarding",
|
||||
[
|
||||
ToggleButtonSimple::new("Light", |_, _, cx| {
|
||||
write_theme_selection(ThemeMode::Light, cx)
|
||||
}),
|
||||
ToggleButtonSimple::new("Dark", |_, _, cx| {
|
||||
write_theme_selection(ThemeMode::Dark, cx)
|
||||
}),
|
||||
ToggleButtonSimple::new("System", |_, _, cx| {
|
||||
write_theme_selection(ThemeMode::System, cx)
|
||||
}),
|
||||
],
|
||||
)
|
||||
.selected_index(match theme_mode {
|
||||
ThemeMode::Light => 0,
|
||||
ThemeMode::Dark => 1,
|
||||
ThemeMode::System => 2,
|
||||
})
|
||||
.style(ui::ToggleButtonGroupStyle::Outlined)
|
||||
.button_width(rems_from_px(64.)),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_telemetry_section() -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.child(Label::new("Telemetry").size(LabelSize::Large))
|
||||
.child(SwitchField::new(
|
||||
"vim_mode",
|
||||
"Help Improve Zed",
|
||||
"Sending anonymous usage data helps us build the right features and create the best experience.",
|
||||
ui::ToggleState::Selected,
|
||||
|_, _, _| {},
|
||||
))
|
||||
.child(SwitchField::new(
|
||||
"vim_mode",
|
||||
"Help Fix Zed",
|
||||
"Send crash reports so we can fix critical issues fast.",
|
||||
ui::ToggleState::Selected,
|
||||
|_, _, _| {},
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn render_basics_page(_: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.child(render_theme_section(cx))
|
||||
.child(
|
||||
v_flex().gap_2().child(Label::new("Base Keymap")).child(
|
||||
ToggleButtonGroup::two_rows(
|
||||
"multiple_row_test",
|
||||
[
|
||||
ToggleButtonWithIcon::new("VS Code", IconName::AiZed, |_, _, _| {}),
|
||||
ToggleButtonWithIcon::new("Jetbrains", IconName::AiZed, |_, _, _| {}),
|
||||
ToggleButtonWithIcon::new("Sublime Text", IconName::AiZed, |_, _, _| {}),
|
||||
],
|
||||
[
|
||||
ToggleButtonWithIcon::new("Atom", IconName::AiZed, |_, _, _| {}),
|
||||
ToggleButtonWithIcon::new("Emacs", IconName::AiZed, |_, _, _| {}),
|
||||
ToggleButtonWithIcon::new("Cursor (Beta)", IconName::AiZed, |_, _, _| {}),
|
||||
],
|
||||
)
|
||||
.button_width(rems_from_px(230.))
|
||||
.style(ui::ToggleButtonGroupStyle::Outlined)
|
||||
),
|
||||
)
|
||||
.child(v_flex().justify_center().child(div().h_0().child("hack").invisible()).child(SwitchField::new(
|
||||
"vim_mode",
|
||||
"Vim Mode",
|
||||
"Coming from Neovim? Zed's first-class implementation of Vim Mode has got your back.",
|
||||
ui::ToggleState::Selected,
|
||||
|_, _, _| {},
|
||||
)))
|
||||
.child(render_telemetry_section())
|
||||
}
|
||||
@@ -6,10 +6,8 @@ use project::project_settings::ProjectSettings;
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
|
||||
use ui::{
|
||||
Clickable, ContextMenu, DropdownMenu, IconButton, Label, LabelCommon, LabelSize,
|
||||
NumericStepper, ParentElement, SharedString, Styled, SwitchColor, SwitchField,
|
||||
ToggleButtonGroup, ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, div, h_flex, px,
|
||||
v_flex,
|
||||
ButtonLike, ContextMenu, DropdownMenu, NumericStepper, SwitchField, ToggleButtonGroup,
|
||||
ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, prelude::*,
|
||||
};
|
||||
|
||||
use crate::{ImportCursorSettings, ImportVsCodeSettings};
|
||||
@@ -118,153 +116,212 @@ fn write_buffer_font_family(font_family: SharedString, cx: &mut App) {
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
fn render_import_settings_section() -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child(
|
||||
v_flex()
|
||||
.child(Label::new("Import Settings").size(LabelSize::Large))
|
||||
.child(
|
||||
Label::new("Automatically pull your settings from other editors.")
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.child(
|
||||
h_flex().w_full().child(
|
||||
ButtonLike::new("import_vs_code")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Large)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.px_1()
|
||||
.child(
|
||||
Icon::new(IconName::Sparkle)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(Label::new("VS Code")),
|
||||
)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(
|
||||
ImportVsCodeSettings::default().boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().child(
|
||||
ButtonLike::new("import_cursor")
|
||||
.full_width()
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Large)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.px_1()
|
||||
.child(
|
||||
Icon::new(IconName::Sparkle)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
)
|
||||
.child(Label::new("Cursor")),
|
||||
)
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(
|
||||
ImportCursorSettings::default().boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
let ui_font_size = theme_settings.ui_font_size(cx);
|
||||
let font_family = theme_settings.buffer_font.family.clone();
|
||||
let buffer_font_size = theme_settings.buffer_font_size(cx);
|
||||
|
||||
v_flex()
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.child(Label::new("Import Settings").size(LabelSize::Large))
|
||||
.child(
|
||||
Label::new("Automatically pull your settings from other editors.")
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(Label::new("UI Font"))
|
||||
.child(
|
||||
IconButton::new("import-vs-code-settings", ui::IconName::Code).on_click(
|
||||
|_, window, cx| {
|
||||
window
|
||||
.dispatch_action(ImportVsCodeSettings::default().boxed_clone(), cx)
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("import-cursor-settings", ui::IconName::CursorIBeam).on_click(
|
||||
|_, window, cx| {
|
||||
window
|
||||
.dispatch_action(ImportCursorSettings::default().boxed_clone(), cx)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(Label::new("Popular Settings").size(LabelSize::Large))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_4()
|
||||
.justify_between()
|
||||
.child(
|
||||
v_flex()
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_1()
|
||||
.child(Label::new("UI Font"))
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(div().min_w(px(120.)).child(DropdownMenu::new(
|
||||
"ui-font-family",
|
||||
theme_settings.ui_font.family.clone(),
|
||||
ContextMenu::build(window, cx, |mut menu, _, cx| {
|
||||
let font_family_cache = FontFamilyCache::global(cx);
|
||||
DropdownMenu::new(
|
||||
"ui-font-family",
|
||||
theme_settings.ui_font.family.clone(),
|
||||
ContextMenu::build(window, cx, |mut menu, _, cx| {
|
||||
let font_family_cache = FontFamilyCache::global(cx);
|
||||
|
||||
for font_name in font_family_cache.list_font_families(cx) {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let font_name = font_name.clone();
|
||||
move |_window, _cx| {
|
||||
Label::new(font_name.clone())
|
||||
.into_any_element()
|
||||
}
|
||||
},
|
||||
{
|
||||
let font_name = font_name.clone();
|
||||
move |_window, cx| {
|
||||
write_ui_font_family(font_name.clone(), cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
for font_name in font_family_cache.list_font_families(cx) {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let font_name = font_name.clone();
|
||||
move |_window, _cx| {
|
||||
Label::new(font_name.clone()).into_any_element()
|
||||
}
|
||||
},
|
||||
{
|
||||
let font_name = font_name.clone();
|
||||
move |_window, cx| {
|
||||
write_ui_font_family(font_name.clone(), cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
menu
|
||||
}),
|
||||
)))
|
||||
.child(
|
||||
NumericStepper::new(
|
||||
"ui-font-size",
|
||||
ui_font_size.to_string(),
|
||||
move |_, _, cx| {
|
||||
write_ui_font_size(ui_font_size - px(1.), cx);
|
||||
},
|
||||
move |_, _, cx| {
|
||||
write_ui_font_size(ui_font_size + px(1.), cx);
|
||||
},
|
||||
)
|
||||
.border(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.justify_between()
|
||||
.gap_1()
|
||||
.child(Label::new("Editor Font"))
|
||||
menu
|
||||
}),
|
||||
)
|
||||
.style(ui::DropdownStyle::Outlined)
|
||||
.full_width(true),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(DropdownMenu::new(
|
||||
"buffer-font-family",
|
||||
font_family,
|
||||
ContextMenu::build(window, cx, |mut menu, _, cx| {
|
||||
let font_family_cache = FontFamilyCache::global(cx);
|
||||
|
||||
for font_name in font_family_cache.list_font_families(cx) {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let font_name = font_name.clone();
|
||||
move |_window, _cx| {
|
||||
Label::new(font_name.clone())
|
||||
.into_any_element()
|
||||
}
|
||||
},
|
||||
{
|
||||
let font_name = font_name.clone();
|
||||
move |_window, cx| {
|
||||
write_buffer_font_family(
|
||||
font_name.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
menu
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
NumericStepper::new(
|
||||
"buffer-font-size",
|
||||
buffer_font_size.to_string(),
|
||||
move |_, _, cx| {
|
||||
write_buffer_font_size(buffer_font_size - px(1.), cx);
|
||||
},
|
||||
move |_, _, cx| {
|
||||
write_buffer_font_size(buffer_font_size + px(1.), cx);
|
||||
},
|
||||
)
|
||||
.border(),
|
||||
),
|
||||
NumericStepper::new(
|
||||
"ui-font-size",
|
||||
ui_font_size.to_string(),
|
||||
move |_, _, cx| {
|
||||
write_ui_font_size(ui_font_size - px(1.), cx);
|
||||
},
|
||||
move |_, _, cx| {
|
||||
write_ui_font_size(ui_font_size + px(1.), cx);
|
||||
},
|
||||
)
|
||||
.style(ui::NumericStepperStyle::Outlined),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(Label::new("Editor Font"))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(
|
||||
DropdownMenu::new(
|
||||
"buffer-font-family",
|
||||
font_family,
|
||||
ContextMenu::build(window, cx, |mut menu, _, cx| {
|
||||
let font_family_cache = FontFamilyCache::global(cx);
|
||||
|
||||
for font_name in font_family_cache.list_font_families(cx) {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let font_name = font_name.clone();
|
||||
move |_window, _cx| {
|
||||
Label::new(font_name.clone()).into_any_element()
|
||||
}
|
||||
},
|
||||
{
|
||||
let font_name = font_name.clone();
|
||||
move |_window, cx| {
|
||||
write_buffer_font_family(font_name.clone(), cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
menu
|
||||
}),
|
||||
)
|
||||
.style(ui::DropdownStyle::Outlined)
|
||||
.full_width(true),
|
||||
)
|
||||
.child(
|
||||
NumericStepper::new(
|
||||
"buffer-font-size",
|
||||
buffer_font_size.to_string(),
|
||||
move |_, _, cx| {
|
||||
write_buffer_font_size(buffer_font_size - px(1.), cx);
|
||||
},
|
||||
move |_, _, cx| {
|
||||
write_buffer_font_size(buffer_font_size + px(1.), cx);
|
||||
},
|
||||
)
|
||||
.style(ui::NumericStepperStyle::Outlined),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_5()
|
||||
.child(Label::new("Popular Settings").size(LabelSize::Large).mt_8())
|
||||
.child(render_font_customization_section(window, cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.child(Label::new("Mini Map"))
|
||||
.child(
|
||||
v_flex().child(Label::new("Mini Map")).child(
|
||||
Label::new("See a high-level overview of your source code.")
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
ToggleButtonGroup::single_row(
|
||||
"onboarding-show-mini-map",
|
||||
@@ -289,36 +346,37 @@ pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl Int
|
||||
.button_width(ui::rems_from_px(64.)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
SwitchField::new(
|
||||
"onboarding-enable-inlay-hints",
|
||||
"Inlay Hints",
|
||||
"See parameter names for function and method calls inline.",
|
||||
if read_inlay_hints(cx) {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
|toggle_state, _, cx| {
|
||||
write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
|
||||
},
|
||||
)
|
||||
.color(SwitchColor::Accent),
|
||||
)
|
||||
.child(
|
||||
SwitchField::new(
|
||||
"onboarding-git-blame-switch",
|
||||
"Git Blame",
|
||||
"See who committed each line on a given file.",
|
||||
if read_git_blame(cx) {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
|toggle_state, _, cx| {
|
||||
set_git_blame(toggle_state == &ToggleState::Selected, cx);
|
||||
},
|
||||
)
|
||||
.color(SwitchColor::Accent),
|
||||
)
|
||||
.child(SwitchField::new(
|
||||
"onboarding-enable-inlay-hints",
|
||||
"Inlay Hints",
|
||||
"See parameter names for function and method calls inline.",
|
||||
if read_inlay_hints(cx) {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
|toggle_state, _, cx| {
|
||||
write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
|
||||
},
|
||||
))
|
||||
.child(SwitchField::new(
|
||||
"onboarding-git-blame-switch",
|
||||
"Git Blame",
|
||||
"See who committed each line on a given file.",
|
||||
if read_git_blame(cx) {
|
||||
ui::ToggleState::Selected
|
||||
} else {
|
||||
ui::ToggleState::Unselected
|
||||
},
|
||||
|toggle_state, _, cx| {
|
||||
set_git_blame(toggle_state == &ToggleState::Selected, cx);
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child(render_import_settings_section())
|
||||
.child(render_popular_settings_section(window, cx))
|
||||
}
|
||||
|
||||
@@ -10,13 +10,9 @@ use gpui::{
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings, SettingsStore, VsCodeSettingsSource, update_settings_file};
|
||||
use settings::{SettingsStore, VsCodeSettingsSource};
|
||||
use std::sync::Arc;
|
||||
use theme::{ThemeMode, ThemeSettings};
|
||||
use ui::{
|
||||
Divider, FluentBuilder, Headline, KeyBinding, ParentElement as _, StatefulInteractiveElement,
|
||||
ToggleButtonGroup, ToggleButtonSimple, Vector, VectorName, prelude::*, rems_from_px,
|
||||
};
|
||||
use ui::{FluentBuilder, KeyBinding, Vector, VectorName, prelude::*, rems_from_px};
|
||||
use workspace::{
|
||||
AppState, Workspace, WorkspaceId,
|
||||
dock::DockPosition,
|
||||
@@ -24,6 +20,7 @@ use workspace::{
|
||||
open_new, with_active_or_new_workspace,
|
||||
};
|
||||
|
||||
mod basics_page;
|
||||
mod editing_page;
|
||||
mod welcome;
|
||||
|
||||
@@ -205,23 +202,6 @@ pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyh
|
||||
)
|
||||
}
|
||||
|
||||
fn read_theme_selection(cx: &App) -> ThemeMode {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
settings
|
||||
.theme_selection
|
||||
.as_ref()
|
||||
.and_then(|selection| selection.mode())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn write_theme_selection(theme_mode: ThemeMode, cx: &App) {
|
||||
let fs = <dyn Fs>::global(cx);
|
||||
|
||||
update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
|
||||
settings.set_mode(theme_mode);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum SelectedPage {
|
||||
Basics,
|
||||
@@ -246,7 +226,7 @@ impl Onboarding {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_page_nav(
|
||||
fn render_nav_button(
|
||||
&mut self,
|
||||
page: SelectedPage,
|
||||
_: &mut Window,
|
||||
@@ -257,54 +237,119 @@ impl Onboarding {
|
||||
SelectedPage::Editing => "Editing",
|
||||
SelectedPage::AiSetup => "AI Setup",
|
||||
};
|
||||
|
||||
let binding = match page {
|
||||
SelectedPage::Basics => {
|
||||
KeyBinding::new(vec![gpui::Keystroke::parse("cmd-1").unwrap()], cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
}
|
||||
SelectedPage::Editing => {
|
||||
KeyBinding::new(vec![gpui::Keystroke::parse("cmd-2").unwrap()], cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
}
|
||||
SelectedPage::AiSetup => {
|
||||
KeyBinding::new(vec![gpui::Keystroke::parse("cmd-3").unwrap()], cx)
|
||||
.map(|kb| kb.size(rems_from_px(12.)))
|
||||
}
|
||||
};
|
||||
|
||||
let selected = self.selected_page == page;
|
||||
|
||||
h_flex()
|
||||
.id(text)
|
||||
.rounded_sm()
|
||||
.child(text)
|
||||
.child(binding)
|
||||
.h_8()
|
||||
.relative()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.px_2()
|
||||
.py_0p5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.map(|this| {
|
||||
if selected {
|
||||
this.bg(Color::Selected.color(cx))
|
||||
.border_l_1()
|
||||
.border_color(Color::Accent.color(cx))
|
||||
} else {
|
||||
this.text_color(Color::Muted.color(cx))
|
||||
}
|
||||
.rounded_sm()
|
||||
.when(selected, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.h_4()
|
||||
.w_px()
|
||||
.bg(cx.theme().colors().text_accent)
|
||||
.absolute()
|
||||
.left_0(),
|
||||
)
|
||||
})
|
||||
.hover(|style| {
|
||||
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
||||
.child(Label::new(text).map(|this| {
|
||||
if selected {
|
||||
style.bg(Color::Selected.color(cx).opacity(0.6))
|
||||
this.color(Color::Default)
|
||||
} else {
|
||||
style.bg(Color::Selected.color(cx).opacity(0.3))
|
||||
this.color(Color::Muted)
|
||||
}
|
||||
})
|
||||
}))
|
||||
.child(binding)
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.selected_page = page;
|
||||
cx.notify();
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_nav(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.h_full()
|
||||
.w(rems_from_px(220.))
|
||||
.flex_shrink_0()
|
||||
.gap_4()
|
||||
.justify_between()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.child(
|
||||
h_flex()
|
||||
.px_2()
|
||||
.gap_4()
|
||||
.child(Vector::square(VectorName::ZedLogo, rems(2.5)))
|
||||
.child(
|
||||
v_flex()
|
||||
.child(
|
||||
Headline::new("Welcome to Zed").size(HeadlineSize::Small),
|
||||
)
|
||||
.child(
|
||||
Label::new("The editor for what's next")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small)
|
||||
.italic(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child(
|
||||
v_flex()
|
||||
.py_4()
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().colors().border_variant.opacity(0.5))
|
||||
.gap_1()
|
||||
.children([
|
||||
self.render_nav_button(SelectedPage::Basics, window, cx)
|
||||
.into_element(),
|
||||
self.render_nav_button(SelectedPage::Editing, window, cx)
|
||||
.into_element(),
|
||||
self.render_nav_button(SelectedPage::AiSetup, window, cx)
|
||||
.into_element(),
|
||||
]),
|
||||
)
|
||||
.child(Button::new("skip_all", "Skip All")),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new("sign_in", "Sign In")
|
||||
.style(ButtonStyle::Outlined)
|
||||
.full_width(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_page(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
match self.selected_page {
|
||||
SelectedPage::Basics => self.render_basics_page(window, cx).into_any_element(),
|
||||
SelectedPage::Basics => {
|
||||
crate::basics_page::render_basics_page(window, cx).into_any_element()
|
||||
}
|
||||
SelectedPage::Editing => {
|
||||
crate::editing_page::render_editing_page(window, cx).into_any_element()
|
||||
}
|
||||
@@ -312,36 +357,6 @@ impl Onboarding {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_basics_page(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let theme_mode = read_theme_selection(cx);
|
||||
|
||||
v_flex().child(
|
||||
h_flex().justify_between().child(Label::new("Theme")).child(
|
||||
ToggleButtonGroup::single_row(
|
||||
"theme-selector-onboarding",
|
||||
[
|
||||
ToggleButtonSimple::new("Light", |_, _, cx| {
|
||||
write_theme_selection(ThemeMode::Light, cx)
|
||||
}),
|
||||
ToggleButtonSimple::new("Dark", |_, _, cx| {
|
||||
write_theme_selection(ThemeMode::Dark, cx)
|
||||
}),
|
||||
ToggleButtonSimple::new("System", |_, _, cx| {
|
||||
write_theme_selection(ThemeMode::System, cx)
|
||||
}),
|
||||
],
|
||||
)
|
||||
.selected_index(match theme_mode {
|
||||
ThemeMode::Light => 0,
|
||||
ThemeMode::Dark => 1,
|
||||
ThemeMode::System => 2,
|
||||
})
|
||||
.style(ui::ToggleButtonGroupStyle::Outlined)
|
||||
.button_width(rems_from_px(64.)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_ai_setup_page(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
div().child("ai setup page")
|
||||
}
|
||||
@@ -352,44 +367,27 @@ impl Render for Onboarding {
|
||||
h_flex()
|
||||
.image_cache(gpui::retain_all("onboarding-page"))
|
||||
.key_context("onboarding-page")
|
||||
.px_24()
|
||||
.py_12()
|
||||
.items_start()
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
v_flex()
|
||||
.w_1_3()
|
||||
.h_full()
|
||||
h_flex()
|
||||
.max_w(rems_from_px(1100.))
|
||||
.size_full()
|
||||
.m_auto()
|
||||
.py_20()
|
||||
.px_12()
|
||||
.items_start()
|
||||
.gap_12()
|
||||
.child(self.render_nav(window, cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.pt_0p5()
|
||||
.child(Vector::square(VectorName::ZedLogo, rems(2.)))
|
||||
.child(
|
||||
v_flex()
|
||||
.left_1()
|
||||
.items_center()
|
||||
.child(Headline::new("Welcome to Zed"))
|
||||
.child(
|
||||
Label::new("The editor for what's next")
|
||||
.color(Color::Muted)
|
||||
.italic(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.p_1()
|
||||
.child(Divider::horizontal())
|
||||
.child(
|
||||
v_flex().gap_1().children([
|
||||
self.render_page_nav(SelectedPage::Basics, window, cx)
|
||||
.into_element(),
|
||||
self.render_page_nav(SelectedPage::Editing, window, cx)
|
||||
.into_element(),
|
||||
self.render_page_nav(SelectedPage::AiSetup, window, cx)
|
||||
.into_element(),
|
||||
]),
|
||||
div()
|
||||
.pl_12()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border_variant.opacity(0.5))
|
||||
.size_full()
|
||||
.child(self.render_page(window, cx)),
|
||||
),
|
||||
)
|
||||
.child(div().child(Divider::vertical()).h_full())
|
||||
.child(div().w_2_3().h_full().child(self.render_page(window, cx)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4911,7 +4911,7 @@ impl LspStore {
|
||||
language_server_id: server_id.0 as u64,
|
||||
hint: Some(InlayHints::project_to_proto_hint(hint.clone())),
|
||||
};
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
let response = upstream_client
|
||||
.request(request)
|
||||
.await
|
||||
@@ -5125,7 +5125,7 @@ impl LspStore {
|
||||
trigger,
|
||||
version: serialize_version(&buffer.read(cx).version()),
|
||||
};
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
client
|
||||
.request(request)
|
||||
.await?
|
||||
@@ -5284,7 +5284,7 @@ impl LspStore {
|
||||
GetDefinitions { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
Ok(definitions_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -5357,7 +5357,7 @@ impl LspStore {
|
||||
GetDeclarations { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
Ok(declarations_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -5430,7 +5430,7 @@ impl LspStore {
|
||||
GetTypeDefinitions { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
Ok(type_definitions_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -5503,7 +5503,7 @@ impl LspStore {
|
||||
GetImplementations { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
Ok(implementations_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -5576,7 +5576,7 @@ impl LspStore {
|
||||
GetReferences { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
Ok(references_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -5660,7 +5660,7 @@ impl LspStore {
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
Ok(all_actions_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -6854,7 +6854,7 @@ impl LspStore {
|
||||
} else {
|
||||
let document_colors_task =
|
||||
self.request_multiple_lsp_locally(buffer, None::<usize>, GetDocumentColor, cx);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
Ok(document_colors_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -6933,7 +6933,7 @@ impl LspStore {
|
||||
GetSignatureHelp { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
all_actions_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -7010,7 +7010,7 @@ impl LspStore {
|
||||
GetHover { position },
|
||||
cx,
|
||||
);
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
all_actions_task
|
||||
.await
|
||||
.into_iter()
|
||||
@@ -8013,7 +8013,7 @@ impl LspStore {
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
let mut responses = Vec::with_capacity(response_results.len());
|
||||
while let Some((server_id, response_result)) = response_results.next().await {
|
||||
if let Some(response) = response_result.log_err() {
|
||||
|
||||
@@ -3372,7 +3372,7 @@ impl Project {
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.definitions(buffer, position, cx)
|
||||
});
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
let result = task.await;
|
||||
drop(guard);
|
||||
result
|
||||
@@ -3390,7 +3390,7 @@ impl Project {
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.declarations(buffer, position, cx)
|
||||
});
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
let result = task.await;
|
||||
drop(guard);
|
||||
result
|
||||
@@ -3408,7 +3408,7 @@ impl Project {
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.type_definitions(buffer, position, cx)
|
||||
});
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
let result = task.await;
|
||||
drop(guard);
|
||||
result
|
||||
@@ -3426,7 +3426,7 @@ impl Project {
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.implementations(buffer, position, cx)
|
||||
});
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
let result = task.await;
|
||||
drop(guard);
|
||||
result
|
||||
@@ -3444,7 +3444,7 @@ impl Project {
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.references(buffer, position, cx)
|
||||
});
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
let result = task.await;
|
||||
drop(guard);
|
||||
result
|
||||
@@ -3996,7 +3996,7 @@ impl Project {
|
||||
let task = self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.request_lsp(buffer_handle, server, request, cx)
|
||||
});
|
||||
cx.spawn(async move |_, _| {
|
||||
cx.background_spawn(async move {
|
||||
let result = task.await;
|
||||
drop(guard);
|
||||
result
|
||||
|
||||
@@ -23,6 +23,7 @@ workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
@@ -30,4 +31,5 @@ menu.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
serde_json.workspace = true
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
theme = { workspace = true, features = ["test-support"] }
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -74,13 +74,10 @@ impl SettingsProfileSelectorDelegate {
|
||||
cx: &mut Context<SettingsProfileSelector>,
|
||||
) -> Self {
|
||||
let settings_store = cx.global::<SettingsStore>();
|
||||
let mut profile_names: Vec<String> = settings_store
|
||||
let mut profile_names: Vec<Option<String>> = settings_store
|
||||
.configured_settings_profiles()
|
||||
.map(|s| s.to_string())
|
||||
.map(|s| Some(s.to_string()))
|
||||
.collect();
|
||||
|
||||
profile_names.sort();
|
||||
let mut profile_names: Vec<_> = profile_names.into_iter().map(Some).collect();
|
||||
profile_names.insert(0, None);
|
||||
|
||||
let matches = profile_names
|
||||
@@ -283,12 +280,15 @@ fn display_name(profile_name: &Option<String>) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use client;
|
||||
use editor;
|
||||
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
|
||||
use language;
|
||||
use menu::{Cancel, Confirm, SelectNext, SelectPrevious};
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use settings::Settings;
|
||||
use theme::{self, ThemeSettings};
|
||||
use workspace::{self, AppState};
|
||||
use zed_actions::settings_profile_selector;
|
||||
|
||||
@@ -298,6 +298,12 @@ mod tests {
|
||||
) -> (Entity<Workspace>, &mut VisualTestContext) {
|
||||
cx.update(|cx| {
|
||||
let state = AppState::test(cx);
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
settings::init(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
ThemeSettings::register(cx);
|
||||
client::init_settings(cx);
|
||||
language::init(cx);
|
||||
super::init(cx);
|
||||
editor::init(cx);
|
||||
@@ -309,7 +315,8 @@ mod tests {
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
let settings_json = json!({
|
||||
"profiles": profiles_json
|
||||
"buffer_font_size": 10.0,
|
||||
"profiles": profiles_json,
|
||||
});
|
||||
|
||||
store
|
||||
@@ -325,6 +332,8 @@ mod tests {
|
||||
|
||||
cx.update(|_, cx| {
|
||||
assert!(!cx.has_global::<ActiveSettingsProfileName>());
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
assert_eq!(theme_settings.buffer_font_size(cx).0, 10.0);
|
||||
});
|
||||
|
||||
(workspace, cx)
|
||||
@@ -347,32 +356,37 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_settings_profile_selector_state(cx: &mut TestAppContext) {
|
||||
let classroom_and_streaming_profile_name = "Classroom / Streaming".to_string();
|
||||
let demo_videos_profile_name = "Demo Videos".to_string();
|
||||
|
||||
let profiles_json = json!({
|
||||
"Demo Videos": {
|
||||
"buffer_font_size": 14
|
||||
classroom_and_streaming_profile_name.clone(): {
|
||||
"buffer_font_size": 20.0,
|
||||
},
|
||||
"Classroom / Streaming": {
|
||||
"buffer_font_size": 16,
|
||||
"vim_mode": true
|
||||
demo_videos_profile_name.clone(): {
|
||||
"buffer_font_size": 15.0
|
||||
}
|
||||
});
|
||||
let (workspace, cx) = init_test(profiles_json.clone(), cx).await;
|
||||
|
||||
cx.dispatch_action(settings_profile_selector::Toggle);
|
||||
|
||||
let picker = active_settings_profile_picker(&workspace, cx);
|
||||
|
||||
picker.read_with(cx, |picker, cx| {
|
||||
assert_eq!(picker.delegate.matches.len(), 3);
|
||||
assert_eq!(picker.delegate.matches[0].string, "Disabled");
|
||||
assert_eq!(picker.delegate.matches[1].string, "Classroom / Streaming");
|
||||
assert_eq!(picker.delegate.matches[2].string, "Demo Videos");
|
||||
assert_eq!(picker.delegate.matches[0].string, display_name(&None));
|
||||
assert_eq!(
|
||||
picker.delegate.matches[1].string,
|
||||
classroom_and_streaming_profile_name
|
||||
);
|
||||
assert_eq!(picker.delegate.matches[2].string, demo_videos_profile_name);
|
||||
assert_eq!(picker.delegate.matches.get(3), None);
|
||||
|
||||
assert_eq!(picker.delegate.selected_index, 0);
|
||||
assert_eq!(picker.delegate.selected_profile_name, None);
|
||||
|
||||
assert_eq!(cx.try_global::<ActiveSettingsProfileName>(), None);
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 10.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(Confirm);
|
||||
@@ -389,20 +403,23 @@ mod tests {
|
||||
assert_eq!(picker.delegate.selected_index, 1);
|
||||
assert_eq!(
|
||||
picker.delegate.selected_profile_name,
|
||||
Some("Classroom / Streaming".to_string())
|
||||
Some(classroom_and_streaming_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Classroom / Streaming".to_string())
|
||||
Some(classroom_and_streaming_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 20.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(Cancel);
|
||||
|
||||
cx.update(|_, cx| {
|
||||
assert_eq!(cx.try_global::<ActiveSettingsProfileName>(), None);
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 10.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(settings_profile_selector::Toggle);
|
||||
@@ -414,14 +431,16 @@ mod tests {
|
||||
assert_eq!(picker.delegate.selected_index, 1);
|
||||
assert_eq!(
|
||||
picker.delegate.selected_profile_name,
|
||||
Some("Classroom / Streaming".to_string())
|
||||
Some(classroom_and_streaming_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Classroom / Streaming".to_string())
|
||||
Some(classroom_and_streaming_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 20.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(SelectNext);
|
||||
@@ -430,14 +449,16 @@ mod tests {
|
||||
assert_eq!(picker.delegate.selected_index, 2);
|
||||
assert_eq!(
|
||||
picker.delegate.selected_profile_name,
|
||||
Some("Demo Videos".to_string())
|
||||
Some(demo_videos_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Demo Videos".to_string())
|
||||
Some(demo_videos_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 15.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(Confirm);
|
||||
@@ -446,8 +467,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Demo Videos".to_string())
|
||||
Some(demo_videos_profile_name.clone())
|
||||
);
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 15.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(settings_profile_selector::Toggle);
|
||||
@@ -457,14 +479,15 @@ mod tests {
|
||||
assert_eq!(picker.delegate.selected_index, 2);
|
||||
assert_eq!(
|
||||
picker.delegate.selected_profile_name,
|
||||
Some("Demo Videos".to_string())
|
||||
Some(demo_videos_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Demo Videos".to_string())
|
||||
Some(demo_videos_profile_name.clone())
|
||||
);
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 15.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(SelectPrevious);
|
||||
@@ -473,14 +496,16 @@ mod tests {
|
||||
assert_eq!(picker.delegate.selected_index, 1);
|
||||
assert_eq!(
|
||||
picker.delegate.selected_profile_name,
|
||||
Some("Classroom / Streaming".to_string())
|
||||
Some(classroom_and_streaming_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Classroom / Streaming".to_string())
|
||||
Some(classroom_and_streaming_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 20.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(Cancel);
|
||||
@@ -489,8 +514,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Demo Videos".to_string())
|
||||
Some(demo_videos_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 15.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(settings_profile_selector::Toggle);
|
||||
@@ -500,14 +527,16 @@ mod tests {
|
||||
assert_eq!(picker.delegate.selected_index, 2);
|
||||
assert_eq!(
|
||||
picker.delegate.selected_profile_name,
|
||||
Some("Demo Videos".to_string())
|
||||
Some(demo_videos_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Demo Videos".to_string())
|
||||
Some(demo_videos_profile_name)
|
||||
);
|
||||
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 15.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(SelectPrevious);
|
||||
@@ -516,14 +545,16 @@ mod tests {
|
||||
assert_eq!(picker.delegate.selected_index, 1);
|
||||
assert_eq!(
|
||||
picker.delegate.selected_profile_name,
|
||||
Some("Classroom / Streaming".to_string())
|
||||
Some(classroom_and_streaming_profile_name.clone())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
cx.try_global::<ActiveSettingsProfileName>()
|
||||
.map(|p| p.0.clone()),
|
||||
Some("Classroom / Streaming".to_string())
|
||||
Some(classroom_and_streaming_profile_name)
|
||||
);
|
||||
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 20.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(SelectPrevious);
|
||||
@@ -537,12 +568,15 @@ mod tests {
|
||||
.map(|p| p.0.clone()),
|
||||
None
|
||||
);
|
||||
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 10.0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(Confirm);
|
||||
|
||||
cx.update(|_, cx| {
|
||||
assert_eq!(cx.try_global::<ActiveSettingsProfileName>(), None);
|
||||
assert_eq!(ThemeSettings::get_global(cx).buffer_font_size(cx).0, 10.0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::application_menu::{
|
||||
|
||||
use auto_update::AutoUpdateStatus;
|
||||
use call::ActiveCall;
|
||||
use client::{Client, UserStore, zed_urls};
|
||||
use client::{Client, CloudUserStore, UserStore, zed_urls};
|
||||
use gpui::{
|
||||
Action, AnyElement, App, Context, Corner, Element, Entity, Focusable, InteractiveElement,
|
||||
IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled,
|
||||
@@ -126,6 +126,7 @@ pub struct TitleBar {
|
||||
platform_titlebar: Entity<PlatformTitleBar>,
|
||||
project: Entity<Project>,
|
||||
user_store: Entity<UserStore>,
|
||||
cloud_user_store: Entity<CloudUserStore>,
|
||||
client: Arc<Client>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
application_menu: Option<Entity<ApplicationMenu>>,
|
||||
@@ -179,24 +180,25 @@ impl Render for TitleBar {
|
||||
children.push(self.banner.clone().into_any_element())
|
||||
}
|
||||
|
||||
let is_authenticated = self.cloud_user_store.read(cx).is_authenticated();
|
||||
let status = self.client.status();
|
||||
let status = &*status.borrow();
|
||||
|
||||
let show_sign_in = !is_authenticated || !matches!(status, client::Status::Connected { .. });
|
||||
|
||||
children.push(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.pr_1()
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.children(self.render_call_controls(window, cx))
|
||||
.map(|el| {
|
||||
let status = self.client.status();
|
||||
let status = &*status.borrow();
|
||||
if matches!(status, client::Status::Connected { .. }) {
|
||||
el.child(self.render_user_menu_button(cx))
|
||||
} else {
|
||||
el.children(self.render_connection_status(status, cx))
|
||||
.when(TitleBarSettings::get_global(cx).show_sign_in, |el| {
|
||||
el.child(self.render_sign_in_button(cx))
|
||||
})
|
||||
.child(self.render_user_menu_button(cx))
|
||||
}
|
||||
.children(self.render_connection_status(status, cx))
|
||||
.when(
|
||||
show_sign_in && TitleBarSettings::get_global(cx).show_sign_in,
|
||||
|el| el.child(self.render_sign_in_button(cx)),
|
||||
)
|
||||
.when(is_authenticated, |parent| {
|
||||
parent.child(self.render_user_menu_button(cx))
|
||||
})
|
||||
.into_any_element(),
|
||||
);
|
||||
@@ -246,6 +248,7 @@ impl TitleBar {
|
||||
) -> Self {
|
||||
let project = workspace.project().clone();
|
||||
let user_store = workspace.app_state().user_store.clone();
|
||||
let cloud_user_store = workspace.app_state().cloud_user_store.clone();
|
||||
let client = workspace.app_state().client.clone();
|
||||
let active_call = ActiveCall::global(cx);
|
||||
|
||||
@@ -293,6 +296,7 @@ impl TitleBar {
|
||||
workspace: workspace.weak_handle(),
|
||||
project,
|
||||
user_store,
|
||||
cloud_user_store,
|
||||
client,
|
||||
_subscriptions: subscriptions,
|
||||
banner,
|
||||
@@ -628,15 +632,15 @@ impl TitleBar {
|
||||
}
|
||||
|
||||
pub fn render_user_menu_button(&mut self, cx: &mut Context<Self>) -> impl Element {
|
||||
let user_store = self.user_store.read(cx);
|
||||
if let Some(user) = user_store.current_user() {
|
||||
let cloud_user_store = self.cloud_user_store.read(cx);
|
||||
if let Some(user) = cloud_user_store.authenticated_user() {
|
||||
let has_subscription_period = self.user_store.read(cx).subscription_period().is_some();
|
||||
let plan = self.user_store.read(cx).current_plan().filter(|_| {
|
||||
// Since the user might be on the legacy free plan we filter based on whether we have a subscription period.
|
||||
has_subscription_period
|
||||
});
|
||||
|
||||
let user_avatar = user.avatar_uri.clone();
|
||||
let user_avatar = user.avatar_url.clone();
|
||||
let free_chip_bg = cx
|
||||
.theme()
|
||||
.colors()
|
||||
|
||||
@@ -8,6 +8,7 @@ use super::PopoverMenuHandle;
|
||||
pub enum DropdownStyle {
|
||||
#[default]
|
||||
Solid,
|
||||
Outlined,
|
||||
Ghost,
|
||||
}
|
||||
|
||||
@@ -147,6 +148,23 @@ impl Component for DropdownMenu {
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Outlined",
|
||||
DropdownMenu::new("outlined", "Outlined Dropdown", menu.clone())
|
||||
.style(DropdownStyle::Outlined)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Ghost",
|
||||
DropdownMenu::new("ghost", "Ghost Dropdown", menu.clone())
|
||||
.style(DropdownStyle::Ghost)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"States",
|
||||
vec![single_example(
|
||||
@@ -170,10 +188,13 @@ pub struct DropdownTriggerStyle {
|
||||
impl DropdownTriggerStyle {
|
||||
pub fn for_style(style: DropdownStyle, cx: &App) -> Self {
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
let bg = match style {
|
||||
DropdownStyle::Solid => colors.editor_background,
|
||||
DropdownStyle::Outlined => colors.surface_background,
|
||||
DropdownStyle::Ghost => colors.ghost_element_background,
|
||||
};
|
||||
|
||||
Self { bg }
|
||||
}
|
||||
}
|
||||
@@ -244,17 +265,24 @@ impl RenderOnce for DropdownMenuTrigger {
|
||||
let disabled = self.disabled;
|
||||
|
||||
let style = DropdownTriggerStyle::for_style(self.style, cx);
|
||||
let is_outlined = matches!(self.style, DropdownStyle::Outlined);
|
||||
|
||||
h_flex()
|
||||
.id("dropdown-menu-trigger")
|
||||
.justify_between()
|
||||
.rounded_sm()
|
||||
.bg(style.bg)
|
||||
.min_w_20()
|
||||
.pl_2()
|
||||
.pr_1p5()
|
||||
.py_0p5()
|
||||
.gap_2()
|
||||
.min_w_20()
|
||||
.justify_between()
|
||||
.rounded_sm()
|
||||
.bg(style.bg)
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.when(is_outlined, |this| {
|
||||
this.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.overflow_hidden()
|
||||
})
|
||||
.map(|el| {
|
||||
if self.full_width {
|
||||
el.w_full()
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
use gpui::ClickEvent;
|
||||
|
||||
use crate::{Divider, IconButtonShape, prelude::*};
|
||||
use crate::{IconButtonShape, prelude::*};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum NumericStepperStyle {
|
||||
Outlined,
|
||||
#[default]
|
||||
Ghost,
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
pub struct NumericStepper {
|
||||
id: ElementId,
|
||||
value: SharedString,
|
||||
style: NumericStepperStyle,
|
||||
on_decrement: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
||||
on_increment: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
||||
/// Whether to reserve space for the reset button.
|
||||
reserve_space_for_reset: bool,
|
||||
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
border: bool,
|
||||
}
|
||||
|
||||
impl NumericStepper {
|
||||
@@ -24,14 +31,19 @@ impl NumericStepper {
|
||||
Self {
|
||||
id: id.into(),
|
||||
value: value.into(),
|
||||
style: NumericStepperStyle::default(),
|
||||
on_decrement: Box::new(on_decrement),
|
||||
on_increment: Box::new(on_increment),
|
||||
border: false,
|
||||
reserve_space_for_reset: false,
|
||||
on_reset: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style(mut self, style: NumericStepperStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reserve_space_for_reset(mut self, reserve_space_for_reset: bool) -> Self {
|
||||
self.reserve_space_for_reset = reserve_space_for_reset;
|
||||
self
|
||||
@@ -44,11 +56,6 @@ impl NumericStepper {
|
||||
self.on_reset = Some(Box::new(on_reset));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn border(mut self) -> Self {
|
||||
self.border = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for NumericStepper {
|
||||
@@ -56,6 +63,8 @@ impl RenderOnce for NumericStepper {
|
||||
let shape = IconButtonShape::Square;
|
||||
let icon_size = IconSize::Small;
|
||||
|
||||
let is_outlined = matches!(self.style, NumericStepperStyle::Outlined);
|
||||
|
||||
h_flex()
|
||||
.id(self.id)
|
||||
.gap_1()
|
||||
@@ -81,31 +90,65 @@ impl RenderOnce for NumericStepper {
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.when(self.border, |this| {
|
||||
this.border_1().border_color(cx.theme().colors().border)
|
||||
})
|
||||
.px_1()
|
||||
.rounded_sm()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
IconButton::new("decrement", IconName::Dash)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.on_click(self.on_decrement),
|
||||
)
|
||||
.when(self.border, |this| {
|
||||
this.child(Divider::vertical().color(super::DividerColor::Border))
|
||||
.map(|this| {
|
||||
if is_outlined {
|
||||
this.overflow_hidden()
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
} else {
|
||||
this.px_1().bg(cx.theme().colors().editor_background)
|
||||
}
|
||||
})
|
||||
.child(Label::new(self.value))
|
||||
.when(self.border, |this| {
|
||||
this.child(Divider::vertical().color(super::DividerColor::Border))
|
||||
.map(|decrement| {
|
||||
if is_outlined {
|
||||
decrement.child(
|
||||
h_flex()
|
||||
.id("decrement_button")
|
||||
.p_1p5()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Icon::new(IconName::Dash).size(IconSize::Small))
|
||||
.on_click(self.on_decrement),
|
||||
)
|
||||
} else {
|
||||
decrement.child(
|
||||
IconButton::new("decrement", IconName::Dash)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.on_click(self.on_decrement),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(
|
||||
IconButton::new("increment", IconName::Plus)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.on_click(self.on_increment),
|
||||
),
|
||||
.when(is_outlined, |this| this)
|
||||
.child(Label::new(self.value).mx_3())
|
||||
.map(|increment| {
|
||||
if is_outlined {
|
||||
increment.child(
|
||||
h_flex()
|
||||
.id("increment_button")
|
||||
.p_1p5()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Icon::new(IconName::Plus).size(IconSize::Small))
|
||||
.on_click(self.on_increment),
|
||||
)
|
||||
} else {
|
||||
increment.child(
|
||||
IconButton::new("increment", IconName::Dash)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.on_click(self.on_increment),
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -116,7 +159,7 @@ impl Component for NumericStepper {
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"NumericStepper"
|
||||
"Numeric Stepper"
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
@@ -124,33 +167,39 @@ impl Component for NumericStepper {
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("A button used to increment or decrement a numeric value. ")
|
||||
Some("A button used to increment or decrement a numeric value.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.child(single_example(
|
||||
"Borderless",
|
||||
NumericStepper::new(
|
||||
"numeric-stepper-component-preview",
|
||||
"10",
|
||||
move |_, _, _| {},
|
||||
move |_, _, _| {},
|
||||
)
|
||||
.into_any_element(),
|
||||
))
|
||||
.child(single_example(
|
||||
"Border",
|
||||
NumericStepper::new(
|
||||
"numeric-stepper-with-border-component-preview",
|
||||
"10",
|
||||
move |_, _, _| {},
|
||||
move |_, _, _| {},
|
||||
)
|
||||
.border()
|
||||
.into_any_element(),
|
||||
))
|
||||
.gap_6()
|
||||
.children(vec![example_group_with_title(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
NumericStepper::new(
|
||||
"numeric-stepper-component-preview",
|
||||
"10",
|
||||
move |_, _, _| {},
|
||||
move |_, _, _| {},
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Outlined",
|
||||
NumericStepper::new(
|
||||
"numeric-stepper-with-border-component-preview",
|
||||
"10",
|
||||
move |_, _, _| {},
|
||||
move |_, _, _| {},
|
||||
)
|
||||
.style(NumericStepperStyle::Outlined)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
)])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -571,7 +571,7 @@ windows-core = { version = "0.61" }
|
||||
windows-numerics = { version = "0.2" }
|
||||
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||
windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] }
|
||||
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_Shell"] }
|
||||
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] }
|
||||
|
||||
[target.x86_64-pc-windows-msvc.build-dependencies]
|
||||
codespan-reporting = { version = "0.12" }
|
||||
@@ -595,7 +595,7 @@ windows-core = { version = "0.61" }
|
||||
windows-numerics = { version = "0.2" }
|
||||
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||
windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Memory", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] }
|
||||
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_Shell"] }
|
||||
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Shell"] }
|
||||
|
||||
[target.x86_64-unknown-linux-musl.dependencies]
|
||||
aes = { version = "0.8", default-features = false, features = ["zeroize"] }
|
||||
|
||||
Reference in New Issue
Block a user