diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml
index 550eda882b..c1d2457ed4 100644
--- a/.github/workflows/release_actions.yml
+++ b/.github/workflows/release_actions.yml
@@ -6,26 +6,27 @@ jobs:
discord_release:
runs-on: ubuntu-latest
steps:
- - name: Get release URL
- id: get-release-url
- run: |
- if [ "${{ github.event.release.prerelease }}" == "true" ]; then
- URL="https://zed.dev/releases/preview/latest"
- else
- URL="https://zed.dev/releases/stable/latest"
- fi
- echo "::set-output name=URL::$URL"
- - name: Get content
- uses: 2428392/gh-truncate-string-action@v1.2.0
- id: get-content
- with:
- stringToTruncate: |
- 📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released!
+ - name: Get release URL
+ id: get-release-url
+ run: |
+ if [ "${{ github.event.release.prerelease }}" == "true" ]; then
+ URL="https://zed.dev/releases/preview/latest"
+ else
+ URL="https://zed.dev/releases/stable/latest"
+ fi
+ echo "::set-output name=URL::$URL"
+ - name: Get content
+ uses: 2428392/gh-truncate-string-action@v1.3.0
+ id: get-content
+ with:
+ stringToTruncate: |
+ 📣 Zed [${{ github.event.release.tag_name }}](${{ steps.get-release-url.outputs.URL }}) was just released!
- ${{ github.event.release.body }}
- maxLength: 2000
- - name: Discord Webhook Action
- uses: tsickert/discord-webhook@v5.3.0
- with:
- webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
- content: ${{ steps.get-content.outputs.string }}
+ ${{ github.event.release.body }}
+ maxLength: 2000
+ truncationSymbol: "..."
+ - name: Discord Webhook Action
+ uses: tsickert/discord-webhook@v5.3.0
+ with:
+ webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
+ content: ${{ steps.get-content.outputs.string }}
diff --git a/Cargo.lock b/Cargo.lock
index 161e1e4b14..f91f574b9c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1603,7 +1603,7 @@ dependencies = [
[[package]]
name = "collab"
-version = "0.27.0"
+version = "0.28.0"
dependencies = [
"anyhow",
"async-trait",
diff --git a/assets/icons/at-sign.svg b/assets/icons/at-sign.svg
new file mode 100644
index 0000000000..5adac38f62
--- /dev/null
+++ b/assets/icons/at-sign.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/bell-off.svg b/assets/icons/bell-off.svg
new file mode 100644
index 0000000000..db1021f2d3
--- /dev/null
+++ b/assets/icons/bell-off.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/bell-ring.svg b/assets/icons/bell-ring.svg
new file mode 100644
index 0000000000..da51fdc5be
--- /dev/null
+++ b/assets/icons/bell-ring.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/bell.svg b/assets/icons/bell.svg
index ea1c6dd42e..4c7d5472db 100644
--- a/assets/icons/bell.svg
+++ b/assets/icons/bell.svg
@@ -1,8 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/mail-open.svg b/assets/icons/mail-open.svg
new file mode 100644
index 0000000000..b63915bd73
--- /dev/null
+++ b/assets/icons/mail-open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/crates/ai/src/test.rs b/crates/ai/src/test.rs
index d4165f3cca..3f331da117 100644
--- a/crates/ai/src/test.rs
+++ b/crates/ai/src/test.rs
@@ -153,10 +153,17 @@ impl FakeCompletionProvider {
pub fn send_completion(&self, completion: impl Into) {
let mut tx = self.last_completion_tx.lock();
- tx.as_mut().unwrap().try_send(completion.into()).unwrap();
+
+ println!("COMPLETION TX: {:?}", &tx);
+
+ let a = tx.as_mut().unwrap();
+ a.try_send(completion.into()).unwrap();
+
+ // tx.as_mut().unwrap().try_send(completion.into()).unwrap();
}
pub fn finish_completion(&self) {
+ println!("FINISHING COMPLETION");
self.last_completion_tx.lock().take().unwrap();
}
}
@@ -181,8 +188,10 @@ impl CompletionProvider for FakeCompletionProvider {
&self,
_prompt: Box,
) -> BoxFuture<'static, anyhow::Result>>> {
+ println!("COMPLETING");
let (tx, rx) = mpsc::channel(1);
*self.last_completion_tx.lock() = Some(tx);
+ println!("TX: {:?}", *self.last_completion_tx.lock());
async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed()
}
fn box_clone(&self) -> Box {
diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs
index 03eb3c238f..6ab96093a7 100644
--- a/crates/assistant/src/assistant_panel.rs
+++ b/crates/assistant/src/assistant_panel.rs
@@ -142,7 +142,7 @@ pub struct AssistantPanel {
zoomed: bool,
has_focus: bool,
toolbar: ViewHandle,
- completion_provider: Box,
+ completion_provider: Arc,
api_key_editor: Option>,
languages: Arc,
fs: Arc,
@@ -204,7 +204,7 @@ impl AssistantPanel {
let semantic_index = SemanticIndex::global(cx);
// Defaulting currently to GPT4, allow for this to be set via config.
- let completion_provider = Box::new(OpenAICompletionProvider::new(
+ let completion_provider = Arc::new(OpenAICompletionProvider::new(
"gpt-4",
cx.background().clone(),
));
@@ -259,7 +259,13 @@ impl AssistantPanel {
cx: &mut ViewContext,
) {
let this = if let Some(this) = workspace.panel::(cx) {
- if this.update(cx, |assistant, _| assistant.has_credentials()) {
+ if this.update(cx, |assistant, cx| {
+ if !assistant.has_credentials() {
+ assistant.load_credentials(cx);
+ };
+
+ assistant.has_credentials()
+ }) {
this
} else {
workspace.focus_panel::(cx);
@@ -320,13 +326,10 @@ impl AssistantPanel {
};
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
- let provider = Arc::new(OpenAICompletionProvider::new(
- "gpt-4",
- cx.background().clone(),
- ));
+ let provider = self.completion_provider.clone();
// Retrieve Credentials Authenticates the Provider
- // provider.retrieve_credentials(cx);
+ provider.retrieve_credentials(cx);
let codegen = cx.add_model(|cx| {
Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
@@ -1439,7 +1442,7 @@ struct Conversation {
pending_save: Task>,
path: Option,
_subscriptions: Vec,
- completion_provider: Box,
+ completion_provider: Arc,
}
impl Entity for Conversation {
@@ -1450,7 +1453,7 @@ impl Conversation {
fn new(
language_registry: Arc,
cx: &mut ModelContext,
- completion_provider: Box,
+ completion_provider: Arc,
) -> Self {
let markdown = language_registry.language_for_name("Markdown");
let buffer = cx.add_model(|cx| {
@@ -1544,7 +1547,7 @@ impl Conversation {
None => Some(Uuid::new_v4().to_string()),
};
let model = saved_conversation.model;
- let completion_provider: Box = Box::new(
+ let completion_provider: Arc = Arc::new(
OpenAICompletionProvider::new(model.full_name(), cx.background().clone()),
);
completion_provider.retrieve_credentials(cx);
@@ -2201,7 +2204,7 @@ struct ConversationEditor {
impl ConversationEditor {
fn new(
- completion_provider: Box,
+ completion_provider: Arc,
language_registry: Arc,
fs: Arc,
workspace: WeakViewHandle,
@@ -3406,7 +3409,7 @@ mod tests {
init(cx);
let registry = Arc::new(LanguageRegistry::test());
- let completion_provider = Box::new(FakeCompletionProvider::new());
+ let completion_provider = Arc::new(FakeCompletionProvider::new());
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
let buffer = conversation.read(cx).buffer.clone();
@@ -3535,7 +3538,7 @@ mod tests {
cx.set_global(SettingsStore::test(cx));
init(cx);
let registry = Arc::new(LanguageRegistry::test());
- let completion_provider = Box::new(FakeCompletionProvider::new());
+ let completion_provider = Arc::new(FakeCompletionProvider::new());
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
let buffer = conversation.read(cx).buffer.clone();
@@ -3633,7 +3636,7 @@ mod tests {
cx.set_global(SettingsStore::test(cx));
init(cx);
let registry = Arc::new(LanguageRegistry::test());
- let completion_provider = Box::new(FakeCompletionProvider::new());
+ let completion_provider = Arc::new(FakeCompletionProvider::new());
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
let buffer = conversation.read(cx).buffer.clone();
@@ -3716,7 +3719,7 @@ mod tests {
cx.set_global(SettingsStore::test(cx));
init(cx);
let registry = Arc::new(LanguageRegistry::test());
- let completion_provider = Box::new(FakeCompletionProvider::new());
+ let completion_provider = Arc::new(FakeCompletionProvider::new());
let conversation =
cx.add_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
let buffer = conversation.read(cx).buffer.clone();
diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs
index f62c91fcb7..25c9deef7f 100644
--- a/crates/assistant/src/codegen.rs
+++ b/crates/assistant/src/codegen.rs
@@ -367,6 +367,8 @@ fn strip_invalid_spans_from_codeblock(
#[cfg(test)]
mod tests {
+ use std::sync::Arc;
+
use super::*;
use ai::test::FakeCompletionProvider;
use futures::stream::{self};
@@ -437,6 +439,7 @@ mod tests {
let max_len = cmp::min(new_text.len(), 10);
let len = rng.gen_range(1..=max_len);
let (chunk, suffix) = new_text.split_at(len);
+ println!("CHUNK: {:?}", &chunk);
provider.send_completion(chunk);
new_text = suffix;
deterministic.run_until_parked();
@@ -569,6 +572,7 @@ mod tests {
let max_len = cmp::min(new_text.len(), 10);
let len = rng.gen_range(1..=max_len);
let (chunk, suffix) = new_text.split_at(len);
+ println!("{:?}", &chunk);
provider.send_completion(chunk);
new_text = suffix;
deterministic.run_until_parked();
diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml
index 987c295407..dea6e09245 100644
--- a/crates/collab/Cargo.toml
+++ b/crates/collab/Cargo.toml
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo "]
default-run = "collab"
edition = "2021"
name = "collab"
-version = "0.27.0"
+version = "0.28.0"
publish = false
[[bin]]
diff --git a/crates/db2/src/db2.rs b/crates/db2/src/db2.rs
index fe79dfbb0c..e052d59d12 100644
--- a/crates/db2/src/db2.rs
+++ b/crates/db2/src/db2.rs
@@ -190,138 +190,142 @@ where
.detach()
}
-// #[cfg(test)]
-// mod tests {
-// use std::thread;
+#[cfg(test)]
+mod tests {
+ use std::thread;
-// use sqlez::domain::Domain;
-// use sqlez_macros::sql;
-// use tempdir::TempDir;
+ use sqlez::domain::Domain;
+ use sqlez_macros::sql;
+ use tempdir::TempDir;
-// use crate::open_db;
+ use crate::open_db;
-// // Test bad migration panics
-// #[gpui::test]
-// #[should_panic]
-// async fn test_bad_migration_panics() {
-// enum BadDB {}
+ // Test bad migration panics
+ #[gpui2::test]
+ #[should_panic]
+ async fn test_bad_migration_panics() {
+ enum BadDB {}
-// impl Domain for BadDB {
-// fn name() -> &'static str {
-// "db_tests"
-// }
+ impl Domain for BadDB {
+ fn name() -> &'static str {
+ "db_tests"
+ }
-// fn migrations() -> &'static [&'static str] {
-// &[
-// sql!(CREATE TABLE test(value);),
-// // failure because test already exists
-// sql!(CREATE TABLE test(value);),
-// ]
-// }
-// }
+ fn migrations() -> &'static [&'static str] {
+ &[
+ sql!(CREATE TABLE test(value);),
+ // failure because test already exists
+ sql!(CREATE TABLE test(value);),
+ ]
+ }
+ }
-// let tempdir = TempDir::new("DbTests").unwrap();
-// let _bad_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-// }
+ let tempdir = TempDir::new("DbTests").unwrap();
+ let _bad_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
+ }
-// /// Test that DB exists but corrupted (causing recreate)
-// #[gpui::test]
-// async fn test_db_corruption() {
-// enum CorruptedDB {}
+ /// Test that DB exists but corrupted (causing recreate)
+ #[gpui2::test]
+ async fn test_db_corruption(cx: &mut gpui2::TestAppContext) {
+ cx.executor().allow_parking();
-// impl Domain for CorruptedDB {
-// fn name() -> &'static str {
-// "db_tests"
-// }
+ enum CorruptedDB {}
-// fn migrations() -> &'static [&'static str] {
-// &[sql!(CREATE TABLE test(value);)]
-// }
-// }
+ impl Domain for CorruptedDB {
+ fn name() -> &'static str {
+ "db_tests"
+ }
-// enum GoodDB {}
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test(value);)]
+ }
+ }
-// impl Domain for GoodDB {
-// fn name() -> &'static str {
-// "db_tests" //Notice same name
-// }
+ enum GoodDB {}
-// fn migrations() -> &'static [&'static str] {
-// &[sql!(CREATE TABLE test2(value);)] //But different migration
-// }
-// }
+ impl Domain for GoodDB {
+ fn name() -> &'static str {
+ "db_tests" //Notice same name
+ }
-// let tempdir = TempDir::new("DbTests").unwrap();
-// {
-// let corrupt_db =
-// open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-// assert!(corrupt_db.persistent());
-// }
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test2(value);)] //But different migration
+ }
+ }
-// let good_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-// assert!(
-// good_db.select_row::("SELECT * FROM test2").unwrap()()
-// .unwrap()
-// .is_none()
-// );
-// }
+ let tempdir = TempDir::new("DbTests").unwrap();
+ {
+ let corrupt_db =
+ open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
+ assert!(corrupt_db.persistent());
+ }
-// /// Test that DB exists but corrupted (causing recreate)
-// #[gpui::test(iterations = 30)]
-// async fn test_simultaneous_db_corruption() {
-// enum CorruptedDB {}
+ let good_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
+ assert!(
+ good_db.select_row::("SELECT * FROM test2").unwrap()()
+ .unwrap()
+ .is_none()
+ );
+ }
-// impl Domain for CorruptedDB {
-// fn name() -> &'static str {
-// "db_tests"
-// }
+ /// Test that DB exists but corrupted (causing recreate)
+ #[gpui2::test(iterations = 30)]
+ async fn test_simultaneous_db_corruption(cx: &mut gpui2::TestAppContext) {
+ cx.executor().allow_parking();
-// fn migrations() -> &'static [&'static str] {
-// &[sql!(CREATE TABLE test(value);)]
-// }
-// }
+ enum CorruptedDB {}
-// enum GoodDB {}
+ impl Domain for CorruptedDB {
+ fn name() -> &'static str {
+ "db_tests"
+ }
-// impl Domain for GoodDB {
-// fn name() -> &'static str {
-// "db_tests" //Notice same name
-// }
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test(value);)]
+ }
+ }
-// fn migrations() -> &'static [&'static str] {
-// &[sql!(CREATE TABLE test2(value);)] //But different migration
-// }
-// }
+ enum GoodDB {}
-// let tempdir = TempDir::new("DbTests").unwrap();
-// {
-// // Setup the bad database
-// let corrupt_db =
-// open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-// assert!(corrupt_db.persistent());
-// }
+ impl Domain for GoodDB {
+ fn name() -> &'static str {
+ "db_tests" //Notice same name
+ }
-// // Try to connect to it a bunch of times at once
-// let mut guards = vec![];
-// for _ in 0..10 {
-// let tmp_path = tempdir.path().to_path_buf();
-// let guard = thread::spawn(move || {
-// let good_db = smol::block_on(open_db::(
-// tmp_path.as_path(),
-// &util::channel::ReleaseChannel::Dev,
-// ));
-// assert!(
-// good_db.select_row::("SELECT * FROM test2").unwrap()()
-// .unwrap()
-// .is_none()
-// );
-// });
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test2(value);)] //But different migration
+ }
+ }
-// guards.push(guard);
-// }
+ let tempdir = TempDir::new("DbTests").unwrap();
+ {
+ // Setup the bad database
+ let corrupt_db =
+ open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
+ assert!(corrupt_db.persistent());
+ }
-// for guard in guards.into_iter() {
-// assert!(guard.join().is_ok());
-// }
-// }
-// }
+ // Try to connect to it a bunch of times at once
+ let mut guards = vec![];
+ for _ in 0..10 {
+ let tmp_path = tempdir.path().to_path_buf();
+ let guard = thread::spawn(move || {
+ let good_db = smol::block_on(open_db::(
+ tmp_path.as_path(),
+ &util::channel::ReleaseChannel::Dev,
+ ));
+ assert!(
+ good_db.select_row::("SELECT * FROM test2").unwrap()()
+ .unwrap()
+ .is_none()
+ );
+ });
+
+ guards.push(guard);
+ }
+
+ for guard in guards.into_iter() {
+ assert!(guard.join().is_ok());
+ }
+ }
+}
diff --git a/crates/db2/src/kvp.rs b/crates/db2/src/kvp.rs
index 254d91689d..b4445e3586 100644
--- a/crates/db2/src/kvp.rs
+++ b/crates/db2/src/kvp.rs
@@ -31,32 +31,32 @@ impl KeyValueStore {
}
}
-// #[cfg(test)]
-// mod tests {
-// use crate::kvp::KeyValueStore;
+#[cfg(test)]
+mod tests {
+ use crate::kvp::KeyValueStore;
-// #[gpui::test]
-// async fn test_kvp() {
-// let db = KeyValueStore(crate::open_test_db("test_kvp").await);
+ #[gpui2::test]
+ async fn test_kvp() {
+ let db = KeyValueStore(crate::open_test_db("test_kvp").await);
-// assert_eq!(db.read_kvp("key-1").unwrap(), None);
+ assert_eq!(db.read_kvp("key-1").unwrap(), None);
-// db.write_kvp("key-1".to_string(), "one".to_string())
-// .await
-// .unwrap();
-// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
+ db.write_kvp("key-1".to_string(), "one".to_string())
+ .await
+ .unwrap();
+ assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
-// db.write_kvp("key-1".to_string(), "one-2".to_string())
-// .await
-// .unwrap();
-// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string()));
+ db.write_kvp("key-1".to_string(), "one-2".to_string())
+ .await
+ .unwrap();
+ assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string()));
-// db.write_kvp("key-2".to_string(), "two".to_string())
-// .await
-// .unwrap();
-// assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
+ db.write_kvp("key-2".to_string(), "two".to_string())
+ .await
+ .unwrap();
+ assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
-// db.delete_kvp("key-1".to_string()).await.unwrap();
-// assert_eq!(db.read_kvp("key-1").unwrap(), None);
-// }
-// }
+ db.delete_kvp("key-1".to_string()).await.unwrap();
+ assert_eq!(db.read_kvp("key-1").unwrap(), None);
+ }
+}
diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs
index 4890b79a9a..a92dbd6ff9 100644
--- a/crates/gpui2/src/element.rs
+++ b/crates/gpui2/src/element.rs
@@ -198,14 +198,19 @@ impl AnyElement {
pub trait Component {
fn render(self) -> AnyElement;
- fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
+ fn map(self, f: impl FnOnce(Self) -> U) -> U
+ where
+ Self: Sized,
+ U: Component,
+ {
+ f(self)
+ }
+
+ fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
where
Self: Sized,
{
- if condition {
- self = then(self);
- }
- self
+ self.map(|this| if condition { then(this) } else { this })
}
}
diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs
index 055c31af16..a75c2ef319 100644
--- a/crates/gpui2/src/window.rs
+++ b/crates/gpui2/src/window.rs
@@ -6,8 +6,9 @@ use crate::{
Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
- SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task,
- Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS,
+ SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
+ TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
+ WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@@ -53,6 +54,7 @@ pub enum DispatchPhase {
Capture,
}
+type AnyObserver = Box bool + 'static>;
type AnyListener = Box;
type AnyKeyListener = Box<
dyn Fn(
@@ -185,6 +187,10 @@ pub struct Window {
default_prevented: bool,
mouse_position: Point,
scale_factor: f32,
+ bounds: WindowBounds,
+ bounds_observers: SubscriberSet<(), AnyObserver>,
+ active: bool,
+ activation_observers: SubscriberSet<(), AnyObserver>,
pub(crate) scene_builder: SceneBuilder,
pub(crate) dirty: bool,
pub(crate) last_blur: Option