Merge branch 'main' into chat-mentions-for-newly-joined-users
This commit is contained in:
commit
9693e14809
50 changed files with 1073 additions and 623 deletions
24
.github/ISSUE_TEMPLATE/0_feature_parity_report.yml
vendored
Normal file
24
.github/ISSUE_TEMPLATE/0_feature_parity_report.yml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
# This is just a temporary template - we will delete this once we move preview to stable
|
||||
name: Parity Report
|
||||
description: "Report that a regression occurred in the transition between gpui-1 Zed and gpui-2 Zed"
|
||||
labels: ["admin read", "triage", "enhancement", "defect", "parity"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the regression
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If necessary, add screenshots of gpui-1 Zed to show the missing feature
|
||||
description: Drag images into the text input below
|
||||
validations:
|
||||
required: false
|
24
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
Normal file
24
.github/ISSUE_TEMPLATE/0_feature_request.yml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
name: Feature Request
|
||||
description: "Tip: open this issue template from within Zed with the `request feature` command palette action"
|
||||
labels: ["admin read", "triage", "enhancement"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If applicable, add mockups / screenshots to help present your vision of the feature
|
||||
description: Drag images into the text input below
|
||||
validations:
|
||||
required: false
|
47
.github/ISSUE_TEMPLATE/1_language_support.yml
vendored
Normal file
47
.github/ISSUE_TEMPLATE/1_language_support.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
name: Language Support
|
||||
description: Request language support
|
||||
title: "<name_of_language> support"
|
||||
labels:
|
||||
[
|
||||
"admin read",
|
||||
"triage",
|
||||
"enhancement",
|
||||
"language",
|
||||
"unsupported language",
|
||||
"potential plugin",
|
||||
]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Language
|
||||
description: What language do you want support for?
|
||||
placeholder: HTML
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Tree Sitter parser link
|
||||
description: If applicable, provide a link to the appropriate tree sitter parser. Look here first - https://tree-sitter.github.io/tree-sitter/#available-parsers
|
||||
placeholder: https://github.com/tree-sitter/tree-sitter-html
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Language server link
|
||||
description: If applicable, provide a link to the appropriate language server. Look here first - https://microsoft.github.io/language-server-protocol/implementors/servers/
|
||||
placeholder: https://github.com/Microsoft/vscode/tree/main/extensions/html-language-features/server
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Misc notes
|
||||
description: Provide any additional things the team should consider when adding support for this language
|
||||
validations:
|
||||
required: false
|
38
.github/ISSUE_TEMPLATE/2_bug_report.yml
vendored
Normal file
38
.github/ISSUE_TEMPLATE/2_bug_report.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
name: Bug Report
|
||||
description: "Tip: open this issue template from within Zed with the `file bug report` command palette action"
|
||||
labels: ["admin read", "triage", "defect"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Check for existing issues
|
||||
description: Check the backlog of issues to reduce the chances of creating duplicates; if an issue already exists, place a `+1` (👍) on it.
|
||||
options:
|
||||
- label: Completed
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug / provide steps to reproduce it
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
|
||||
description: Drag issues into the text input below
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: |
|
||||
If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
description: Drag Zed.log into the text input below
|
||||
validations:
|
||||
required: false
|
13
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
13
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
contact_links:
|
||||
- name: Open Source Checklist
|
||||
url: https://github.com/zed-industries/community/issues/2197
|
||||
about: An issue tracking our progress towards open sourcing Zed
|
||||
- name: Top-Ranking Issues
|
||||
url: https://github.com/zed-industries/community/issues/52
|
||||
about: See an overview of the most popular Zed issues
|
||||
- name: Platform Support
|
||||
url: https://github.com/zed-industries/community/issues/174
|
||||
about: A quick note on platform support
|
||||
- name: Postive Feedback
|
||||
url: https://github.com/zed-industries/community/discussions/2337
|
||||
about: A central location for kind words about Zed
|
17
.github/workflows/update_top_ranking_issues.yml
vendored
Normal file
17
.github/workflows/update_top_ranking_issues.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
on:
|
||||
schedule:
|
||||
- cron: "0 */12 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update_top_ranking_issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10.5"
|
||||
architecture: "x64"
|
||||
cache: "pip"
|
||||
- run: pip install -r scripts/update_top_ranking_issues/requirements.txt
|
||||
- run: python scripts/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --prod
|
3
CODE_OF_CONDUCT.md
Normal file
3
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Code of Conduct
|
||||
|
||||
The Code of Conduct for this repository can be found online at [zed.dev/docs/code-of-conduct](https://zed.dev/docs/code-of-conduct).
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -9704,7 +9704,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.120.0"
|
||||
version = "0.121.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
|
|
13
README.md
13
README.md
|
@ -1,10 +1,3 @@
|
|||
# 🚧 TODO 🚧
|
||||
|
||||
- [ ] Add intro
|
||||
- [ ] Add link to contributing guide
|
||||
- [ ] Add barebones running zed from source instructions
|
||||
- [ ] Link out to further dev docs
|
||||
|
||||
# Zed
|
||||
|
||||
[](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
|
||||
|
@ -16,7 +9,11 @@ Welcome to Zed, a high-performance, multiplayer code editor from the creators of
|
|||
- [Building Zed](./docs/src/developing_zed__building_zed.md)
|
||||
- [Running Collaboration Locally](./docs/src/developing_zed__local_collaboration.md)
|
||||
|
||||
### Licensing
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
|
||||
|
||||
## Licensing
|
||||
|
||||
License information for third party dependencies must be correctly provided for CI to pass.
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,93 +0,0 @@
|
|||
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@ -79,5 +79,11 @@
|
|||
"cmd-1": "workspace::ToggleLeftDock",
|
||||
"cmd-6": "diagnostics::Deploy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"enter": "project_panel::Open"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use futures::future::BoxFuture;
|
||||
use gpui::AppContext;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -9,7 +10,14 @@ pub enum ProviderCredential {
|
|||
|
||||
pub trait CredentialProvider: Send + Sync {
|
||||
fn has_credentials(&self) -> bool;
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
|
||||
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
|
||||
fn delete_credentials(&self, cx: &mut AppContext);
|
||||
#[must_use]
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture<ProviderCredential>;
|
||||
#[must_use]
|
||||
fn save_credentials(
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
credential: ProviderCredential,
|
||||
) -> BoxFuture<()>;
|
||||
#[must_use]
|
||||
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()>;
|
||||
}
|
||||
|
|
|
@ -201,8 +201,10 @@ pub struct OpenAICompletionProvider {
|
|||
}
|
||||
|
||||
impl OpenAICompletionProvider {
|
||||
pub fn new(model_name: &str, executor: BackgroundExecutor) -> Self {
|
||||
let model = OpenAILanguageModel::load(model_name);
|
||||
pub async fn new(model_name: String, executor: BackgroundExecutor) -> Self {
|
||||
let model = executor
|
||||
.spawn(async move { OpenAILanguageModel::load(&model_name) })
|
||||
.await;
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
Self {
|
||||
model,
|
||||
|
@ -220,16 +222,19 @@ impl CredentialProvider for OpenAICompletionProvider {
|
|||
}
|
||||
}
|
||||
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture<ProviderCredential> {
|
||||
let existing_credential = self.credential.read().clone();
|
||||
let retrieved_credential = match existing_credential {
|
||||
ProviderCredential::Credentials { .. } => existing_credential.clone(),
|
||||
ProviderCredential::Credentials { .. } => {
|
||||
return async move { existing_credential }.boxed()
|
||||
}
|
||||
_ => {
|
||||
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||
ProviderCredential::Credentials { api_key }
|
||||
} else if let Some(Some((_, api_key))) =
|
||||
cx.read_credentials(OPENAI_API_URL).log_err()
|
||||
{
|
||||
async move { ProviderCredential::Credentials { api_key } }.boxed()
|
||||
} else {
|
||||
let credentials = cx.read_credentials(OPENAI_API_URL);
|
||||
async move {
|
||||
if let Some(Some((_, api_key))) = credentials.await.log_err() {
|
||||
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||
ProviderCredential::Credentials { api_key }
|
||||
} else {
|
||||
|
@ -239,26 +244,48 @@ impl CredentialProvider for OpenAICompletionProvider {
|
|||
ProviderCredential::NoCredentials
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async move {
|
||||
let retrieved_credential = retrieved_credential.await;
|
||||
*self.credential.write() = retrieved_credential.clone();
|
||||
retrieved_credential
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
|
||||
fn save_credentials(
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
credential: ProviderCredential,
|
||||
) -> BoxFuture<()> {
|
||||
*self.credential.write() = credential.clone();
|
||||
let credential = credential.clone();
|
||||
match credential {
|
||||
let write_credentials = match credential {
|
||||
ProviderCredential::Credentials { api_key } => {
|
||||
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
|
||||
.log_err();
|
||||
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
|
||||
}
|
||||
_ => {}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
async move {
|
||||
if let Some(write_credentials) = write_credentials {
|
||||
write_credentials.await.log_err();
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, cx: &mut AppContext) {
|
||||
cx.delete_credentials(OPENAI_API_URL).log_err();
|
||||
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
|
||||
*self.credential.write() = ProviderCredential::NoCredentials;
|
||||
let delete_credentials = cx.delete_credentials(OPENAI_API_URL);
|
||||
async move {
|
||||
delete_credentials.await.log_err();
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::AsyncReadExt;
|
||||
use futures::FutureExt;
|
||||
use gpui::AppContext;
|
||||
use gpui::BackgroundExecutor;
|
||||
use isahc::http::StatusCode;
|
||||
|
@ -67,11 +69,14 @@ struct OpenAIEmbeddingUsage {
|
|||
}
|
||||
|
||||
impl OpenAIEmbeddingProvider {
|
||||
pub fn new(client: Arc<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
|
||||
pub async fn new(client: Arc<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
|
||||
let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
|
||||
let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
|
||||
|
||||
let model = OpenAILanguageModel::load("text-embedding-ada-002");
|
||||
// Loading the model is expensive, so ensure this runs off the main thread.
|
||||
let model = executor
|
||||
.spawn(async move { OpenAILanguageModel::load("text-embedding-ada-002") })
|
||||
.await;
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
|
||||
OpenAIEmbeddingProvider {
|
||||
|
@ -154,17 +159,20 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
|
||||
let existing_credential = self.credential.read().clone();
|
||||
|
||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture<ProviderCredential> {
|
||||
let existing_credential = self.credential.read().clone();
|
||||
let retrieved_credential = match existing_credential {
|
||||
ProviderCredential::Credentials { .. } => existing_credential.clone(),
|
||||
ProviderCredential::Credentials { .. } => {
|
||||
return async move { existing_credential }.boxed()
|
||||
}
|
||||
_ => {
|
||||
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||
ProviderCredential::Credentials { api_key }
|
||||
} else if let Some(Some((_, api_key))) =
|
||||
cx.read_credentials(OPENAI_API_URL).log_err()
|
||||
{
|
||||
async move { ProviderCredential::Credentials { api_key } }.boxed()
|
||||
} else {
|
||||
let credentials = cx.read_credentials(OPENAI_API_URL);
|
||||
async move {
|
||||
if let Some(Some((_, api_key))) = credentials.await.log_err() {
|
||||
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||
ProviderCredential::Credentials { api_key }
|
||||
} else {
|
||||
|
@ -174,26 +182,48 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
|||
ProviderCredential::NoCredentials
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async move {
|
||||
let retrieved_credential = retrieved_credential.await;
|
||||
*self.credential.write() = retrieved_credential.clone();
|
||||
retrieved_credential
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
|
||||
fn save_credentials(
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
credential: ProviderCredential,
|
||||
) -> BoxFuture<()> {
|
||||
*self.credential.write() = credential.clone();
|
||||
match credential {
|
||||
let credential = credential.clone();
|
||||
let write_credentials = match credential {
|
||||
ProviderCredential::Credentials { api_key } => {
|
||||
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
|
||||
.log_err();
|
||||
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
|
||||
}
|
||||
_ => {}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
async move {
|
||||
if let Some(write_credentials) = write_credentials {
|
||||
write_credentials.await.log_err();
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, cx: &mut AppContext) {
|
||||
cx.delete_credentials(OPENAI_API_URL).log_err();
|
||||
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
|
||||
*self.credential.write() = ProviderCredential::NoCredentials;
|
||||
let delete_credentials = cx.delete_credentials(OPENAI_API_URL);
|
||||
async move {
|
||||
delete_credentials.await.log_err();
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,11 +104,22 @@ impl CredentialProvider for FakeEmbeddingProvider {
|
|||
fn has_credentials(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
|
||||
ProviderCredential::NotNeeded
|
||||
|
||||
fn retrieve_credentials(&self, _cx: &mut AppContext) -> BoxFuture<ProviderCredential> {
|
||||
async { ProviderCredential::NotNeeded }.boxed()
|
||||
}
|
||||
|
||||
fn save_credentials(
|
||||
&self,
|
||||
_cx: &mut AppContext,
|
||||
_credential: ProviderCredential,
|
||||
) -> BoxFuture<()> {
|
||||
async {}.boxed()
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, _cx: &mut AppContext) -> BoxFuture<()> {
|
||||
async {}.boxed()
|
||||
}
|
||||
fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
|
||||
fn delete_credentials(&self, _cx: &mut AppContext) {}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
@ -165,11 +176,22 @@ impl CredentialProvider for FakeCompletionProvider {
|
|||
fn has_credentials(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
|
||||
ProviderCredential::NotNeeded
|
||||
|
||||
fn retrieve_credentials(&self, _cx: &mut AppContext) -> BoxFuture<ProviderCredential> {
|
||||
async { ProviderCredential::NotNeeded }.boxed()
|
||||
}
|
||||
|
||||
fn save_credentials(
|
||||
&self,
|
||||
_cx: &mut AppContext,
|
||||
_credential: ProviderCredential,
|
||||
) -> BoxFuture<()> {
|
||||
async {}.boxed()
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, _cx: &mut AppContext) -> BoxFuture<()> {
|
||||
async {}.boxed()
|
||||
}
|
||||
fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
|
||||
fn delete_credentials(&self, _cx: &mut AppContext) {}
|
||||
}
|
||||
|
||||
impl CompletionProvider for FakeCompletionProvider {
|
||||
|
|
|
@ -6,14 +6,12 @@ use crate::{
|
|||
NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
|
||||
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
|
||||
};
|
||||
|
||||
use ai::prompts::repository_context::PromptCodeSnippet;
|
||||
use ai::{
|
||||
auth::ProviderCredential,
|
||||
completion::{CompletionProvider, CompletionRequest},
|
||||
providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
|
||||
};
|
||||
|
||||
use ai::prompts::repository_context::PromptCodeSnippet;
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Local};
|
||||
use client::telemetry::AssistantKind;
|
||||
|
@ -31,9 +29,9 @@ use fs::Fs;
|
|||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext,
|
||||
AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter, FocusHandle,
|
||||
FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model,
|
||||
ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
|
||||
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter,
|
||||
FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement,
|
||||
IntoElement, Model, ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
|
||||
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
|
@ -123,6 +121,10 @@ impl AssistantPanel {
|
|||
.await
|
||||
.log_err()
|
||||
.unwrap_or_default();
|
||||
// Defaulting currently to GPT4, allow for this to be set via config.
|
||||
let completion_provider =
|
||||
OpenAICompletionProvider::new("gpt-4".into(), cx.background_executor().clone())
|
||||
.await;
|
||||
|
||||
// TODO: deserialize state.
|
||||
let workspace_handle = workspace.clone();
|
||||
|
@ -156,11 +158,6 @@ impl AssistantPanel {
|
|||
});
|
||||
|
||||
let semantic_index = SemanticIndex::global(cx);
|
||||
// Defaulting currently to GPT4, allow for this to be set via config.
|
||||
let completion_provider = Arc::new(OpenAICompletionProvider::new(
|
||||
"gpt-4",
|
||||
cx.background_executor().clone(),
|
||||
));
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
cx.on_focus_in(&focus_handle, Self::focus_in).detach();
|
||||
|
@ -176,7 +173,7 @@ impl AssistantPanel {
|
|||
zoomed: false,
|
||||
focus_handle,
|
||||
toolbar,
|
||||
completion_provider,
|
||||
completion_provider: Arc::new(completion_provider),
|
||||
api_key_editor: None,
|
||||
languages: workspace.app_state().languages.clone(),
|
||||
fs: workspace.app_state().fs.clone(),
|
||||
|
@ -221,23 +218,9 @@ impl AssistantPanel {
|
|||
_: &InlineAssist,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let this = if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
|
||||
if this.update(cx, |assistant, cx| {
|
||||
if !assistant.has_credentials() {
|
||||
assistant.load_credentials(cx);
|
||||
};
|
||||
|
||||
assistant.has_credentials()
|
||||
}) {
|
||||
this
|
||||
} else {
|
||||
workspace.focus_panel::<AssistantPanel>(cx);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let active_editor = if let Some(active_editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
|
@ -246,12 +229,32 @@ impl AssistantPanel {
|
|||
} else {
|
||||
return;
|
||||
};
|
||||
let project = workspace.project().clone();
|
||||
|
||||
let project = workspace.project();
|
||||
|
||||
this.update(cx, |assistant, cx| {
|
||||
assistant.new_inline_assist(&active_editor, cx, project)
|
||||
if assistant.update(cx, |assistant, _| assistant.has_credentials()) {
|
||||
assistant.update(cx, |assistant, cx| {
|
||||
assistant.new_inline_assist(&active_editor, cx, &project)
|
||||
});
|
||||
} else {
|
||||
let assistant = assistant.downgrade();
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
assistant
|
||||
.update(&mut cx, |assistant, cx| assistant.load_credentials(cx))?
|
||||
.await;
|
||||
if assistant.update(&mut cx, |assistant, _| assistant.has_credentials())? {
|
||||
assistant.update(&mut cx, |assistant, cx| {
|
||||
assistant.new_inline_assist(&active_editor, cx, &project)
|
||||
})?;
|
||||
} else {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
workspace.focus_panel::<AssistantPanel>(cx)
|
||||
})?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn new_inline_assist(
|
||||
|
@ -291,9 +294,6 @@ impl AssistantPanel {
|
|||
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
|
||||
let provider = self.completion_provider.clone();
|
||||
|
||||
// Retrieve Credentials Authenticates the Provider
|
||||
provider.retrieve_credentials(cx);
|
||||
|
||||
let codegen = cx.new_model(|cx| {
|
||||
Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
|
||||
});
|
||||
|
@ -846,11 +846,18 @@ impl AssistantPanel {
|
|||
api_key: api_key.clone(),
|
||||
};
|
||||
|
||||
self.completion_provider.save_credentials(cx, credential);
|
||||
let completion_provider = self.completion_provider.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.update(|cx| completion_provider.save_credentials(cx, credential))?
|
||||
.await;
|
||||
|
||||
self.api_key_editor.take();
|
||||
self.focus_handle.focus(cx);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.api_key_editor.take();
|
||||
this.focus_handle.focus(cx);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
} else {
|
||||
cx.propagate();
|
||||
|
@ -858,10 +865,17 @@ impl AssistantPanel {
|
|||
}
|
||||
|
||||
fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
|
||||
self.completion_provider.delete_credentials(cx);
|
||||
self.api_key_editor = Some(build_api_key_editor(cx));
|
||||
self.focus_handle.focus(cx);
|
||||
let completion_provider = self.completion_provider.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.update(|cx| completion_provider.delete_credentials(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.api_key_editor = Some(build_api_key_editor(cx));
|
||||
this.focus_handle.focus(cx);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||
|
@ -1079,9 +1093,9 @@ impl AssistantPanel {
|
|||
cx.spawn(|this, mut cx| async move {
|
||||
let saved_conversation = fs.load(&path).await?;
|
||||
let saved_conversation = serde_json::from_str(&saved_conversation)?;
|
||||
let conversation = cx.new_model(|cx| {
|
||||
Conversation::deserialize(saved_conversation, path.clone(), languages, cx)
|
||||
})?;
|
||||
let conversation =
|
||||
Conversation::deserialize(saved_conversation, path.clone(), languages, &mut cx)
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
// If, by the time we've loaded the conversation, the user has already opened
|
||||
// the same conversation, we don't want to open it again.
|
||||
|
@ -1108,8 +1122,16 @@ impl AssistantPanel {
|
|||
self.completion_provider.has_credentials()
|
||||
}
|
||||
|
||||
fn load_credentials(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.completion_provider.retrieve_credentials(cx);
|
||||
fn load_credentials(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
|
||||
let completion_provider = self.completion_provider.clone();
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
if let Some(retrieve_credentials) = cx
|
||||
.update(|cx| completion_provider.retrieve_credentials(cx))
|
||||
.log_err()
|
||||
{
|
||||
retrieve_credentials.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1315,11 +1337,16 @@ impl Panel for AssistantPanel {
|
|||
|
||||
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
||||
if active {
|
||||
self.load_credentials(cx);
|
||||
|
||||
if self.editors.is_empty() {
|
||||
self.new_conversation(cx);
|
||||
let load_credentials = self.load_credentials(cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
load_credentials.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.editors.is_empty() {
|
||||
this.new_conversation(cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1462,21 +1489,25 @@ impl Conversation {
|
|||
}
|
||||
}
|
||||
|
||||
fn deserialize(
|
||||
async fn deserialize(
|
||||
saved_conversation: SavedConversation,
|
||||
path: PathBuf,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
let id = match saved_conversation.id {
|
||||
Some(id) => Some(id),
|
||||
None => Some(Uuid::new_v4().to_string()),
|
||||
};
|
||||
let model = saved_conversation.model;
|
||||
let completion_provider: Arc<dyn CompletionProvider> = Arc::new(
|
||||
OpenAICompletionProvider::new(model.full_name(), cx.background_executor().clone()),
|
||||
OpenAICompletionProvider::new(
|
||||
model.full_name().into(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
completion_provider.retrieve_credentials(cx);
|
||||
cx.update(|cx| completion_provider.retrieve_credentials(cx))?;
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
let mut message_anchors = Vec::new();
|
||||
let mut next_message_id = MessageId(0);
|
||||
|
@ -1499,8 +1530,9 @@ impl Conversation {
|
|||
})
|
||||
.detach_and_log_err(cx);
|
||||
buffer
|
||||
});
|
||||
})?;
|
||||
|
||||
cx.new_model(|cx| {
|
||||
let mut this = Self {
|
||||
id,
|
||||
message_anchors,
|
||||
|
@ -1525,6 +1557,7 @@ impl Conversation {
|
|||
};
|
||||
this.count_remaining_tokens(cx);
|
||||
this
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_buffer_event(
|
||||
|
@ -3169,7 +3202,7 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::MessageId;
|
||||
use ai::test::FakeCompletionProvider;
|
||||
use gpui::AppContext;
|
||||
use gpui::{AppContext, TestAppContext};
|
||||
use settings::SettingsStore;
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -3487,16 +3520,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_serialization(cx: &mut AppContext) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
async fn test_serialization(cx: &mut TestAppContext) {
|
||||
let settings_store = cx.update(SettingsStore::test);
|
||||
cx.set_global(settings_store);
|
||||
init(cx);
|
||||
cx.update(init);
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
let conversation =
|
||||
cx.new_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
let message_0 = conversation.read(cx).message_anchors[0].id;
|
||||
let buffer = conversation.read_with(cx, |conversation, _| conversation.buffer.clone());
|
||||
let message_0 =
|
||||
conversation.read_with(cx, |conversation, _| conversation.message_anchors[0].id);
|
||||
let message_1 = conversation.update(cx, |conversation, cx| {
|
||||
conversation
|
||||
.insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
|
||||
|
@ -3517,9 +3551,9 @@ mod tests {
|
|||
.unwrap()
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
||||
assert_eq!(buffer.read(cx).text(), "a\nb\nc\n");
|
||||
assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "a\nb\nc\n");
|
||||
assert_eq!(
|
||||
messages(&conversation, cx),
|
||||
cx.read(|cx| messages(&conversation, cx)),
|
||||
[
|
||||
(message_0, Role::User, 0..2),
|
||||
(message_1.id, Role::Assistant, 2..6),
|
||||
|
@ -3527,18 +3561,22 @@ mod tests {
|
|||
]
|
||||
);
|
||||
|
||||
let deserialized_conversation = cx.new_model(|cx| {
|
||||
Conversation::deserialize(
|
||||
conversation.read(cx).serialize(cx),
|
||||
let deserialized_conversation = Conversation::deserialize(
|
||||
conversation.read_with(cx, |conversation, cx| conversation.serialize(cx)),
|
||||
Default::default(),
|
||||
registry.clone(),
|
||||
cx,
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
});
|
||||
let deserialized_buffer = deserialized_conversation.read(cx).buffer.clone();
|
||||
assert_eq!(deserialized_buffer.read(cx).text(), "a\nb\nc\n");
|
||||
.await
|
||||
.unwrap();
|
||||
let deserialized_buffer =
|
||||
deserialized_conversation.read_with(cx, |conversation, _| conversation.buffer.clone());
|
||||
assert_eq!(
|
||||
messages(&deserialized_conversation, cx),
|
||||
deserialized_buffer.read_with(cx, |buffer, _| buffer.text()),
|
||||
"a\nb\nc\n"
|
||||
);
|
||||
assert_eq!(
|
||||
cx.read(|cx| messages(&deserialized_conversation, cx)),
|
||||
[
|
||||
(message_0, Role::User, 0..2),
|
||||
(message_1.id, Role::Assistant, 2..6),
|
||||
|
|
|
@ -700,8 +700,8 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
|
||||
read_credentials_from_keychain(cx).is_some()
|
||||
pub async fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
|
||||
read_credentials_from_keychain(cx).await.is_some()
|
||||
}
|
||||
|
||||
#[async_recursion(?Send)]
|
||||
|
@ -732,7 +732,7 @@ impl Client {
|
|||
let mut read_from_keychain = false;
|
||||
let mut credentials = self.state.read().credentials.clone();
|
||||
if credentials.is_none() && try_keychain {
|
||||
credentials = read_credentials_from_keychain(cx);
|
||||
credentials = read_credentials_from_keychain(cx).await;
|
||||
read_from_keychain = credentials.is_some();
|
||||
}
|
||||
if credentials.is_none() {
|
||||
|
@ -770,7 +770,7 @@ impl Client {
|
|||
Ok(conn) => {
|
||||
self.state.write().credentials = Some(credentials.clone());
|
||||
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
|
||||
write_credentials_to_keychain(credentials, cx).log_err();
|
||||
write_credentials_to_keychain(credentials, cx).await.log_err();
|
||||
}
|
||||
|
||||
futures::select_biased! {
|
||||
|
@ -784,7 +784,7 @@ impl Client {
|
|||
Err(EstablishConnectionError::Unauthorized) => {
|
||||
self.state.write().credentials.take();
|
||||
if read_from_keychain {
|
||||
delete_credentials_from_keychain(cx).log_err();
|
||||
delete_credentials_from_keychain(cx).await.log_err();
|
||||
self.set_status(Status::SignedOut, cx);
|
||||
self.authenticate_and_connect(false, cx).await
|
||||
} else {
|
||||
|
@ -1350,14 +1350,16 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
|
||||
async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
|
||||
if IMPERSONATE_LOGIN.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (user_id, access_token) = cx
|
||||
.update(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
|
||||
.ok()??;
|
||||
.update(|cx| cx.read_credentials(&ZED_SERVER_URL))
|
||||
.log_err()?
|
||||
.await
|
||||
.log_err()??;
|
||||
|
||||
Some(Credentials {
|
||||
user_id: user_id.parse().ok()?,
|
||||
|
@ -1365,7 +1367,10 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
|
|||
})
|
||||
}
|
||||
|
||||
fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext) -> Result<()> {
|
||||
async fn write_credentials_to_keychain(
|
||||
credentials: Credentials,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
cx.update(move |cx| {
|
||||
cx.write_credentials(
|
||||
&ZED_SERVER_URL,
|
||||
|
@ -1373,10 +1378,12 @@ fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext)
|
|||
credentials.access_token.as_bytes(),
|
||||
)
|
||||
})?
|
||||
.await
|
||||
}
|
||||
|
||||
fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
|
||||
async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
|
||||
cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
|
||||
.await
|
||||
}
|
||||
|
||||
const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";
|
||||
|
|
|
@ -126,6 +126,7 @@ const MAX_LINE_LEN: usize = 1024;
|
|||
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
|
||||
const MAX_SELECTION_HISTORY_LEN: usize = 1024;
|
||||
const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
|
||||
pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
|
||||
#[doc(hidden)]
|
||||
pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
|
||||
#[doc(hidden)]
|
||||
|
@ -369,7 +370,7 @@ pub struct Editor {
|
|||
collaboration_hub: Option<Box<dyn CollaborationHub>>,
|
||||
blink_manager: Model<BlinkManager>,
|
||||
show_cursor_names: bool,
|
||||
hovered_cursor: Option<HoveredCursor>,
|
||||
hovered_cursors: HashMap<HoveredCursor, Task<()>>,
|
||||
pub show_local_selections: bool,
|
||||
mode: EditorMode,
|
||||
show_gutter: bool,
|
||||
|
@ -463,6 +464,7 @@ enum SelectionHistoryMode {
|
|||
Redoing,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
struct HoveredCursor {
|
||||
replica_id: u16,
|
||||
selection_id: usize,
|
||||
|
@ -1440,7 +1442,7 @@ impl Editor {
|
|||
gutter_width: Default::default(),
|
||||
style: None,
|
||||
show_cursor_names: false,
|
||||
hovered_cursor: Default::default(),
|
||||
hovered_cursors: Default::default(),
|
||||
editor_actions: Default::default(),
|
||||
show_copilot_suggestions: mode == EditorMode::Full,
|
||||
_subscriptions: vec![
|
||||
|
@ -3741,7 +3743,7 @@ impl Editor {
|
|||
self.show_cursor_names = true;
|
||||
cx.notify();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor().timer(Duration::from_secs(3)).await;
|
||||
cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.show_cursor_names = false;
|
||||
cx.notify()
|
||||
|
|
|
@ -17,8 +17,8 @@ use crate::{
|
|||
mouse_context_menu,
|
||||
scroll::scroll_amount::ScrollAmount,
|
||||
CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
|
||||
HalfPageDown, HalfPageUp, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase,
|
||||
Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
|
||||
HalfPageDown, HalfPageUp, HoveredCursor, LineDown, LineUp, OpenExcerpts, PageDown, PageUp,
|
||||
Point, SelectPhase, Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
|
@ -612,13 +612,24 @@ impl EditorElement {
|
|||
.anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
|
||||
|
||||
let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
|
||||
editor.hovered_cursor.take();
|
||||
return;
|
||||
};
|
||||
editor.hovered_cursor.replace(crate::HoveredCursor {
|
||||
let key = crate::HoveredCursor {
|
||||
replica_id: selection.replica_id,
|
||||
selection_id: selection.selection.id,
|
||||
});
|
||||
};
|
||||
editor.hovered_cursors.insert(
|
||||
key.clone(),
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
editor.hovered_cursors.remove(&key);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
|
@ -1986,7 +1997,9 @@ impl EditorElement {
|
|||
if Some(selection.peer_id) == editor.leader_peer_id {
|
||||
continue;
|
||||
}
|
||||
let is_shown = editor.show_cursor_names || editor.hovered_cursor.as_ref().is_some_and(|c| c.replica_id == selection.replica_id && c.selection_id == selection.selection.id);
|
||||
let key = HoveredCursor{replica_id: selection.replica_id, selection_id: selection.selection.id};
|
||||
|
||||
let is_shown = editor.show_cursor_names || editor.hovered_cursors.contains_key(&key);
|
||||
|
||||
remote_selections
|
||||
.entry(selection.replica_id)
|
||||
|
|
|
@ -1,3 +1,40 @@
|
|||
# GPUI
|
||||
# Welcome to GPUI!
|
||||
|
||||
A fast, productive UI framework from the creators of [https://zed.dev].
|
||||
GPUI is a hybrid immediate and retained mode, GPU accelerated, UI framework
|
||||
for Rust, designed to support a wide variety of applications.
|
||||
|
||||
## Getting Started
|
||||
|
||||
GPUI is still in active development as we work on the Zed code editor and isn't yet on crates.io. You'll also need to use the latest version of stable rust and be on macOS. Add the following to your Cargo.toml:
|
||||
|
||||
```
|
||||
gpui = { git = "https://github.com/zed-industries/zed" }
|
||||
```
|
||||
|
||||
Everything in GPUI starts with an `App`. You can create one with `App::new()`, and kick off your application by passing a callback to `App::run()`. Inside this callback, you can create a new window with `AppContext::open_window()`, and register your first root view. See [gpui.rs](https://www.gpui.rs/) for a complete example.
|
||||
|
||||
## The Big Picture
|
||||
|
||||
GPUI offers three different registers(https://en.wikipedia.org/wiki/Register_(sociolinguistics)) depending on your needs:
|
||||
|
||||
- State management and communication with Models. Whenever you need to store application state that communicates between different parts of your application, you'll want to use GPUI's models. Models are owned by GPUI and are only accessible through an owned smart pointer similar to an `Rc`. See the `app::model_context` module for more information.
|
||||
|
||||
- High level, declarative UI with Views. All UI in GPUI starts with a View. A view is simply a model that can be rendered, via the `Render` trait. At the start of each frame, GPUI will call this render method on the root view of a given window. Views build a tree of `elements`, lay them out and style them with a tailwind-style API, and then give them to GPUI to turn into pixels. See the `div` element for an all purpose swiss-army knife of rendering.
|
||||
|
||||
- Low level, imperative UI with Elements. Elements are the building blocks of UI in GPUI, and they provide a nice wrapper around an imperative API that provides as much flexibility and control as you need. Elements have total control over how they and their child elements are rendered and and can be used for making efficient views into large lists, implement custom layouting for a code editor, and anything else you can think of. See the `element` module for more information.
|
||||
|
||||
Each of these registers has one or more corresponding contexts that can be accessed from all GPUI services. This context is your main interface to GPUI, and is used extensively throughout the framework.
|
||||
|
||||
## Other Resources
|
||||
|
||||
In addition to the systems above, GPUI provides a range of smaller services that are useful for building complex applications:
|
||||
|
||||
- Actions are user-defined structs that are used for converting keystrokes into logical operations in your UI. Use this for implementing keyboard shortcuts, such as cmd-q. See the `action` module for more information.
|
||||
|
||||
- Platform services, such as `quit the app` or `open a URL` are available as methods on the `app::AppContext`.
|
||||
|
||||
- An async executor that is integrated with the platform's event loop. See the `executor` module for more information.,
|
||||
|
||||
- The `[gpui::test]` macro provides a convenient way to write tests for your GPUI applications. Tests also have their own kind of context, a `TestAppContext` which provides ways of simulating common platform input. See `app::test_context` and `test` modules for more details.
|
||||
|
||||
Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop a question in the [Zed Discord](https://discord.gg/U4qhCEhMXP). We're working on improving the documentation, creating more examples, and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog).
|
||||
|
|
|
@ -523,17 +523,22 @@ impl AppContext {
|
|||
}
|
||||
|
||||
/// Writes credentials to the platform keychain.
|
||||
pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
|
||||
pub fn write_credentials(
|
||||
&self,
|
||||
url: &str,
|
||||
username: &str,
|
||||
password: &[u8],
|
||||
) -> Task<Result<()>> {
|
||||
self.platform.write_credentials(url, username, password)
|
||||
}
|
||||
|
||||
/// Reads credentials from the platform keychain.
|
||||
pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
|
||||
pub fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||
self.platform.read_credentials(url)
|
||||
}
|
||||
|
||||
/// Deletes credentials from the platform keychain.
|
||||
pub fn delete_credentials(&self, url: &str) -> Result<()> {
|
||||
pub fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
|
||||
self.platform.delete_credentials(url)
|
||||
}
|
||||
|
||||
|
|
|
@ -154,6 +154,7 @@ impl AsyncAppContext {
|
|||
}
|
||||
|
||||
/// Reads the global state of the specified type, passing it to the given callback.
|
||||
///
|
||||
/// Panics if no global state of the specified type has been assigned.
|
||||
/// Returns an error if the `AppContext` has been dropped.
|
||||
pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result<R> {
|
||||
|
@ -166,7 +167,10 @@ impl AsyncAppContext {
|
|||
}
|
||||
|
||||
/// Reads the global state of the specified type, passing it to the given callback.
|
||||
/// Similar to [read_global], but returns an error instead of panicking if no state of the specified type has been assigned.
|
||||
///
|
||||
/// Similar to [`AsyncAppContext::read_global`], but returns an error instead of panicking
|
||||
/// if no state of the specified type has been assigned.
|
||||
///
|
||||
/// Returns an error if no state of the specified type has been assigned the `AppContext` has been dropped.
|
||||
pub fn try_read_global<G: 'static, R>(
|
||||
&self,
|
||||
|
@ -212,12 +216,12 @@ impl AsyncWindowContext {
|
|||
self.window
|
||||
}
|
||||
|
||||
/// A convenience method for [WindowContext::update()]
|
||||
/// A convenience method for [`AppContext::update_window`].
|
||||
pub fn update<R>(&mut self, update: impl FnOnce(&mut WindowContext) -> R) -> Result<R> {
|
||||
self.app.update_window(self.window, |_, cx| update(cx))
|
||||
}
|
||||
|
||||
/// A convenience method for [WindowContext::update()]
|
||||
/// A convenience method for [`AppContext::update_window`].
|
||||
pub fn update_root<R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(AnyView, &mut WindowContext) -> R,
|
||||
|
@ -225,12 +229,12 @@ impl AsyncWindowContext {
|
|||
self.app.update_window(self.window, update)
|
||||
}
|
||||
|
||||
/// A convenience method for [WindowContext::on_next_frame()]
|
||||
/// A convenience method for [`WindowContext::on_next_frame`].
|
||||
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
|
||||
self.window.update(self, |_, cx| cx.on_next_frame(f)).ok();
|
||||
}
|
||||
|
||||
/// A convenience method for [AppContext::global()]
|
||||
/// A convenience method for [`AppContext::global`].
|
||||
pub fn read_global<G: 'static, R>(
|
||||
&mut self,
|
||||
read: impl FnOnce(&G, &WindowContext) -> R,
|
||||
|
@ -238,7 +242,7 @@ impl AsyncWindowContext {
|
|||
self.window.update(self, |_, cx| read(cx.global(), cx))
|
||||
}
|
||||
|
||||
/// A convenience method for [AppContext::update_global()]
|
||||
/// A convenience method for [`AppContext::update_global`].
|
||||
/// for updating the global state of the specified type.
|
||||
pub fn update_global<G, R>(
|
||||
&mut self,
|
||||
|
|
|
@ -42,7 +42,8 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
|||
self.model_state.clone()
|
||||
}
|
||||
|
||||
/// Arranges for the given function to be called whenever [ModelContext::notify] or [ViewContext::notify] is called with the given model or view.
|
||||
/// Arranges for the given function to be called whenever [`ModelContext::notify`] or
|
||||
/// [`ViewContext::notify`](crate::ViewContext::notify) is called with the given model or view.
|
||||
pub fn observe<W, E>(
|
||||
&mut self,
|
||||
entity: &E,
|
||||
|
@ -150,7 +151,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
|||
}
|
||||
|
||||
/// Arrange for the given function to be invoked whenever the application is quit.
|
||||
/// The future returned from this callback will be polled for up to [gpui::SHUTDOWN_TIMEOUT] until the app fully quits.
|
||||
/// The future returned from this callback will be polled for up to [crate::SHUTDOWN_TIMEOUT] until the app fully quits.
|
||||
pub fn on_app_quit<Fut>(
|
||||
&mut self,
|
||||
mut on_quit: impl FnMut(&mut T, &mut ModelContext<T>) -> Fut + 'static,
|
||||
|
|
|
@ -44,6 +44,14 @@ impl Arena {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.offset as usize - self.start as usize
|
||||
}
|
||||
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.end as usize - self.start as usize
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.valid.set(false);
|
||||
self.valid = Rc::new(Cell::new(true));
|
||||
|
|
|
@ -3,11 +3,11 @@ use serde::de::{self, Deserialize, Deserializer, Visitor};
|
|||
use std::fmt;
|
||||
|
||||
/// Convert an RGB hex color code number to a color type
|
||||
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
|
||||
pub fn rgb(hex: u32) -> Rgba {
|
||||
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
|
||||
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
|
||||
let b = (hex & 0xFF) as f32 / 255.0;
|
||||
Rgba { r, g, b, a: 1.0 }.into()
|
||||
Rgba { r, g, b, a: 1.0 }
|
||||
}
|
||||
|
||||
/// Convert an RGBA hex color code number to [`Rgba`]
|
||||
|
@ -40,7 +40,6 @@ impl fmt::Debug for Rgba {
|
|||
|
||||
impl Rgba {
|
||||
/// Create a new [`Rgba`] color by blending this and another color together
|
||||
/// TODO!(docs): find the source for this algorithm
|
||||
pub fn blend(&self, other: Rgba) -> Self {
|
||||
if other.a >= 1.0 {
|
||||
other
|
||||
|
|
|
@ -136,6 +136,25 @@ impl Render for () {
|
|||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {}
|
||||
}
|
||||
|
||||
/// A quick way to create a [`Render`]able view without having to define a new type.
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct TestView(Box<dyn FnMut(&mut ViewContext<TestView>) -> AnyElement>);
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl TestView {
|
||||
/// Construct a TestView from a render closure.
|
||||
pub fn new<F: FnMut(&mut ViewContext<TestView>) -> AnyElement + 'static>(f: F) -> Self {
|
||||
Self(Box::new(f))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl Render for TestView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
(self.0)(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// You can derive [`IntoElement`] on any type that implements this trait.
|
||||
/// It is used to construct reusable `components` out of plain data. Think of
|
||||
/// components as a recipe for a certain pattern of elements. RenderOnce allows
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Div is the central, reusable element that most GPUI trees will be built from.
|
||||
//! It functions as a container for other elements, and provides a number of
|
||||
//! useful features for laying out and styling its children as well as binding
|
||||
//! mouse events and action handlers. It is meant to be similar to the HTML <div>
|
||||
//! mouse events and action handlers. It is meant to be similar to the HTML `<div>`
|
||||
//! element, but for GPUI.
|
||||
//!
|
||||
//! # Build your own div
|
||||
|
@ -14,13 +14,6 @@
|
|||
//! as several associated traits. Together, these provide the full suite of Dom-like events
|
||||
//! and Tailwind-like styling that you can use to build your own custom elements. Div is
|
||||
//! constructed by combining these two systems into an all-in-one element.
|
||||
//!
|
||||
//! # Capturing and bubbling
|
||||
//!
|
||||
//! Note that while event dispatch in GPUI uses similar names and concepts to the web
|
||||
//! even API, the details are very different. See the documentation in [TODO!(docs)
|
||||
//! DOCUMENT EVENT DISPATCH SOMEWHERE IN WINDOW CONTEXT] for more details
|
||||
//!
|
||||
|
||||
use crate::{
|
||||
point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
|
||||
|
@ -85,7 +78,7 @@ impl Interactivity {
|
|||
/// Bind the given callback to the mouse down event for the given mouse button, during the bubble phase
|
||||
/// The imperative API equivalent of [`InteractiveElement::on_mouse_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to the view state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to the view state from this callback.
|
||||
pub fn on_mouse_down(
|
||||
&mut self,
|
||||
button: MouseButton,
|
||||
|
@ -105,7 +98,7 @@ impl Interactivity {
|
|||
/// Bind the given callback to the mouse down event for any button, during the capture phase
|
||||
/// The imperative API equivalent of [`InteractiveElement::capture_any_mouse_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn capture_any_mouse_down(
|
||||
&mut self,
|
||||
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -119,9 +112,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse down event for any button, during the bubble phase
|
||||
/// the imperative API equivalent to [`InteractiveElement::on_any_mouse_down()`]
|
||||
/// the imperative API equivalent to [`InteractiveElement::on_any_mouse_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_any_mouse_down(
|
||||
&mut self,
|
||||
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -135,9 +128,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse up event for the given button, during the bubble phase
|
||||
/// the imperative API equivalent to [`InteractiveElement::on_mouse_up()`]
|
||||
/// the imperative API equivalent to [`InteractiveElement::on_mouse_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_mouse_up(
|
||||
&mut self,
|
||||
button: MouseButton,
|
||||
|
@ -155,9 +148,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse up event for any button, during the capture phase
|
||||
/// the imperative API equivalent to [`InteractiveElement::capture_any_mouse_up()`]
|
||||
/// the imperative API equivalent to [`InteractiveElement::capture_any_mouse_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn capture_any_mouse_up(
|
||||
&mut self,
|
||||
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
|
||||
|
@ -171,9 +164,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse up event for any button, during the bubble phase
|
||||
/// the imperative API equivalent to [`InteractiveElement::on_any_mouse_up()`]
|
||||
/// the imperative API equivalent to [`Interactivity::on_any_mouse_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_any_mouse_up(
|
||||
&mut self,
|
||||
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
|
||||
|
@ -188,9 +181,9 @@ impl Interactivity {
|
|||
|
||||
/// Bind the given callback to the mouse down event, on any button, during the capture phase,
|
||||
/// when the mouse is outside of the bounds of this element.
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_mouse_down_out()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_mouse_down_out`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_mouse_down_out(
|
||||
&mut self,
|
||||
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -206,9 +199,9 @@ impl Interactivity {
|
|||
|
||||
/// Bind the given callback to the mouse up event, for the given button, during the capture phase,
|
||||
/// when the mouse is outside of the bounds of this element.
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_mouse_up_out()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_mouse_up_out`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_mouse_up_out(
|
||||
&mut self,
|
||||
button: MouseButton,
|
||||
|
@ -226,9 +219,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse move event, during the bubble phase
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_mouse_move()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_mouse_move`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_mouse_move(
|
||||
&mut self,
|
||||
listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
|
||||
|
@ -245,9 +238,9 @@ impl Interactivity {
|
|||
/// will be called for all move events, inside or outside of this element, as long as the
|
||||
/// drag was started with this element under the mouse. Useful for implementing draggable
|
||||
/// UIs that don't conform to a drag and drop style interaction, like resizing.
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_drag_move()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_drag_move`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_drag_move<T>(
|
||||
&mut self,
|
||||
listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
|
||||
|
@ -275,9 +268,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to scroll wheel events during the bubble phase
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_scroll_wheel()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_scroll_wheel`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_scroll_wheel(
|
||||
&mut self,
|
||||
listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
|
||||
|
@ -291,9 +284,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to an action dispatch during the capture phase
|
||||
/// The imperative API equivalent to [`InteractiveElement::capture_action()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::capture_action`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn capture_action<A: Action>(
|
||||
&mut self,
|
||||
listener: impl Fn(&A, &mut WindowContext) + 'static,
|
||||
|
@ -310,9 +303,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to an action dispatch during the bubble phase
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_action()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_action`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
|
||||
self.action_listeners.push((
|
||||
TypeId::of::<A>(),
|
||||
|
@ -328,9 +321,9 @@ impl Interactivity {
|
|||
/// Bind the given callback to an action dispatch, based on a dynamic action parameter
|
||||
/// instead of a type parameter. Useful for component libraries that want to expose
|
||||
/// action bindings to their users.
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_boxed_action()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_boxed_action`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_boxed_action(
|
||||
&mut self,
|
||||
action: &dyn Action,
|
||||
|
@ -348,9 +341,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to key down events during the bubble phase
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_key_down()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_key_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_key_down(&mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static) {
|
||||
self.key_down_listeners
|
||||
.push(Box::new(move |event, phase, cx| {
|
||||
|
@ -361,9 +354,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to key down events during the capture phase
|
||||
/// The imperative API equivalent to [`InteractiveElement::capture_key_down()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::capture_key_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn capture_key_down(
|
||||
&mut self,
|
||||
listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -377,9 +370,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to key up events during the bubble phase
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_key_up()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_key_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) {
|
||||
self.key_up_listeners
|
||||
.push(Box::new(move |event, phase, cx| {
|
||||
|
@ -390,9 +383,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to key up events during the capture phase
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_key_up()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_key_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn capture_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) {
|
||||
self.key_up_listeners
|
||||
.push(Box::new(move |event, phase, cx| {
|
||||
|
@ -403,9 +396,9 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Bind the given callback to drop events of the given type, whether or not the drag started on this element
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_drop()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_drop`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) {
|
||||
self.drop_listeners.push((
|
||||
TypeId::of::<T>(),
|
||||
|
@ -416,15 +409,15 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Use the given predicate to determine whether or not a drop event should be dispatched to this element
|
||||
/// The imperative API equivalent to [`InteractiveElement::can_drop()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::can_drop`]
|
||||
pub fn can_drop(&mut self, predicate: impl Fn(&dyn Any, &mut WindowContext) -> bool + 'static) {
|
||||
self.can_drop_predicate = Some(Box::new(predicate));
|
||||
}
|
||||
|
||||
/// Bind the given callback to click events of this element
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_click()`]
|
||||
/// The imperative API equivalent to [`StatefulInteractiveElement::on_click`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static)
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -435,10 +428,10 @@ impl Interactivity {
|
|||
|
||||
/// On drag initiation, this callback will be used to create a new view to render the dragged value for a
|
||||
/// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
|
||||
/// the [`Self::on_drag_move()`] API
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_drag()`]
|
||||
/// the [`Self::on_drag_move`] API
|
||||
/// The imperative API equivalent to [`StatefulInteractiveElement::on_drag`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_drag<T, W>(
|
||||
&mut self,
|
||||
value: T,
|
||||
|
@ -460,9 +453,9 @@ impl Interactivity {
|
|||
|
||||
/// Bind the given callback on the hover start and end events of this element. Note that the boolean
|
||||
/// passed to the callback is true when the hover starts and false when it ends.
|
||||
/// The imperative API equivalent to [`InteractiveElement::on_drag()`]
|
||||
/// The imperative API equivalent to [`StatefulInteractiveElement::on_drag`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static)
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -475,7 +468,7 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
|
||||
/// The imperative API equivalent to [`InteractiveElement::tooltip()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::tooltip`]
|
||||
pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static)
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -488,7 +481,7 @@ impl Interactivity {
|
|||
}
|
||||
|
||||
/// Block the mouse from interacting with this element or any of it's children
|
||||
/// The imperative API equivalent to [`InteractiveElement::block_mouse()`]
|
||||
/// The imperative API equivalent to [`InteractiveElement::block_mouse`]
|
||||
pub fn block_mouse(&mut self) {
|
||||
self.block_mouse = true;
|
||||
}
|
||||
|
@ -559,9 +552,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse down event for the given mouse button,
|
||||
/// the fluent API equivalent to [`Interactivity::on_mouse_down()`]
|
||||
/// the fluent API equivalent to [`Interactivity::on_mouse_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to the view state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to the view state from this callback.
|
||||
fn on_mouse_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
|
@ -573,7 +566,7 @@ pub trait InteractiveElement: Sized {
|
|||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
/// Set a key that can be used to look up this element's bounds
|
||||
/// in the [`VisualTestContext::debug_bounds()`] map
|
||||
/// in the [`VisualTestContext::debug_bounds`] map
|
||||
/// This is a noop in release builds
|
||||
fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
|
||||
self.interactivity().debug_selector = Some(f());
|
||||
|
@ -582,7 +575,7 @@ pub trait InteractiveElement: Sized {
|
|||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
/// Set a key that can be used to look up this element's bounds
|
||||
/// in the [`VisualTestContext::debug_bounds()`] map
|
||||
/// in the [`VisualTestContext::debug_bounds`] map
|
||||
/// This is a noop in release builds
|
||||
#[inline]
|
||||
fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
|
||||
|
@ -590,9 +583,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse down event for any button, during the capture phase
|
||||
/// the fluent API equivalent to [`Interactivity::capture_any_mouse_down()`]
|
||||
/// the fluent API equivalent to [`Interactivity::capture_any_mouse_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn capture_any_mouse_down(
|
||||
mut self,
|
||||
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -602,9 +595,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse down event for any button, during the capture phase
|
||||
/// the fluent API equivalent to [`Interactivity::on_any_mouse_down()`]
|
||||
/// the fluent API equivalent to [`Interactivity::on_any_mouse_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_any_mouse_down(
|
||||
mut self,
|
||||
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -614,9 +607,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse up event for the given button, during the bubble phase
|
||||
/// the fluent API equivalent to [`Interactivity::on_mouse_up()`]
|
||||
/// the fluent API equivalent to [`Interactivity::on_mouse_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_mouse_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
|
@ -627,9 +620,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse up event for any button, during the capture phase
|
||||
/// the fluent API equivalent to [`Interactivity::capture_any_mouse_up()`]
|
||||
/// the fluent API equivalent to [`Interactivity::capture_any_mouse_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn capture_any_mouse_up(
|
||||
mut self,
|
||||
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
|
||||
|
@ -640,9 +633,9 @@ pub trait InteractiveElement: Sized {
|
|||
|
||||
/// Bind the given callback to the mouse down event, on any button, during the capture phase,
|
||||
/// when the mouse is outside of the bounds of this element.
|
||||
/// The fluent API equivalent to [`Interactivity::on_mouse_down_out()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_mouse_down_out`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_mouse_down_out(
|
||||
mut self,
|
||||
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -653,9 +646,9 @@ pub trait InteractiveElement: Sized {
|
|||
|
||||
/// Bind the given callback to the mouse up event, for the given button, during the capture phase,
|
||||
/// when the mouse is outside of the bounds of this element.
|
||||
/// The fluent API equivalent to [`Interactivity::on_mouse_up_out()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_mouse_up_out`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_mouse_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
|
@ -666,9 +659,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to the mouse move event, during the bubble phase
|
||||
/// The fluent API equivalent to [`Interactivity::on_mouse_move()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_mouse_move`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_mouse_move(
|
||||
mut self,
|
||||
listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
|
||||
|
@ -681,9 +674,9 @@ pub trait InteractiveElement: Sized {
|
|||
/// will be called for all move events, inside or outside of this element, as long as the
|
||||
/// drag was started with this element under the mouse. Useful for implementing draggable
|
||||
/// UIs that don't conform to a drag and drop style interaction, like resizing.
|
||||
/// The fluent API equivalent to [`Interactivity::on_drag_move()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_drag_move`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_drag_move<T: 'static>(
|
||||
mut self,
|
||||
listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
|
||||
|
@ -696,9 +689,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to scroll wheel events during the bubble phase
|
||||
/// The fluent API equivalent to [`Interactivity::on_scroll_wheel()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_scroll_wheel`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_scroll_wheel(
|
||||
mut self,
|
||||
listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
|
||||
|
@ -708,9 +701,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Capture the given action, before normal action dispatch can fire
|
||||
/// The fluent API equivalent to [`Interactivity::on_scroll_wheel()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_scroll_wheel`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn capture_action<A: Action>(
|
||||
mut self,
|
||||
listener: impl Fn(&A, &mut WindowContext) + 'static,
|
||||
|
@ -720,9 +713,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to an action dispatch during the bubble phase
|
||||
/// The fluent API equivalent to [`Interactivity::on_action()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_action`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
|
||||
self.interactivity().on_action(listener);
|
||||
self
|
||||
|
@ -731,9 +724,9 @@ pub trait InteractiveElement: Sized {
|
|||
/// Bind the given callback to an action dispatch, based on a dynamic action parameter
|
||||
/// instead of a type parameter. Useful for component libraries that want to expose
|
||||
/// action bindings to their users.
|
||||
/// The fluent API equivalent to [`Interactivity::on_boxed_action()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_boxed_action`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_boxed_action(
|
||||
mut self,
|
||||
action: &dyn Action,
|
||||
|
@ -744,9 +737,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to key down events during the bubble phase
|
||||
/// The fluent API equivalent to [`Interactivity::on_key_down()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_key_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_key_down(
|
||||
mut self,
|
||||
listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -756,9 +749,9 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to key down events during the capture phase
|
||||
/// The fluent API equivalent to [`Interactivity::capture_key_down()`]
|
||||
/// The fluent API equivalent to [`Interactivity::capture_key_down`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn capture_key_down(
|
||||
mut self,
|
||||
listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
|
||||
|
@ -768,18 +761,18 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to key up events during the bubble phase
|
||||
/// The fluent API equivalent to [`Interactivity::on_key_up()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_key_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self {
|
||||
self.interactivity().on_key_up(listener);
|
||||
self
|
||||
}
|
||||
|
||||
/// Bind the given callback to key up events during the capture phase
|
||||
/// The fluent API equivalent to [`Interactivity::capture_key_up()`]
|
||||
/// The fluent API equivalent to [`Interactivity::capture_key_up`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn capture_key_up(
|
||||
mut self,
|
||||
listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static,
|
||||
|
@ -813,16 +806,16 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Bind the given callback to drop events of the given type, whether or not the drag started on this element
|
||||
/// The fluent API equivalent to [`Interactivity::on_drop()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_drop`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_drop<T: 'static>(mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) -> Self {
|
||||
self.interactivity().on_drop(listener);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use the given predicate to determine whether or not a drop event should be dispatched to this element
|
||||
/// The fluent API equivalent to [`Interactivity::can_drop()`]
|
||||
/// The fluent API equivalent to [`Interactivity::can_drop`]
|
||||
fn can_drop(
|
||||
mut self,
|
||||
predicate: impl Fn(&dyn Any, &mut WindowContext) -> bool + 'static,
|
||||
|
@ -832,7 +825,7 @@ pub trait InteractiveElement: Sized {
|
|||
}
|
||||
|
||||
/// Block the mouse from interacting with this element or any of it's children
|
||||
/// The fluent API equivalent to [`Interactivity::block_mouse()`]
|
||||
/// The fluent API equivalent to [`Interactivity::block_mouse`]
|
||||
fn block_mouse(mut self) -> Self {
|
||||
self.interactivity().block_mouse();
|
||||
self
|
||||
|
@ -899,9 +892,9 @@ pub trait StatefulInteractiveElement: InteractiveElement {
|
|||
}
|
||||
|
||||
/// Bind the given callback to click events of this element
|
||||
/// The fluent API equivalent to [`Interactivity::on_click()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_click`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -912,10 +905,10 @@ pub trait StatefulInteractiveElement: InteractiveElement {
|
|||
|
||||
/// On drag initiation, this callback will be used to create a new view to render the dragged value for a
|
||||
/// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
|
||||
/// the [`Self::on_drag_move()`] API
|
||||
/// The fluent API equivalent to [`Interactivity::on_drag()`]
|
||||
/// the [`Self::on_drag_move`] API
|
||||
/// The fluent API equivalent to [`Interactivity::on_drag`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_drag<T, W>(
|
||||
mut self,
|
||||
value: T,
|
||||
|
@ -932,9 +925,9 @@ pub trait StatefulInteractiveElement: InteractiveElement {
|
|||
|
||||
/// Bind the given callback on the hover start and end events of this element. Note that the boolean
|
||||
/// passed to the callback is true when the hover starts and false when it ends.
|
||||
/// The fluent API equivalent to [`Interactivity::on_hover()`]
|
||||
/// The fluent API equivalent to [`Interactivity::on_hover`]
|
||||
///
|
||||
/// See [`ViewContext::listener()`] to get access to a view's state from this callback
|
||||
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
|
||||
fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -944,7 +937,7 @@ pub trait StatefulInteractiveElement: InteractiveElement {
|
|||
}
|
||||
|
||||
/// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
|
||||
/// The fluent API equivalent to [`Interactivity::tooltip()`]
|
||||
/// The fluent API equivalent to [`Interactivity::tooltip`]
|
||||
fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
|
|
|
@ -104,7 +104,7 @@ impl Element for Img {
|
|||
cx.with_z_index(1, |cx| {
|
||||
match source {
|
||||
ImageSource::Uri(uri) => {
|
||||
let image_future = cx.image_cache.get(uri.clone());
|
||||
let image_future = cx.image_cache.get(uri.clone(), cx);
|
||||
if let Some(data) = image_future
|
||||
.clone()
|
||||
.now_or_never()
|
||||
|
|
|
@ -46,6 +46,18 @@ impl IntoElement for &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoElement for String {
|
||||
type Element = SharedString;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for SharedString {
|
||||
type State = TextState;
|
||||
|
||||
|
|
|
@ -1,30 +1,64 @@
|
|||
//! # Welcome to GPUI!
|
||||
//!
|
||||
//! GPUI is a hybrid immediate and retained mode, GPU accelerated, UI framework
|
||||
//! for Rust, designed to support a wide variety of applications. GPUI is currently
|
||||
//! being actively developed and improved for the [Zed code editor](https://zed.dev/), and new versions
|
||||
//! will have breaking changes. You'll probably need to use the latest stable version
|
||||
//! of rust to use GPUI.
|
||||
//! for Rust, designed to support a wide variety of applications.
|
||||
//!
|
||||
//! # Getting started with GPUI
|
||||
//! ## Getting Started
|
||||
//!
|
||||
//! TODO!(docs): Write a code sample showing how to create a window and render a simple
|
||||
//! div
|
||||
//! GPUI is still in active development as we work on the Zed code editor and isn't yet on crates.io.
|
||||
//! You'll also need to use the latest version of stable rust and be on macOS. Add the following to your
|
||||
//! Cargo.toml:
|
||||
//!
|
||||
//! # Drawing interesting things
|
||||
//! ```
|
||||
//! gpui = { git = "https://github.com/zed-industries/zed" }
|
||||
//! ```
|
||||
//!
|
||||
//! TODO!(docs): Expand demo to show how to draw a more interesting scene, with
|
||||
//! a counter to store state and a button to increment it.
|
||||
//! Everything in GPUI starts with an [`App`]. You can create one with [`App::new`], and
|
||||
//! kick off your application by passing a callback to [`App::run`]. Inside this callback,
|
||||
//! you can create a new window with [`AppContext::open_window`], and register your first root
|
||||
//! view. See [gpui.rs](https://www.gpui.rs/) for a complete example.
|
||||
//!
|
||||
//! # Interacting with your application state
|
||||
//! ## The Big Picture
|
||||
//!
|
||||
//! TODO!(docs): Expand demo to show GPUI entity interactions, like subscriptions and entities
|
||||
//! maybe make a network request to show async stuff?
|
||||
//! GPUI offers three different [registers](https://en.wikipedia.org/wiki/Register_(sociolinguistics)) depending on your needs:
|
||||
//!
|
||||
//! # Conclusion
|
||||
//! - State management and communication with Models. Whenever you need to store application state
|
||||
//! that communicates between different parts of your application, you'll want to use GPUI's
|
||||
//! models. Models are owned by GPUI and are only accessible through an owned smart pointer
|
||||
//! similar to an [`Rc`]. See the [`app::model_context`] module for more information.
|
||||
//!
|
||||
//! TODO!(docs): Wrap up with a conclusion and links to other places? Zed / GPUI website?
|
||||
//! Discord for chatting about it? Other tutorials or references?
|
||||
//! - High level, declarative UI with Views. All UI in GPUI starts with a View. A view is simply
|
||||
//! a model that can be rendered, via the [`Render`] trait. At the start of each frame, GPUI
|
||||
//! will call this render method on the root view of a given window. Views build a tree of
|
||||
//! `elements`, lay them out and style them with a tailwind-style API, and then give them to
|
||||
//! GPUI to turn into pixels. See the [`elements::Div`] element for an all purpose swiss-army
|
||||
//! knife for UI.
|
||||
//!
|
||||
//! - Low level, imperative UI with Elements. Elements are the building blocks of UI in GPUI, and they
|
||||
//! provide a nice wrapper around an imperative API that provides as much flexibility and control as
|
||||
//! you need. Elements have total control over how they and their child elements are rendered and and
|
||||
//! can be used for making efficient views into large lists, implement custom layouting for a code editor,
|
||||
//! and anything else you can think of. See the [`element`] module for more information.
|
||||
//!
|
||||
//! Each of these registers has one or more corresponding contexts that can be accessed from all GPUI services.
|
||||
//! This context is your main interface to GPUI, and is used extensively throughout the framework.
|
||||
//!
|
||||
//! ## Other Resources
|
||||
//!
|
||||
//! In addition to the systems above, GPUI provides a range of smaller services that are useful for building
|
||||
//! complex applications:
|
||||
//!
|
||||
//! - Actions are user-defined structs that are used for converting keystrokes into logical operations in your UI.
|
||||
//! Use this for implementing keyboard shortcuts, such as cmd-q. See the [`action`] module for more information.
|
||||
//! - Platform services, such as `quit the app` or `open a URL` are available as methods on the [`app::AppContext`].
|
||||
//! - An async executor that is integrated with the platform's event loop. See the [`executor`] module for more information.,
|
||||
//! - The [gpui::test] macro provides a convenient way to write tests for your GPUI applications. Tests also have their
|
||||
//! own kind of context, a [`TestAppContext`] which provides ways of simulating common platform input. See [`app::test_context`]
|
||||
//! and [`test`] modules for more details.
|
||||
//!
|
||||
//! Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop
|
||||
//! a question in the [Zed Discord](https://discord.gg/U4qhCEhMXP). We're working on improving the documentation, creating more examples,
|
||||
//! and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog).
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use crate::{ImageData, ImageId, SharedUrl};
|
||||
use crate::{AppContext, ImageData, ImageId, SharedUrl, Task};
|
||||
use collections::HashMap;
|
||||
use futures::{
|
||||
future::{BoxFuture, Shared},
|
||||
AsyncReadExt, FutureExt, TryFutureExt,
|
||||
};
|
||||
use futures::{future::Shared, AsyncReadExt, FutureExt, TryFutureExt};
|
||||
use image::ImageError;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
@ -44,10 +41,10 @@ impl From<ImageError> for Error {
|
|||
|
||||
pub(crate) struct ImageCache {
|
||||
client: Arc<dyn HttpClient>,
|
||||
images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
|
||||
images: Arc<Mutex<HashMap<SharedUrl, FetchImageTask>>>,
|
||||
}
|
||||
|
||||
type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
|
||||
type FetchImageTask = Shared<Task<Result<Arc<ImageData>, Error>>>;
|
||||
|
||||
impl ImageCache {
|
||||
pub fn new(client: Arc<dyn HttpClient>) -> Self {
|
||||
|
@ -57,10 +54,7 @@ impl ImageCache {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
&self,
|
||||
uri: impl Into<SharedUrl>,
|
||||
) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
|
||||
pub fn get(&self, uri: impl Into<SharedUrl>, cx: &AppContext) -> FetchImageTask {
|
||||
let uri = uri.into();
|
||||
let mut images = self.images.lock();
|
||||
|
||||
|
@ -68,10 +62,14 @@ impl ImageCache {
|
|||
Some(future) => future.clone(),
|
||||
None => {
|
||||
let client = self.client.clone();
|
||||
let future = {
|
||||
let future = cx
|
||||
.background_executor()
|
||||
.spawn(
|
||||
{
|
||||
let uri = uri.clone();
|
||||
async move {
|
||||
let mut response = client.get(uri.as_ref(), ().into(), true).await?;
|
||||
let mut response =
|
||||
client.get(uri.as_ref(), ().into(), true).await?;
|
||||
let mut body = Vec::new();
|
||||
response.body_mut().read_to_end(&mut body).await?;
|
||||
|
||||
|
@ -83,20 +81,19 @@ impl ImageCache {
|
|||
}
|
||||
|
||||
let format = image::guess_format(&body)?;
|
||||
let image =
|
||||
image::load_from_memory_with_format(&body, format)?.into_bgra8();
|
||||
let image = image::load_from_memory_with_format(&body, format)?
|
||||
.into_bgra8();
|
||||
Ok(Arc::new(ImageData::new(image)))
|
||||
}
|
||||
}
|
||||
.map_err({
|
||||
let uri = uri.clone();
|
||||
|
||||
move |error| {
|
||||
log::log!(log::Level::Error, "{:?} {:?}", &uri, &error);
|
||||
error
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
}),
|
||||
)
|
||||
.shared();
|
||||
|
||||
images.insert(uri, future.clone());
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
|
||||
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
|
||||
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
|
||||
Scene, SharedString, Size, TaskLabel, WindowContext,
|
||||
Scene, SharedString, Size, Task, TaskLabel, WindowContext,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_task::Runnable;
|
||||
|
@ -108,9 +108,9 @@ pub(crate) trait Platform: 'static {
|
|||
fn write_to_clipboard(&self, item: ClipboardItem);
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
||||
|
||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
|
||||
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
|
||||
fn delete_credentials(&self, url: &str) -> Result<()>;
|
||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
|
||||
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
|
||||
fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
|
||||
}
|
||||
|
||||
/// A handle to a platform's display, e.g. a monitor or laptop screen.
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
||||
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
|
||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use block::ConcreteBlock;
|
||||
|
@ -856,14 +856,18 @@ impl Platform for MacPlatform {
|
|||
}
|
||||
}
|
||||
|
||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
|
||||
let url = CFString::from(url);
|
||||
let username = CFString::from(username);
|
||||
let password = CFData::from_buffer(password);
|
||||
|
||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
|
||||
let url = url.to_string();
|
||||
let username = username.to_string();
|
||||
let password = password.to_vec();
|
||||
self.background_executor().spawn(async move {
|
||||
unsafe {
|
||||
use security::*;
|
||||
|
||||
let url = CFString::from(url.as_str());
|
||||
let username = CFString::from(username.as_str());
|
||||
let password = CFData::from_buffer(&password);
|
||||
|
||||
// First, check if there are already credentials for the given server. If so, then
|
||||
// update the username and password.
|
||||
let mut verb = "updating";
|
||||
|
@ -893,10 +897,13 @@ impl Platform for MacPlatform {
|
|||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
|
||||
let url = CFString::from(url);
|
||||
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||
let url = url.to_string();
|
||||
self.background_executor().spawn(async move {
|
||||
let url = CFString::from(url.as_str());
|
||||
let cf_true = CFBoolean::true_value().as_CFTypeRef();
|
||||
|
||||
unsafe {
|
||||
|
@ -935,14 +942,17 @@ impl Platform for MacPlatform {
|
|||
|
||||
Ok(Some((username.to_string(), password.bytes().to_vec())))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, url: &str) -> Result<()> {
|
||||
let url = CFString::from(url);
|
||||
fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
|
||||
let url = url.to_string();
|
||||
|
||||
self.background_executor().spawn(async move {
|
||||
unsafe {
|
||||
use security::*;
|
||||
|
||||
let url = CFString::from(url.as_str());
|
||||
let mut query_attrs = CFMutableDictionary::with_capacity(2);
|
||||
query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
|
||||
query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
|
||||
|
@ -954,6 +964,7 @@ impl Platform for MacPlatform {
|
|||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
|
||||
Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
|
||||
Keymap, Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow,
|
||||
WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::VecDeque;
|
||||
|
@ -280,16 +281,16 @@ impl Platform for TestPlatform {
|
|||
self.current_clipboard_item.lock().clone()
|
||||
}
|
||||
|
||||
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Result<()> {
|
||||
Ok(())
|
||||
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn read_credentials(&self, _url: &str) -> Result<Option<(String, Vec<u8>)>> {
|
||||
Ok(None)
|
||||
fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||
Task::ready(Ok(None))
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, _url: &str) -> Result<()> {
|
||||
Ok(())
|
||||
fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn double_click_interval(&self) -> std::time::Duration {
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::{borrow::Borrow, sync::Arc};
|
|||
use util::arc_cow::ArcCow;
|
||||
|
||||
/// A shared string is an immutable string that can be cheaply cloned in GPUI
|
||||
/// tasks. Essentially an abstraction over an Arc<str> and &'static str,
|
||||
/// tasks. Essentially an abstraction over an `Arc<str>` and `&'static str`,
|
||||
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ pub(crate) const SUBPIXEL_VARIANTS: u8 = 4;
|
|||
pub struct TextSystem {
|
||||
line_layout_cache: Arc<LineLayoutCache>,
|
||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||
font_ids_by_font: RwLock<FxHashMap<Font, FontId>>,
|
||||
font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
|
||||
font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
|
||||
raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
|
||||
wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
|
||||
|
@ -91,13 +91,26 @@ impl TextSystem {
|
|||
|
||||
/// Get the FontId for the configure font family and style.
|
||||
pub fn font_id(&self, font: &Font) -> Result<FontId> {
|
||||
let font_id = self.font_ids_by_font.read().get(font).copied();
|
||||
fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
|
||||
match font_id {
|
||||
Ok(font_id) => Ok(*font_id),
|
||||
Err(err) => Err(anyhow!("{}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
let font_id = self
|
||||
.font_ids_by_font
|
||||
.read()
|
||||
.get(font)
|
||||
.map(clone_font_id_result);
|
||||
if let Some(font_id) = font_id {
|
||||
Ok(font_id)
|
||||
font_id
|
||||
} else {
|
||||
let font_id = self.platform_text_system.font_id(font)?;
|
||||
self.font_ids_by_font.write().insert(font.clone(), font_id);
|
||||
Ok(font_id)
|
||||
let font_id = self.platform_text_system.font_id(font);
|
||||
self.font_ids_by_font
|
||||
.write()
|
||||
.insert(font.clone(), clone_font_id_result(&font_id));
|
||||
font_id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
|
|||
|
||||
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FocusEvent {
|
||||
previous_focus_path: SmallVec<[FocusId; 8]>,
|
||||
current_focus_path: SmallVec<[FocusId; 8]>,
|
||||
|
@ -105,7 +106,7 @@ slotmap::new_key_type! {
|
|||
}
|
||||
|
||||
thread_local! {
|
||||
pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(4 * 1024 * 1024));
|
||||
pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(8 * 1024 * 1024));
|
||||
}
|
||||
|
||||
impl FocusId {
|
||||
|
@ -1030,7 +1031,13 @@ impl<'a> WindowContext<'a> {
|
|||
self.window
|
||||
.next_frame
|
||||
.finish(&mut self.window.rendered_frame);
|
||||
ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear());
|
||||
ELEMENT_ARENA.with_borrow_mut(|element_arena| {
|
||||
let percentage = (element_arena.len() as f32 / element_arena.capacity() as f32) * 100.;
|
||||
if percentage >= 80. {
|
||||
log::warn!("elevated element arena occupation: {}.", percentage);
|
||||
}
|
||||
element_arena.clear();
|
||||
});
|
||||
|
||||
let previous_focus_path = self.window.rendered_frame.focus_path();
|
||||
let previous_window_active = self.window.rendered_frame.window_active;
|
||||
|
@ -2015,11 +2022,12 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
|||
}
|
||||
}
|
||||
|
||||
if !self.window.drawing {
|
||||
self.window_cx.window.dirty = true;
|
||||
// Always emit a notify effect, so that handlers fire correctly
|
||||
self.window_cx.app.push_effect(Effect::Notify {
|
||||
emitter: self.view.model.entity_id,
|
||||
});
|
||||
if !self.window.drawing {
|
||||
self.window_cx.window.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2669,7 +2677,7 @@ impl From<(&'static str, u64)> for ElementId {
|
|||
}
|
||||
|
||||
/// A rectangle to be rendered in the window at the given position and size.
|
||||
/// Passed as an argument [`WindowContext::paint_quad`].
|
||||
/// Passed as an argument [`ElementContext::paint_quad`].
|
||||
#[derive(Clone)]
|
||||
pub struct PaintQuad {
|
||||
bounds: Bounds<Pixels>,
|
||||
|
@ -2751,3 +2759,59 @@ pub fn outline(bounds: impl Into<Bounds<Pixels>>, border_color: impl Into<Hsla>)
|
|||
border_color: border_color.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
self as gpui, div, FocusHandle, InteractiveElement, IntoElement, Render, TestAppContext,
|
||||
ViewContext, VisualContext,
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_notify_on_focus(cx: &mut TestAppContext) {
|
||||
struct TestFocusView {
|
||||
handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl Render for TestFocusView {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div().id("test").track_focus(&self.handle)
|
||||
}
|
||||
}
|
||||
|
||||
let notify_counter = Rc::new(RefCell::new(0));
|
||||
|
||||
let (notify_producer, cx) = cx.add_window_view(|cx| {
|
||||
cx.activate_window();
|
||||
let handle = cx.focus_handle();
|
||||
|
||||
cx.on_focus(&handle, |_, cx| {
|
||||
cx.notify();
|
||||
})
|
||||
.detach();
|
||||
|
||||
TestFocusView { handle }
|
||||
});
|
||||
|
||||
let focus_handle = cx.update(|cx| notify_producer.read(cx).handle.clone());
|
||||
|
||||
let _notify_consumer = cx.new_view({
|
||||
|cx| {
|
||||
let notify_counter = notify_counter.clone();
|
||||
cx.observe(¬ify_producer, move |_, _, _| {
|
||||
*notify_counter.borrow_mut() += 1;
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.focus(&focus_handle);
|
||||
});
|
||||
|
||||
assert_eq!(*notify_counter.borrow(), 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -656,7 +656,7 @@ impl<'a> ElementContext<'a> {
|
|||
|
||||
/// Paint one or more quads into the scene for the next frame at the current stacking context.
|
||||
/// Quads are colored rectangular regions with an optional background, border, and corner radius.
|
||||
/// see [`fill`], [`outline`], and [`quad`] to construct this type.
|
||||
/// see [`fill`](crate::fill), [`outline`](crate::outline), and [`quad`](crate::quad) to construct this type.
|
||||
pub fn paint_quad(&mut self, quad: PaintQuad) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let content_mask = self.content_mask();
|
||||
|
@ -731,9 +731,11 @@ impl<'a> ElementContext<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
/// Paint a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index.
|
||||
/// Paints a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index.
|
||||
///
|
||||
/// The y component of the origin is the baseline of the glyph.
|
||||
/// You should generally prefer to use the [`ShapedLine::paint`] or [`WrappedLine::paint`] methods in the [`text_system`].
|
||||
/// You should generally prefer to use the [`ShapedLine::paint`](crate::ShapedLine::paint) or
|
||||
/// [`WrappedLine::paint`](crate::WrappedLine::paint) methods in the [`TextSystem`](crate::TextSystem).
|
||||
/// This method is only useful if you need to paint a single glyph that has already been shaped.
|
||||
pub fn paint_glyph(
|
||||
&mut self,
|
||||
|
@ -790,9 +792,11 @@ impl<'a> ElementContext<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Paint an emoji glyph into the scene for the next frame at the current z-index.
|
||||
/// Paints an emoji glyph into the scene for the next frame at the current z-index.
|
||||
///
|
||||
/// The y component of the origin is the baseline of the glyph.
|
||||
/// You should generally prefer to use the [`ShapedLine::paint`] or [`WrappedLine::paint`] methods in the [`text_system`].
|
||||
/// You should generally prefer to use the [`ShapedLine::paint`](crate::ShapedLine::paint) or
|
||||
/// [`WrappedLine::paint`](crate::WrappedLine::paint) methods in the [`TextSystem`](crate::TextSystem).
|
||||
/// This method is only useful if you need to paint a single emoji that has already been shaped.
|
||||
pub fn paint_emoji(
|
||||
&mut self,
|
||||
|
|
|
@ -90,13 +90,12 @@ pub fn init(
|
|||
.detach();
|
||||
|
||||
cx.spawn(move |cx| async move {
|
||||
let embedding_provider =
|
||||
OpenAIEmbeddingProvider::new(http_client, cx.background_executor().clone()).await;
|
||||
let semantic_index = SemanticIndex::new(
|
||||
fs,
|
||||
db_file_path,
|
||||
Arc::new(OpenAIEmbeddingProvider::new(
|
||||
http_client,
|
||||
cx.background_executor().clone(),
|
||||
)),
|
||||
Arc::new(embedding_provider),
|
||||
language_registry,
|
||||
cx.clone(),
|
||||
)
|
||||
|
@ -279,14 +278,22 @@ impl SemanticIndex {
|
|||
.map(|semantic_index| semantic_index.clone())
|
||||
}
|
||||
|
||||
pub fn authenticate(&mut self, cx: &mut AppContext) -> bool {
|
||||
pub fn authenticate(&mut self, cx: &mut AppContext) -> Task<bool> {
|
||||
if !self.embedding_provider.has_credentials() {
|
||||
self.embedding_provider.retrieve_credentials(cx);
|
||||
} else {
|
||||
return true;
|
||||
let embedding_provider = self.embedding_provider.clone();
|
||||
cx.spawn(|cx| async move {
|
||||
if let Some(retrieve_credentials) = cx
|
||||
.update(|cx| embedding_provider.retrieve_credentials(cx))
|
||||
.log_err()
|
||||
{
|
||||
retrieve_credentials.await;
|
||||
}
|
||||
|
||||
self.embedding_provider.has_credentials()
|
||||
embedding_provider.has_credentials()
|
||||
})
|
||||
} else {
|
||||
Task::ready(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_authenticated(&self) -> bool {
|
||||
|
@ -1006,12 +1013,26 @@ impl SemanticIndex {
|
|||
project: Model<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if !self.is_authenticated() {
|
||||
if !self.authenticate(cx) {
|
||||
return Task::ready(Err(anyhow!("user is not authenticated")));
|
||||
if self.is_authenticated() {
|
||||
self.index_project_internal(project, cx)
|
||||
} else {
|
||||
let authenticate = self.authenticate(cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if authenticate.await {
|
||||
this.update(&mut cx, |this, cx| this.index_project_internal(project, cx))?
|
||||
.await
|
||||
} else {
|
||||
Err(anyhow!("user is not authenticated"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn index_project_internal(
|
||||
&mut self,
|
||||
project: Model<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if !self.projects.contains_key(&project.downgrade()) {
|
||||
let subscription = cx.subscribe(&project, |this, project, event, cx| match event {
|
||||
project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use gpui::{px, rgb, Div, Hsla, IntoElement, Render, RenderOnce};
|
||||
use gpui::{px, rgb, Div, IntoElement, Render, RenderOnce};
|
||||
use story::Story;
|
||||
use ui::prelude::*;
|
||||
|
||||
|
@ -51,22 +51,22 @@ trait Styles: Styled + Sized {
|
|||
self.absolute()
|
||||
.w(px(150.))
|
||||
.h(px(50.))
|
||||
.text_color(rgb::<Hsla>(0x000000))
|
||||
.text_color(rgb(0x000000))
|
||||
}
|
||||
|
||||
fn blue(self) -> Self {
|
||||
self.bg(rgb::<Hsla>(0xe5e8fc))
|
||||
self.bg(rgb(0xe5e8fc))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0x112382))
|
||||
.border_color(rgb(0x112382))
|
||||
.line_height(px(55.))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(24.))
|
||||
}
|
||||
|
||||
fn red(self) -> Self {
|
||||
self.bg(rgb::<Hsla>(0xfce5e7))
|
||||
self.bg(rgb(0xfce5e7))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0xe3a1a7))
|
||||
.border_color(rgb(0xe3a1a7))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(8.))
|
||||
}
|
||||
|
@ -92,10 +92,10 @@ impl RenderOnce for ZIndexExample {
|
|||
.left(px(15.))
|
||||
.w(px(180.))
|
||||
.h(px(230.))
|
||||
.bg(rgb::<Hsla>(0xfcfbe5))
|
||||
.text_color(rgb::<Hsla>(0x000000))
|
||||
.bg(rgb(0xfcfbe5))
|
||||
.text_color(rgb(0x000000))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0xe3e0a1))
|
||||
.border_color(rgb(0xe3e0a1))
|
||||
.line_height(px(215.))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(24.))
|
||||
|
|
|
@ -261,7 +261,7 @@ mod tests {
|
|||
fn override_a_single_theme_color() {
|
||||
let mut colors = ThemeColors::light();
|
||||
|
||||
let magenta: Hsla = gpui::rgb(0xff00ff);
|
||||
let magenta: Hsla = gpui::rgb(0xff00ff).into();
|
||||
|
||||
assert_ne!(colors.text, magenta);
|
||||
|
||||
|
@ -279,8 +279,8 @@ mod tests {
|
|||
fn override_multiple_theme_colors() {
|
||||
let mut colors = ThemeColors::light();
|
||||
|
||||
let magenta: Hsla = gpui::rgb(0xff00ff);
|
||||
let green: Hsla = gpui::rgb(0x00ff00);
|
||||
let magenta: Hsla = gpui::rgb(0xff00ff).into();
|
||||
let green: Hsla = gpui::rgb(0x00ff00).into();
|
||||
|
||||
assert_ne!(colors.text, magenta);
|
||||
assert_ne!(colors.background, green);
|
||||
|
@ -305,7 +305,7 @@ mod tests {
|
|||
}))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(colors.background, Some(gpui::rgb(0xff00ff)));
|
||||
assert_eq!(colors.text, Some(gpui::rgb(0xff0000)));
|
||||
assert_eq!(colors.background, Some(gpui::rgb(0xff00ff).into()));
|
||||
assert_eq!(colors.text, Some(gpui::rgb(0xff0000).into()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -342,7 +342,16 @@ impl Pane {
|
|||
}
|
||||
|
||||
pub fn has_focus(&self, cx: &WindowContext) -> bool {
|
||||
// We not only check whether our focus handle contains focus, but also
|
||||
// whether the active_item might have focus, because we might have just activated an item
|
||||
// but that hasn't rendered yet.
|
||||
// So before the next render, we might have transferred focus
|
||||
// to the item and `focus_handle.contains_focus` returns false because the `active_item`
|
||||
// is not hooked up to us in the dispatch tree.
|
||||
self.focus_handle.contains_focused(cx)
|
||||
|| self
|
||||
.active_item()
|
||||
.map_or(false, |item| item.focus_handle(cx).contains_focused(cx))
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
|
||||
|
@ -1470,7 +1479,7 @@ impl Pane {
|
|||
),
|
||||
)
|
||||
})
|
||||
.when(self.was_focused || self.has_focus(cx), |tab_bar| {
|
||||
.when(self.has_focus(cx), |tab_bar| {
|
||||
tab_bar.end_child({
|
||||
let render_tab_buttons = self.render_tab_bar_buttons.clone();
|
||||
render_tab_buttons(self, cx)
|
||||
|
|
|
@ -233,12 +233,11 @@ impl SerializedPane {
|
|||
workspace: WeakView<Workspace>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
|
||||
let mut items = Vec::new();
|
||||
let mut item_tasks = Vec::new();
|
||||
let mut active_item_index = None;
|
||||
for (index, item) in self.children.iter().enumerate() {
|
||||
let project = project.clone();
|
||||
let item_handle = pane
|
||||
.update(cx, |_, cx| {
|
||||
item_tasks.push(pane.update(cx, |_, cx| {
|
||||
if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) {
|
||||
deserializer(project, workspace.clone(), workspace_id, item.item_id, cx)
|
||||
} else {
|
||||
|
@ -247,10 +246,15 @@ impl SerializedPane {
|
|||
item.kind
|
||||
)))
|
||||
}
|
||||
})?
|
||||
.await
|
||||
.log_err();
|
||||
})?);
|
||||
if item.active {
|
||||
active_item_index = Some(index);
|
||||
}
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
for item_handle in futures::future::join_all(item_tasks).await {
|
||||
let item_handle = item_handle.log_err();
|
||||
items.push(item_handle.clone());
|
||||
|
||||
if let Some(item_handle) = item_handle {
|
||||
|
@ -258,10 +262,6 @@ impl SerializedPane {
|
|||
pane.add_item(item_handle.clone(), true, true, None, cx);
|
||||
})?;
|
||||
}
|
||||
|
||||
if item.active {
|
||||
active_item_index = Some(index);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_item_index) = active_item_index {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.120.0"
|
||||
version = "0.121.0"
|
||||
publish = false
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
|
|
|
@ -376,7 +376,7 @@ async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
|
|||
if client::IMPERSONATE_LOGIN.is_some() {
|
||||
client.authenticate_and_connect(false, &cx).await?;
|
||||
}
|
||||
} else if client.has_keychain_credentials(&cx) {
|
||||
} else if client.has_keychain_credentials(&cx).await {
|
||||
client.authenticate_and_connect(true, &cx).await?;
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
|
|
|
@ -2705,11 +2705,6 @@ mod tests {
|
|||
.unwrap()
|
||||
.to_vec()
|
||||
.into(),
|
||||
Assets
|
||||
.load("fonts/plex/IBMPlexSans-Regular.ttf")
|
||||
.unwrap()
|
||||
.to_vec()
|
||||
.into(),
|
||||
])
|
||||
.unwrap();
|
||||
let themes = ThemeRegistry::default();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## macOS
|
||||
|
||||
Supported versions: Catalina (10.15) - Ventura (13.x).
|
||||
Supported versions: Catalina (10.15) - Sonoma (14.x).
|
||||
|
||||
{% hint style="info" %}
|
||||
The implementation of our screen sharing feature makes use of [LiveKit](https://livekit.io). The LiveKit SDK requires macOS Catalina (10.15); consequently, in v0.62.4, we dropped support for earlier macOS versions that we were initially supporting.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue