Compare commits

...

6 Commits

Author SHA1 Message Date
Thorsten Ball
a745e9b58b zed 0.121.1 2024-02-01 16:53:24 +01:00
Ares Andrew
958fbacc2b Filter LSP github releases that have no assets to properly download LSP servers (#7189)
Fixes https://github.com/zed-industries/zed/issues/7183

Release Notes:

- Filter lsp github releases that have no assets ([7189](https://github.com/zed-industries/zed/issues/7183))
2024-02-01 16:50:14 +01:00
Thorsten Ball
bb6c06e204 assistant: render api key editor if no credentials are set (#7197)
This hopefully reduces confusion for new users. I updated the docs just
this morning, but I figured it's probably better to fix the issue
itself.

So what this does is to render the API key editor whenever the assistant
panel is opened/focused and no credentials can be found.

See: https://github.com/zed-industries/zed/discussions/6943

Release Notes:

- Fixed assistant panel not showing dialog to enter API key when opened
without saved credentials.

---------

Co-authored-by: Piotr <piotr@zed.dev>
2024-02-01 16:44:38 +01:00
Marshall Bowers
2e9f665c77 Watch the themes directory for changes (#7173)
This PR makes Zed watch the themes directory for changes.

When theme files are added or modified, we reload the theme and apply
any changes to Zed.

Release Notes:

- Added live reloading for the themes directory.
2024-01-31 18:30:23 -05:00
Conrad Irwin
3fdccaacbc disallow opening private files (#7165)
- Disallow sharing gitignored files through collab
- Show errors when failing to open files
- Show a warning to followers when view is unshared

/cc @mikaylamaki, let's update this to use your `private_files` config
before merge.


Release Notes:

- Added the ability to prevent sharing private files over collab.

---------

Co-authored-by: Piotr <piotr@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2024-01-31 12:08:09 -08:00
Joseph T. Lyons
7dd7ecd07f v0.121.x preview 2024-01-31 14:44:02 -05:00
16 changed files with 214 additions and 68 deletions

2
Cargo.lock generated
View File

@@ -10261,7 +10261,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.121.0"
version = "0.121.1"
dependencies = [
"activity_indicator",
"ai",

View File

@@ -199,9 +199,13 @@ impl AssistantPanel {
.update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
cx.notify();
if self.focus_handle.is_focused(cx) {
if let Some(editor) = self.active_editor() {
cx.focus_view(editor);
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
if self.has_credentials() {
if let Some(editor) = self.active_editor() {
cx.focus_view(editor);
}
}
if let Some(api_key_editor) = self.api_key_editor.as_ref() {
cx.focus_view(api_key_editor);
}
}
@@ -777,6 +781,10 @@ impl AssistantPanel {
});
}
fn build_api_key_editor(&mut self, cx: &mut WindowContext<'_>) {
self.api_key_editor = Some(build_api_key_editor(cx));
}
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
let editor = cx.new_view(|cx| {
ConversationEditor::new(
@@ -870,7 +878,7 @@ impl AssistantPanel {
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.build_api_key_editor(cx);
this.focus_handle.focus(cx);
cx.notify();
})
@@ -1136,7 +1144,7 @@ impl AssistantPanel {
}
}
fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
fn build_api_key_editor(cx: &mut WindowContext) -> View<Editor> {
cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("sk-000000000000000000000000000000000000000000000000", cx);
@@ -1147,9 +1155,10 @@ fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
if let Some(api_key_editor) = self.api_key_editor.clone() {
const INSTRUCTIONS: [&'static str; 5] = [
const INSTRUCTIONS: [&'static str; 6] = [
"To use the assistant panel or inline assistant, you need to add your OpenAI API key.",
" - You can create an API key at: platform.openai.com/api-keys",
" - Make sure your OpenAI account has credits",
" - Having a subscription for another service like GitHub Copilot won't work.",
" ",
"Paste your OpenAI API key and press Enter to use the assistant:"
@@ -1342,7 +1351,9 @@ impl Panel for AssistantPanel {
cx.spawn(|this, mut cx| async move {
load_credentials.await;
this.update(&mut cx, |this, cx| {
if this.editors.is_empty() {
if !this.has_credentials() {
this.build_api_key_editor(cx);
} else if this.editors.is_empty() {
this.new_conversation(cx);
}
})

View File

@@ -28,6 +28,9 @@ async fn main() -> Result<()> {
Some("version") => {
println!("collab v{VERSION}");
}
Some("migrate") => {
run_migrations().await?;
}
Some("serve") => {
let config = envy::from_env::<Config>().expect("error loading config");
init_tracing(&config);

View File

@@ -185,6 +185,14 @@ impl FollowableItem for Editor {
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
let buffer = self.buffer.read(cx);
if buffer
.as_singleton()
.and_then(|buffer| buffer.read(cx).file())
.map_or(false, |file| file.is_private())
{
return None;
}
let scroll_anchor = self.scroll_manager.anchor();
let excerpts = buffer
.read(cx)

View File

@@ -384,7 +384,7 @@ pub trait File: Send + Sync {
/// Converts this file into a protobuf message.
fn to_proto(&self) -> rpc::proto::File;
/// Return whether Zed considers this to be a dotenv file.
/// Return whether Zed considers this to be a private file.
fn is_private(&self) -> bool;
}
@@ -406,6 +406,11 @@ pub trait LocalFile: File {
mtime: SystemTime,
cx: &mut AppContext,
);
/// Returns true if the file should not be shared with collaborators.
fn is_private(&self, _: &AppContext) -> bool {
false
}
}
/// The auto-indent behavior associated with an editing operation.

View File

@@ -56,6 +56,7 @@ use postage::watch;
use prettier_support::{DefaultPrettier, PrettierInstance};
use project_settings::{LspSettings, ProjectSettings};
use rand::prelude::*;
use rpc::{ErrorCode, ErrorExt};
use search::SearchQuery;
use serde::Serialize;
use settings::{Settings, SettingsStore};
@@ -1760,7 +1761,7 @@ impl Project {
cx.background_executor().spawn(async move {
wait_for_loading_buffer(loading_watch)
.await
.map_err(|error| anyhow!("{project_path:?} opening failure: {error:#}"))
.map_err(|e| e.cloned())
})
}
@@ -8011,11 +8012,20 @@ impl Project {
.update(&mut cx, |this, cx| this.open_buffer_for_symbol(&symbol, cx))?
.await?;
Ok(proto::OpenBufferForSymbolResponse {
buffer_id: this.update(&mut cx, |this, cx| {
this.create_buffer_for_peer(&buffer, peer_id, cx).into()
})?,
})
this.update(&mut cx, |this, cx| {
let is_private = buffer
.read(cx)
.file()
.map(|f| f.is_private())
.unwrap_or_default();
if is_private {
Err(anyhow!(ErrorCode::UnsharedItem))
} else {
Ok(proto::OpenBufferForSymbolResponse {
buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
})
}
})?
}
fn symbol_signature(&self, project_path: &ProjectPath) -> [u8; 32] {
@@ -8037,11 +8047,7 @@ impl Project {
let buffer = this
.update(&mut cx, |this, cx| this.open_buffer_by_id(buffer_id, cx))?
.await?;
this.update(&mut cx, |this, cx| {
Ok(proto::OpenBufferResponse {
buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
})
})?
Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
}
async fn handle_open_buffer_by_path(
@@ -8063,10 +8069,28 @@ impl Project {
})?;
let buffer = open_buffer.await?;
this.update(&mut cx, |this, cx| {
Ok(proto::OpenBufferResponse {
buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
})
Project::respond_to_open_buffer_request(this, buffer, peer_id, &mut cx)
}
fn respond_to_open_buffer_request(
this: Model<Self>,
buffer: Model<Buffer>,
peer_id: proto::PeerId,
cx: &mut AsyncAppContext,
) -> Result<proto::OpenBufferResponse> {
this.update(cx, |this, cx| {
let is_private = buffer
.read(cx)
.file()
.map(|f| f.is_private())
.unwrap_or_default();
if is_private {
Err(anyhow!(ErrorCode::UnsharedItem))
} else {
Ok(proto::OpenBufferResponse {
buffer_id: this.create_buffer_for_peer(&buffer, peer_id, cx).into(),
})
}
})?
}

View File

@@ -31,6 +31,7 @@ theme = { path = "../theme" }
ui = { path = "../ui" }
unicase = "2.6"
util = { path = "../util" }
client = { path = "../client" }
workspace = { path = "../workspace", package = "workspace" }
[dev-dependencies]

View File

@@ -1,5 +1,6 @@
pub mod file_associations;
mod project_panel_settings;
use client::{ErrorCode, ErrorExt};
use settings::Settings;
use db::kvp::KEY_VALUE_STORE;
@@ -35,6 +36,7 @@ use unicase::UniCase;
use util::{maybe, ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
notifications::DetachAndPromptErr,
Workspace,
};
@@ -259,6 +261,7 @@ impl ProjectPanel {
} => {
if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) {
let file_path = entry.path.clone();
workspace
.open_path(
ProjectPath {
@@ -269,7 +272,15 @@ impl ProjectPanel {
focus_opened_item,
cx,
)
.detach_and_log_err(cx);
.detach_and_prompt_err("Failed to open file", cx, move |e, _| {
match e.error_code() {
ErrorCode::UnsharedItem => Some(format!(
"{} is not shared by the host. This could be because it has been marked as `private`",
file_path.display()
)),
_ => None,
}
});
if !focus_opened_item {
if let Some(project_panel) = project_panel.upgrade() {
let focus_handle = project_panel.read(cx).focus_handle.clone();

View File

@@ -216,6 +216,7 @@ enum ErrorCode {
BadPublicNesting = 9;
CircularNesting = 10;
WrongMoveTarget = 11;
UnsharedItem = 12;
}
message Test {

View File

@@ -80,6 +80,8 @@ pub trait ErrorExt {
fn error_tag(&self, k: &str) -> Option<&str>;
/// to_proto() converts the error into a proto::Error
fn to_proto(&self) -> proto::Error;
///
fn cloned(&self) -> anyhow::Error;
}
impl ErrorExt for anyhow::Error {
@@ -106,6 +108,14 @@ impl ErrorExt for anyhow::Error {
ErrorCode::Internal.message(format!("{}", self)).to_proto()
}
}
fn cloned(&self) -> anyhow::Error {
if let Some(rpc_error) = self.downcast_ref::<RpcError>() {
rpc_error.cloned()
} else {
anyhow::anyhow!("{}", self)
}
}
}
impl From<proto::ErrorCode> for anyhow::Error {
@@ -189,6 +199,10 @@ impl ErrorExt for RpcError {
tags: self.tags.clone(),
}
}
fn cloned(&self) -> anyhow::Error {
self.clone().into()
}
}
impl std::error::Error for RpcError {

View File

@@ -255,19 +255,23 @@ impl ThemeRegistry {
continue;
};
let Some(reader) = fs.open_sync(&theme_path).await.log_err() else {
continue;
};
let Some(theme) = serde_json_lenient::from_reader(reader).log_err() else {
continue;
};
self.insert_user_theme_families([theme]);
self.load_user_theme(&theme_path, fs.clone())
.await
.log_err();
}
Ok(())
}
/// Loads the user theme from the specified path and adds it to the registry.
pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
let reader = fs.open_sync(&theme_path).await?;
let theme = serde_json_lenient::from_reader(reader)?;
self.insert_user_theme_families([theme]);
Ok(())
}
}
impl Default for ThemeRegistry {

View File

@@ -68,6 +68,6 @@ pub async fn latest_github_release(
releases
.into_iter()
.find(|release| release.pre_release == pre_release)
.find(|release| !release.assets.is_empty() && release.pre_release == pre_release)
.ok_or(anyhow!("Failed to find a release"))
}

View File

@@ -176,11 +176,19 @@ impl Member {
return div().into_any();
}
let leader = follower_states.get(pane).and_then(|state| {
let follower_state = follower_states.get(pane);
let leader = follower_state.and_then(|state| {
let room = active_call?.read(cx).room()?.read(cx);
room.remote_participant_for_peer_id(state.leader_id)
});
let is_in_unshared_view = follower_state.map_or(false, |state| {
state.active_view_id.is_some_and(|view_id| {
!state.items_by_leader_view_id.contains_key(&view_id)
})
});
let mut leader_border = None;
let mut leader_status_box = None;
let mut leader_join_data = None;
@@ -198,7 +206,14 @@ impl Member {
project_id: leader_project_id,
} => {
if Some(leader_project_id) == project.read(cx).remote_id() {
None
if is_in_unshared_view {
Some(Label::new(format!(
"{} is in an unshared pane",
leader.user.github_login
)))
} else {
None
}
} else {
leader_join_data = Some((leader_project_id, leader.user.id));
Some(Label::new(format!(

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.121.0"
version = "0.121.1"
publish = false
license = "GPL-3.0-or-later"

View File

@@ -1 +1 @@
dev
preview

View File

@@ -11,6 +11,7 @@ use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use env_logger::Builder;
use fs::RealFs;
use fsevent::StreamFlags;
use futures::StreamExt;
use gpui::{App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
use isahc::{prelude::Configurable, Request};
@@ -171,35 +172,8 @@ fn main() {
);
assistant::init(cx);
// TODO: Should we be loading the themes in a different spot?
cx.spawn({
let fs = fs.clone();
|cx| async move {
if let Some(theme_registry) =
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
{
if let Some(()) = theme_registry
.load_user_themes(&paths::THEMES_DIR.clone(), fs)
.await
.log_err()
{
cx.update(|cx| {
let mut theme_settings = ThemeSettings::get_global(cx).clone();
if let Some(requested_theme) = theme_settings.requested_theme.clone() {
if let Some(_theme) =
theme_settings.switch_theme(&requested_theme, cx)
{
ThemeSettings::override_global(theme_settings, cx);
}
}
})
.log_err();
}
}
}
})
.detach();
load_user_themes_in_background(fs.clone(), cx);
watch_themes(fs.clone(), cx);
cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
.detach();
@@ -899,6 +873,81 @@ fn load_embedded_fonts(cx: &AppContext) {
.unwrap();
}
/// Spawns a background task to load the user themes from the themes directory.
fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
cx.spawn({
let fs = fs.clone();
|cx| async move {
if let Some(theme_registry) =
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
{
if let Some(()) = theme_registry
.load_user_themes(&paths::THEMES_DIR.clone(), fs)
.await
.log_err()
{
cx.update(|cx| {
let mut theme_settings = ThemeSettings::get_global(cx).clone();
if let Some(requested_theme) = theme_settings.requested_theme.clone() {
if let Some(_theme) = theme_settings.switch_theme(&requested_theme, cx)
{
ThemeSettings::override_global(theme_settings, cx);
}
}
})
.log_err();
}
}
}
})
.detach();
}
/// Spawns a background task to watch the themes directory for changes.
fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
cx.spawn(|cx| async move {
let mut events = fs
.watch(&paths::THEMES_DIR.clone(), Duration::from_millis(100))
.await;
while let Some(events) = events.next().await {
for event in events {
if event.flags.contains(StreamFlags::ITEM_REMOVED) {
// Theme was removed, don't need to reload.
// We may want to remove the theme from the registry, in this case.
} else {
if let Some(theme_registry) =
cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
{
if let Some(()) = theme_registry
.load_user_theme(&event.path, fs.clone())
.await
.log_err()
{
cx.update(|cx| {
let mut theme_settings = ThemeSettings::get_global(cx).clone();
if let Some(requested_theme) =
theme_settings.requested_theme.clone()
{
if let Some(_theme) =
theme_settings.switch_theme(&requested_theme, cx)
{
ThemeSettings::override_global(theme_settings, cx);
}
}
})
.log_err();
}
}
}
}
}
})
.detach()
}
async fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>) {
let reload_debounce = Duration::from_millis(250);