Compare commits

..

1 Commits

Author SHA1 Message Date
Conrad Irwin
d797750f74 Disable binary downloads
Co-Authored-By: Mikayla <mikayla@zed.dev>
Co-Authored-By: Marshall <marshall@zed.dev>
2024-07-09 16:39:35 -06:00
92 changed files with 2686 additions and 5604 deletions

View File

@@ -319,6 +319,7 @@ jobs:
- name: Upload app bundle to release
uses: softprops/action-gh-release@v1
if: ${{ env.RELEASE_CHANNEL == 'preview' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}

11
Cargo.lock generated
View File

@@ -377,7 +377,6 @@ dependencies = [
"cargo_toml",
"chrono",
"client",
"clock",
"collections",
"command_palette_hooks",
"ctor",
@@ -420,7 +419,6 @@ dependencies = [
"telemetry_events",
"terminal",
"terminal_view",
"text",
"theme",
"tiktoken-rs",
"toml 0.8.10",
@@ -2407,7 +2405,6 @@ version = "0.1.0"
dependencies = [
"chrono",
"parking_lot",
"serde",
"smallvec",
]
@@ -2466,7 +2463,6 @@ version = "0.44.0"
dependencies = [
"anthropic",
"anyhow",
"assistant",
"async-trait",
"async-tungstenite",
"audio",
@@ -3936,7 +3932,6 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
"snippet_provider",
"task",
"theme",
"toml 0.8.10",
@@ -6802,6 +6797,7 @@ dependencies = [
"async_zip",
"futures 0.3.28",
"http 0.1.0",
"language",
"log",
"paths",
"semver",
@@ -8274,7 +8270,6 @@ dependencies = [
"assistant",
"editor",
"gpui",
"repl",
"search",
"settings",
"ui",
@@ -9873,7 +9868,6 @@ dependencies = [
"fs",
"futures 0.3.28",
"gpui",
"parking_lot",
"serde",
"serde_json",
"snippet",
@@ -13586,7 +13580,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.145.0"
version = "0.144.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -13661,7 +13655,6 @@ dependencies = [
"settings",
"simplelog",
"smol",
"snippet_provider",
"supermaven",
"tab_switcher",
"task",

View File

@@ -518,7 +518,7 @@ single_range_in_vec_init = "allow"
# There are a bunch of rules currently failing in the `style` group, so
# allow all of those, for now.
style = { level = "allow", priority = -1 }
style = "allow"
# Individual rules that have violations in the codebase:
almost_complete_range = "allow"

View File

@@ -1,14 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_32_58)">
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3999 21.9C12.3227 22.2159 14.2958 21.9632 16.0769 21.173C17.858 20.3827 19.3694 19.0893 20.4254 17.4517C21.4814 15.8142 22.036 13.9037 22.021 11.9553C22.006 10.0068 21.422 8.10512 20.3409 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.4998 2.10002C11.5849 1.8076 9.62631 2.07763 7.86198 2.87732C6.09765 3.677 4.60356 4.9719 3.56126 6.60468C2.51896 8.23745 1.97332 10.1378 1.99063 12.0748C2.00795 14.0118 2.58749 15.9021 3.65882 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_32_58">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,20 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_39_129)">
<path d="M22.0209 11.9553C22.0059 10.0068 21.4219 8.10512 20.3408 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.1001 2.18C11.355 1.93537 12.1493 1.93674 13.5027 2.10594" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.8198 10.1C22.0644 11.3548 22.0644 12.6451 21.8198 13.9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20.2898 17.6C19.5716 18.6622 18.6548 19.5757 17.5898 20.29" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.9008 21.82C12.6459 22.0644 11.6432 22.1543 10.3883 21.91" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.18005 13.9C1.93543 12.6451 1.93543 11.3548 2.18005 10.1" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.70996 6.40002C4.42822 5.33775 5.34503 4.42433 6.40996 3.71002" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.99072 12.0748C2.00804 14.0118 2.58758 15.9021 3.65891 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_39_129">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,15 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_32_70)">
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3999 21.9C12.3227 22.2159 14.2958 21.9632 16.0769 21.173C17.858 20.3827 19.3694 19.0893 20.4254 17.4517C21.4814 15.8142 22.036 13.9037 22.021 11.9553C22.006 10.0068 21.422 8.10512 20.3409 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.4998 2.10002C11.5849 1.8076 9.62631 2.07763 7.86198 2.87732C6.09765 3.677 4.60356 4.9719 3.56126 6.60468C2.51896 8.23745 1.97332 10.1378 1.99063 12.0748C2.00795 14.0118 2.58749 15.9021 3.65882 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 15V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 15V9" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_32_70">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,14 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_32_64)">
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3999 21.9C12.3227 22.2159 14.2958 21.9632 16.0769 21.173C17.858 20.3827 19.3694 19.0893 20.4254 17.4517C21.4814 15.8142 22.036 13.9037 22.021 11.9553C22.006 10.0068 21.422 8.10512 20.3409 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.4998 2.10002C11.5849 1.8076 9.62631 2.07763 7.86198 2.87732C6.09765 3.677 4.60356 4.9719 3.56126 6.60468C2.51896 8.23745 1.97332 10.1378 1.99063 12.0748C2.00795 14.0118 2.58749 15.9021 3.65882 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 8.56055C10 8.32095 10.267 8.17803 10.4664 8.31094L15.6256 11.7504C15.8037 11.8691 15.8037 12.1309 15.6256 12.2496L10.4664 15.6891C10.267 15.822 10 15.6791 10 15.4394V8.56055Z" fill="white" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_32_64">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -356,7 +356,7 @@
"alt-7": ["workspace::ActivatePane", 6],
"alt-8": ["workspace::ActivatePane", 7],
"alt-9": ["workspace::ActivatePane", 8],
"ctrl-alt-b": "workspace::ToggleRightDock",
"ctrl-alt-b": "workspace::ToggleLeftDock",
"ctrl-b": "workspace::ToggleLeftDock",
"ctrl-j": "workspace::ToggleBottomDock",
"ctrl-alt-y": "workspace::CloseAllDocks",

View File

@@ -569,7 +569,7 @@
}
},
{
"context": "Editor && jupyter && !ContextEditor",
"context": "Editor && jupyter",
"bindings": {
"cmd-enter": "repl::Run"
}

View File

@@ -17,7 +17,6 @@
"j": "vim::Down",
"down": "vim::Down",
"enter": "vim::NextLineStart",
"ctrl-m": "vim::NextLineStart",
"tab": "vim::Tab",
"shift-tab": "vim::Tab",
"k": "vim::Up",

View File

@@ -83,6 +83,9 @@
"confirm_quit": false,
// Whether to restore last closed project when fresh Zed instance is opened.
"restore_on_startup": "last_workspace",
// Whether zed should automatically download binaries to run
// language servers if they are missing.
"download_missing_binaries": true,
// Size of the drop target in the editor.
"drop_target_size": 0.2,
// Whether the window should be closed when using 'close active item' on a window with no tabs.
@@ -128,14 +131,7 @@
// The default number of lines to expand excerpts in the multibuffer by.
"expand_excerpt_lines": 3,
// Globs to match against file paths to determine if a file is private.
"private_files": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/secrets.yml"
],
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,

View File

@@ -491,7 +491,7 @@
"info": "#5c78e2ff",
"info.background": "#e2e2faff",
"info.border": "#cbcdf6ff",
"modified": "#a47a23ff",
"modified": "#dec184ff",
"modified.background": "#faf2e6ff",
"modified.border": "#f4e7d1ff",
"predictive": "#9b9ec6ff",

View File

@@ -12,14 +12,6 @@ workspace = true
path = "src/assistant.rs"
doctest = false
[features]
test-support = [
"editor/test-support",
"language/test-support",
"project/test-support",
"text/test-support",
]
[dependencies]
anthropic = { workspace = true, features = ["schemars"] }
anyhow.workspace = true
@@ -29,7 +21,6 @@ breadcrumbs.workspace = true
cargo_toml.workspace = true
chrono.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
editor.workspace = true
@@ -81,9 +72,7 @@ picker.workspace = true
ctor.workspace = true
editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
language = { workspace = true, features = ["test-support"] }
log.workspace = true
project = { workspace = true, features = ["test-support"] }
rand.workspace = true
text = { workspace = true, features = ["test-support"] }
unindent.workspace = true

View File

@@ -1,8 +1,7 @@
pub mod assistant_panel;
pub mod assistant_settings;
mod completion_provider;
mod context;
pub mod context_store;
mod context_store;
mod inline_assistant;
mod model_selector;
mod prompt_library;
@@ -17,9 +16,8 @@ use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaMo
use assistant_slash_command::SlashCommandRegistry;
use client::{proto, Client};
use command_palette_hooks::CommandPaletteFilter;
pub use completion_provider::*;
pub use context::*;
pub use context_store::*;
pub(crate) use completion_provider::*;
pub(crate) use context_store::*;
use fs::Fs;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
use indexed_docs::IndexedDocsRegistry;
@@ -59,14 +57,10 @@ actions!(
]
);
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct MessageId(clock::Lamport);
impl MessageId {
pub fn as_u64(self) -> u64 {
self.0.as_u64()
}
}
#[derive(
Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
struct MessageId(usize);
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
@@ -77,26 +71,8 @@ pub enum Role {
}
impl Role {
pub fn from_proto(role: i32) -> Role {
match proto::LanguageModelRole::from_i32(role) {
Some(proto::LanguageModelRole::LanguageModelUser) => Role::User,
Some(proto::LanguageModelRole::LanguageModelAssistant) => Role::Assistant,
Some(proto::LanguageModelRole::LanguageModelSystem) => Role::System,
Some(proto::LanguageModelRole::LanguageModelTool) => Role::System,
None => Role::User,
}
}
pub fn to_proto(&self) -> proto::LanguageModelRole {
match self {
Role::User => proto::LanguageModelRole::LanguageModelUser,
Role::Assistant => proto::LanguageModelRole::LanguageModelAssistant,
Role::System => proto::LanguageModelRole::LanguageModelSystem,
}
}
pub fn cycle(self) -> Role {
match self {
pub fn cycle(&mut self) {
*self = match self {
Role::User => Role::Assistant,
Role::Assistant => Role::System,
Role::System => Role::User,
@@ -175,7 +151,11 @@ pub struct LanguageModelRequestMessage {
impl LanguageModelRequestMessage {
pub fn to_proto(&self) -> proto::LanguageModelRequestMessage {
proto::LanguageModelRequestMessage {
role: self.role.to_proto() as i32,
role: match self.role {
Role::User => proto::LanguageModelRole::LanguageModelUser,
Role::Assistant => proto::LanguageModelRole::LanguageModelAssistant,
Role::System => proto::LanguageModelRole::LanguageModelSystem,
} as i32,
content: self.content.clone(),
tool_calls: Vec::new(),
tool_call_id: None,
@@ -242,48 +222,19 @@ pub struct LanguageModelChoiceDelta {
pub finish_reason: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum MessageStatus {
#[derive(Clone, Debug, Serialize, Deserialize)]
struct MessageMetadata {
role: Role,
status: MessageStatus,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum MessageStatus {
Pending,
Done,
Error(SharedString),
}
impl MessageStatus {
pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus {
match status.variant {
Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending,
Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done,
Some(proto::context_message_status::Variant::Error(error)) => {
MessageStatus::Error(error.message.into())
}
None => MessageStatus::Pending,
}
}
pub fn to_proto(&self) -> proto::ContextMessageStatus {
match self {
MessageStatus::Pending => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Pending(
proto::context_message_status::Pending {},
)),
},
MessageStatus::Done => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Done(
proto::context_message_status::Done {},
)),
},
MessageStatus::Error(message) => proto::ContextMessageStatus {
variant: Some(proto::context_message_status::Variant::Error(
proto::context_message_status::Error {
message: message.to_string(),
},
)),
},
}
}
}
/// The state pertaining to the Assistant.
#[derive(Default)]
struct Assistant {
@@ -336,7 +287,6 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
})
.detach();
context_store::init(&client);
prompt_library::init(cx);
completion_provider::init(client.clone(), cx);
assistant_slash_command::init(cx);

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
mod anthropic;
mod cloud;
#[cfg(any(test, feature = "test-support"))]
#[cfg(test)]
mod fake;
mod ollama;
mod open_ai;
pub use anthropic::*;
pub use cloud::*;
#[cfg(any(test, feature = "test-support"))]
#[cfg(test)]
pub use fake::*;
pub use ollama::*;
pub use open_ai::*;

View File

@@ -13,6 +13,7 @@ pub struct FakeCompletionProvider {
}
impl FakeCompletionProvider {
#[cfg(test)]
pub fn setup_test(cx: &mut AppContext) -> Self {
use crate::CompletionProvider;
use parking_lot::RwLock;

File diff suppressed because it is too large Load Diff

View File

@@ -1,117 +1,97 @@
use crate::{
Context, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
SavedContextMetadata,
};
use anyhow::{anyhow, Context as _, Result};
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
use clock::ReplicaId;
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
use collections::HashMap;
use fs::Fs;
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, Task, WeakModel};
use language::LanguageRegistry;
use gpui::{AppContext, Model, ModelContext, Task};
use paths::contexts_dir;
use project::Project;
use regex::Regex;
use std::{
cmp::Reverse,
ffi::OsStr,
mem,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use serde::{Deserialize, Serialize};
use std::{cmp::Reverse, ffi::OsStr, path::PathBuf, sync::Arc, time::Duration};
use ui::Context;
use util::{ResultExt, TryFutureExt};
pub fn init(client: &Arc<Client>) {
client.add_model_message_handler(ContextStore::handle_advertise_contexts);
client.add_model_request_handler(ContextStore::handle_open_context);
client.add_model_message_handler(ContextStore::handle_update_context);
client.add_model_request_handler(ContextStore::handle_synchronize_contexts);
#[derive(Serialize, Deserialize)]
pub struct SavedMessage {
pub id: MessageId,
pub start: usize,
}
#[derive(Serialize, Deserialize)]
pub struct SavedContext {
pub id: Option<String>,
pub zed: String,
pub version: String,
pub text: String,
pub messages: Vec<SavedMessage>,
pub message_metadata: HashMap<MessageId, MessageMetadata>,
pub summary: String,
pub slash_command_output_sections: Vec<SlashCommandOutputSection<usize>>,
}
impl SavedContext {
pub const VERSION: &'static str = "0.3.0";
}
#[derive(Serialize, Deserialize)]
pub struct SavedContextV0_2_0 {
pub id: Option<String>,
pub zed: String,
pub version: String,
pub text: String,
pub messages: Vec<SavedMessage>,
pub message_metadata: HashMap<MessageId, MessageMetadata>,
pub summary: String,
}
#[derive(Serialize, Deserialize)]
struct SavedContextV0_1_0 {
id: Option<String>,
zed: String,
version: String,
text: String,
messages: Vec<SavedMessage>,
message_metadata: HashMap<MessageId, MessageMetadata>,
summary: String,
api_url: Option<String>,
model: OpenAiModel,
}
#[derive(Clone)]
pub struct RemoteContextMetadata {
pub id: ContextId,
pub summary: Option<String>,
pub struct SavedContextMetadata {
pub title: String,
pub path: PathBuf,
pub mtime: chrono::DateTime<chrono::Local>,
}
pub struct ContextStore {
contexts: Vec<ContextHandle>,
contexts_metadata: Vec<SavedContextMetadata>,
host_contexts: Vec<RemoteContextMetadata>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
telemetry: Arc<Telemetry>,
_watch_updates: Task<Option<()>>,
client: Arc<Client>,
project: Model<Project>,
project_is_shared: bool,
client_subscription: Option<client::Subscription>,
_project_subscriptions: Vec<gpui::Subscription>,
}
enum ContextHandle {
Weak(WeakModel<Context>),
Strong(Model<Context>),
}
impl ContextHandle {
fn upgrade(&self) -> Option<Model<Context>> {
match self {
ContextHandle::Weak(weak) => weak.upgrade(),
ContextHandle::Strong(strong) => Some(strong.clone()),
}
}
fn downgrade(&self) -> WeakModel<Context> {
match self {
ContextHandle::Weak(weak) => weak.clone(),
ContextHandle::Strong(strong) => strong.downgrade(),
}
}
}
impl ContextStore {
pub fn new(project: Model<Project>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
let fs = project.read(cx).fs().clone();
let languages = project.read(cx).languages().clone();
let telemetry = project.read(cx).client().telemetry().clone();
pub fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
let mut this = Self {
contexts: Vec::new(),
contexts_metadata: Vec::new(),
host_contexts: Vec::new(),
fs,
languages,
telemetry,
_watch_updates: cx.spawn(|this, mut cx| {
async move {
while events.next().await.is_some() {
this.update(&mut cx, |this, cx| this.reload(cx))?
.await
.log_err();
}
anyhow::Ok(())
let this = cx.new_model(|cx: &mut ModelContext<Self>| Self {
contexts_metadata: Vec::new(),
fs,
_watch_updates: cx.spawn(|this, mut cx| {
async move {
while events.next().await.is_some() {
this.update(&mut cx, |this, cx| this.reload(cx))?
.await
.log_err();
}
.log_err()
}),
client_subscription: None,
_project_subscriptions: vec![
cx.observe(&project, Self::handle_project_changed),
cx.subscribe(&project, Self::handle_project_event),
],
project_is_shared: false,
client: project.read(cx).client(),
project: project.clone(),
};
this.handle_project_changed(project, cx);
this.synchronize_contexts(cx);
this
anyhow::Ok(())
}
.log_err()
}),
})?;
this.update(&mut cx, |this, cx| this.reload(cx))?
.await
@@ -120,431 +100,52 @@ impl ContextStore {
})
}
async fn handle_advertise_contexts(
this: Model<Self>,
envelope: TypedEnvelope<proto::AdvertiseContexts>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.host_contexts = envelope
.payload
.contexts
.into_iter()
.map(|context| RemoteContextMetadata {
id: ContextId::from_proto(context.context_id),
summary: context.summary,
})
.collect();
cx.notify();
})
}
async fn handle_open_context(
this: Model<Self>,
envelope: TypedEnvelope<proto::OpenContext>,
mut cx: AsyncAppContext,
) -> Result<proto::OpenContextResponse> {
let context_id = ContextId::from_proto(envelope.payload.context_id);
let operations = this.update(&mut cx, |this, cx| {
if this.project.read(cx).is_remote() {
return Err(anyhow!("only the host contexts can be opened"));
}
let context = this
.loaded_context_for_id(&context_id, cx)
.context("context not found")?;
if context.read(cx).replica_id() != ReplicaId::default() {
return Err(anyhow!("context must be opened via the host"));
}
anyhow::Ok(
context
.read(cx)
.serialize_ops(&ContextVersion::default(), cx),
)
})??;
let operations = operations.await;
Ok(proto::OpenContextResponse {
context: Some(proto::Context { operations }),
})
}
async fn handle_update_context(
this: Model<Self>,
envelope: TypedEnvelope<proto::UpdateContext>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
let context_id = ContextId::from_proto(envelope.payload.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
let operation_proto = envelope.payload.operation.context("invalid operation")?;
let operation = ContextOperation::from_proto(operation_proto)?;
context.update(cx, |context, cx| context.apply_ops([operation], cx))?;
}
Ok(())
})?
}
async fn handle_synchronize_contexts(
this: Model<Self>,
envelope: TypedEnvelope<proto::SynchronizeContexts>,
mut cx: AsyncAppContext,
) -> Result<proto::SynchronizeContextsResponse> {
this.update(&mut cx, |this, cx| {
if this.project.read(cx).is_remote() {
return Err(anyhow!("only the host can synchronize contexts"));
}
let mut local_versions = Vec::new();
for remote_version_proto in envelope.payload.contexts {
let remote_version = ContextVersion::from_proto(&remote_version_proto);
let context_id = ContextId::from_proto(remote_version_proto.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
let context = context.read(cx);
let operations = context.serialize_ops(&remote_version, cx);
local_versions.push(context.version(cx).to_proto(context_id.clone()));
let client = this.client.clone();
let project_id = envelope.payload.project_id;
cx.background_executor()
.spawn(async move {
let operations = operations.await;
for operation in operations {
client.send(proto::UpdateContext {
project_id,
context_id: context_id.to_proto(),
operation: Some(operation),
})?;
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
}
this.advertise_contexts(cx);
anyhow::Ok(proto::SynchronizeContextsResponse {
contexts: local_versions,
})
})?
}
fn handle_project_changed(&mut self, _: Model<Project>, cx: &mut ModelContext<Self>) {
let is_shared = self.project.read(cx).is_shared();
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
if is_shared == was_shared {
return;
}
if is_shared {
self.contexts.retain_mut(|context| {
if let Some(strong_context) = context.upgrade() {
*context = ContextHandle::Strong(strong_context);
true
} else {
false
}
});
let remote_id = self.project.read(cx).remote_id().unwrap();
self.client_subscription = self
.client
.subscribe_to_entity(remote_id)
.log_err()
.map(|subscription| subscription.set_model(&cx.handle(), &mut cx.to_async()));
self.advertise_contexts(cx);
} else {
self.client_subscription = None;
}
}
fn handle_project_event(
&mut self,
_: Model<Project>,
event: &project::Event,
cx: &mut ModelContext<Self>,
) {
match event {
project::Event::Reshared => {
self.advertise_contexts(cx);
}
project::Event::HostReshared | project::Event::Rejoined => {
self.synchronize_contexts(cx);
}
project::Event::DisconnectedFromHost => {
self.contexts.retain_mut(|context| {
if let Some(strong_context) = context.upgrade() {
*context = ContextHandle::Weak(context.downgrade());
strong_context.update(cx, |context, cx| {
if context.replica_id() != ReplicaId::default() {
context.set_capability(language::Capability::ReadOnly, cx);
}
});
true
} else {
false
}
});
self.host_contexts.clear();
cx.notify();
}
_ => {}
}
}
pub fn create(&mut self, cx: &mut ModelContext<Self>) -> Model<Context> {
let context = cx.new_model(|cx| {
Context::local(self.languages.clone(), Some(self.telemetry.clone()), cx)
});
self.register_context(&context, cx);
context
}
pub fn open_local_context(
&mut self,
path: PathBuf,
cx: &ModelContext<Self>,
) -> Task<Result<Model<Context>>> {
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
return Task::ready(Ok(existing_context));
}
pub fn load(&self, path: PathBuf, cx: &AppContext) -> Task<Result<SavedContext>> {
let fs = self.fs.clone();
let languages = self.languages.clone();
let telemetry = self.telemetry.clone();
let load = cx.background_executor().spawn({
let path = path.clone();
async move {
let saved_context = fs.load(&path).await?;
SavedContext::from_json(&saved_context)
}
});
cx.spawn(|this, mut cx| async move {
let saved_context = load.await?;
let context = cx.new_model(|cx| {
Context::deserialize(saved_context, path.clone(), languages, Some(telemetry), cx)
})?;
this.update(&mut cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
existing_context
} else {
this.register_context(&context, cx);
context
}
})
})
}
fn loaded_context_for_path(&self, path: &Path, cx: &AppContext) -> Option<Model<Context>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).path() == Some(path) {
Some(context)
} else {
None
}
})
}
fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option<Model<Context>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).id() == id {
Some(context)
} else {
None
}
})
}
pub fn open_remote_context(
&mut self,
context_id: ContextId,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Context>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote")));
};
if project.is_local() {
return Task::ready(Err(anyhow!("cannot open remote contexts as the host")));
}
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
return Task::ready(Ok(context));
}
let replica_id = project.replica_id();
let capability = project.capability();
let language_registry = self.languages.clone();
let telemetry = self.telemetry.clone();
let request = self.client.request(proto::OpenContext {
project_id,
context_id: context_id.to_proto(),
});
cx.spawn(|this, mut cx| async move {
let response = request.await?;
let context_proto = response.context.context("invalid context")?;
let context = cx.new_model(|cx| {
Context::new(
context_id.clone(),
replica_id,
capability,
language_registry,
Some(telemetry),
cx,
)
})?;
let operations = cx
.background_executor()
.spawn(async move {
context_proto
.operations
.into_iter()
.map(|op| ContextOperation::from_proto(op))
.collect::<Result<Vec<_>>>()
})
.await?;
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))??;
this.update(&mut cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
existing_context
} else {
this.register_context(&context, cx);
this.synchronize_contexts(cx);
context
}
})
})
}
fn register_context(&mut self, context: &Model<Context>, cx: &mut ModelContext<Self>) {
let handle = if self.project_is_shared {
ContextHandle::Strong(context.clone())
} else {
ContextHandle::Weak(context.downgrade())
};
self.contexts.push(handle);
self.advertise_contexts(cx);
cx.subscribe(context, Self::handle_context_event).detach();
}
fn handle_context_event(
&mut self,
context: Model<Context>,
event: &ContextEvent,
cx: &mut ModelContext<Self>,
) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
};
match event {
ContextEvent::SummaryChanged => {
self.advertise_contexts(cx);
}
ContextEvent::Operation(operation) => {
let context_id = context.read(cx).id().to_proto();
let operation = operation.to_proto();
self.client
.send(proto::UpdateContext {
project_id,
context_id,
operation: Some(operation),
})
.log_err();
}
_ => {}
}
}
fn advertise_contexts(&self, cx: &AppContext) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
};
// For now, only the host can advertise their open contexts.
if self.project.read(cx).is_remote() {
return;
}
let contexts = self
.contexts
.iter()
.rev()
.filter_map(|context| {
let context = context.upgrade()?.read(cx);
if context.replica_id() == ReplicaId::default() {
Some(proto::ContextMetadata {
context_id: context.id().to_proto(),
summary: context.summary().map(|summary| summary.text.clone()),
})
} else {
None
}
})
.collect();
self.client
.send(proto::AdvertiseContexts {
project_id,
contexts,
})
.ok();
}
fn synchronize_contexts(&mut self, cx: &mut ModelContext<Self>) {
let Some(project_id) = self.project.read(cx).remote_id() else {
return;
};
let contexts = self
.contexts
.iter()
.filter_map(|context| {
let context = context.upgrade()?.read(cx);
if context.replica_id() != ReplicaId::default() {
Some(context.version(cx).to_proto(context.id().clone()))
} else {
None
}
})
.collect();
let client = self.client.clone();
let request = self.client.request(proto::SynchronizeContexts {
project_id,
contexts,
});
cx.spawn(|this, cx| async move {
let response = request.await?;
let mut context_ids = Vec::new();
let mut operations = Vec::new();
this.read_with(&cx, |this, cx| {
for context_version_proto in response.contexts {
let context_version = ContextVersion::from_proto(&context_version_proto);
let context_id = ContextId::from_proto(context_version_proto.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
context_ids.push(context_id);
operations.push(context.read(cx).serialize_ops(&context_version, cx));
cx.background_executor().spawn(async move {
let saved_context = fs.load(&path).await?;
let saved_context_json = serde_json::from_str::<serde_json::Value>(&saved_context)?;
match saved_context_json
.get("version")
.ok_or_else(|| anyhow!("version not found"))?
{
serde_json::Value::String(version) => match version.as_str() {
SavedContext::VERSION => {
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
}
}
})?;
let operations = futures::future::join_all(operations).await;
for (context_id, operations) in context_ids.into_iter().zip(operations) {
for operation in operations {
client.send(proto::UpdateContext {
project_id,
context_id: context_id.to_proto(),
operation: Some(operation),
})?;
}
"0.2.0" => {
let saved_context =
serde_json::from_value::<SavedContextV0_2_0>(saved_context_json)?;
Ok(SavedContext {
id: saved_context.id,
zed: saved_context.zed,
version: saved_context.version,
text: saved_context.text,
messages: saved_context.messages,
message_metadata: saved_context.message_metadata,
summary: saved_context.summary,
slash_command_output_sections: Vec::new(),
})
}
"0.1.0" => {
let saved_context =
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
Ok(SavedContext {
id: saved_context.id,
zed: saved_context.zed,
version: saved_context.version,
text: saved_context.text,
messages: saved_context.messages,
message_metadata: saved_context.message_metadata,
summary: saved_context.summary,
slash_command_output_sections: Vec::new(),
})
}
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
},
_ => Err(anyhow!("version not found on saved context")),
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
pub fn search(&self, query: String, cx: &AppContext) -> Task<Vec<SavedContextMetadata>> {
@@ -577,10 +178,6 @@ impl ContextStore {
})
}
pub fn host_contexts(&self) -> &[RemoteContextMetadata] {
&self.host_contexts
}
fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move {

View File

@@ -3,6 +3,7 @@ use crate::{
InlineAssist, InlineAssistant, LanguageModelRequest, LanguageModelRequestMessage, Role,
};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandRegistry;
use chrono::{DateTime, Utc};
use collections::{HashMap, HashSet};
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
@@ -447,6 +448,7 @@ impl PromptLibrary {
self.set_active_prompt(Some(prompt_id), cx);
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
let language_registry = self.language_registry.clone();
let commands = SlashCommandRegistry::global(cx);
let prompt = self.store.load(prompt_id);
self.pending_load = cx.spawn(|this, mut cx| async move {
let prompt = prompt.await;
@@ -475,7 +477,7 @@ impl PromptLibrary {
editor.set_use_modal_editing(false);
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
editor.set_completion_provider(Box::new(
SlashCommandCompletionProvider::new(None, None),
SlashCommandCompletionProvider::new(commands, None, None),
));
if focus {
editor.focus(cx);

View File

@@ -31,6 +31,7 @@ pub mod tabs_command;
pub mod term_command;
pub(crate) struct SlashCommandCompletionProvider {
commands: Arc<SlashCommandRegistry>,
cancel_flag: Mutex<Arc<AtomicBool>>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
@@ -45,12 +46,14 @@ pub(crate) struct SlashCommandLine {
impl SlashCommandCompletionProvider {
pub fn new(
commands: Arc<SlashCommandRegistry>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
) -> Self {
Self {
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
editor,
commands,
workspace,
}
}
@@ -62,8 +65,8 @@ impl SlashCommandCompletionProvider {
name_range: Range<Anchor>,
cx: &mut WindowContext,
) -> Task<Result<Vec<project::Completion>>> {
let commands = SlashCommandRegistry::global(cx);
let candidates = commands
let candidates = self
.commands
.command_names()
.into_iter()
.enumerate()
@@ -73,6 +76,7 @@ impl SlashCommandCompletionProvider {
char_bag: def.as_ref().into(),
})
.collect::<Vec<_>>();
let commands = self.commands.clone();
let command_name = command_name.to_string();
let editor = self.editor.clone();
let workspace = self.workspace.clone();
@@ -151,8 +155,7 @@ impl SlashCommandCompletionProvider {
flag.store(true, SeqCst);
*flag = new_cancel_flag.clone();
let commands = SlashCommandRegistry::global(cx);
if let Some(command) = commands.command(command_name) {
if let Some(command) = self.commands.command(command_name) {
let completions = command.complete_argument(
argument,
new_cancel_flag.clone(),

View File

@@ -67,7 +67,7 @@ pub struct SlashCommandOutput {
pub run_commands_in_text: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize)]
pub struct SlashCommandOutputSection<T> {
pub range: Range<T>,
pub icon: IconName,

View File

@@ -72,7 +72,7 @@ impl Render for Breadcrumbs {
.into_any()
});
let breadcrumbs = Itertools::intersperse_with(highlighted_segments, || {
Label::new("").color(Color::Placeholder).into_any_element()
Label::new("").color(Color::Muted).into_any_element()
});
let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
@@ -83,7 +83,7 @@ impl Render for Breadcrumbs {
Some(editor) => element.child(
ButtonLike::new("toggle outline view")
.child(breadcrumbs_stack)
.style(ButtonStyle::Transparent)
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
if let Some(editor) = editor.upgrade() {
outline::toggle(editor, &editor::actions::ToggleOutline, cx)

View File

@@ -217,9 +217,6 @@ pub struct Client {
>,
>,
>,
#[cfg(any(test, feature = "test-support"))]
rpc_url: RwLock<Option<Url>>,
}
#[derive(Error, Debug)]
@@ -530,8 +527,6 @@ impl Client {
authenticate: Default::default(),
#[cfg(any(test, feature = "test-support"))]
establish_connection: Default::default(),
#[cfg(any(test, feature = "test-support"))]
rpc_url: RwLock::default(),
})
}
@@ -589,12 +584,6 @@ impl Client {
self
}
#[cfg(any(test, feature = "test-support"))]
pub fn override_rpc_url(&self, url: Url) -> &Self {
*self.rpc_url.write() = Some(url);
self
}
pub fn global(cx: &AppContext) -> Arc<Self> {
cx.global::<GlobalClient>().0.clone()
}
@@ -1097,50 +1086,38 @@ impl Client {
self.establish_websocket_connection(credentials, cx)
}
fn rpc_url(
&self,
async fn get_rpc_url(
http: Arc<HttpClientWithUrl>,
release_channel: Option<ReleaseChannel>,
) -> impl Future<Output = Result<Url>> {
#[cfg(any(test, feature = "test-support"))]
let url_override = self.rpc_url.read().clone();
async move {
#[cfg(any(test, feature = "test-support"))]
if let Some(url) = url_override {
return Ok(url);
}
if let Some(url) = &*ZED_RPC_URL {
return Url::parse(url).context("invalid rpc url");
}
let mut url = http.build_url("/rpc");
if let Some(preview_param) =
release_channel.and_then(|channel| channel.release_query_param())
{
url += "?";
url += preview_param;
}
let response = http.get(&url, Default::default(), false).await?;
let collab_url = if response.status().is_redirection() {
response
.headers()
.get("Location")
.ok_or_else(|| anyhow!("missing location header in /rpc response"))?
.to_str()
.map_err(EstablishConnectionError::other)?
.to_string()
} else {
Err(anyhow!(
"unexpected /rpc response status {}",
response.status()
))?
};
Url::parse(&collab_url).context("invalid rpc url")
) -> Result<Url> {
if let Some(url) = &*ZED_RPC_URL {
return Url::parse(url).context("invalid rpc url");
}
let mut url = http.build_url("/rpc");
if let Some(preview_param) =
release_channel.and_then(|channel| channel.release_query_param())
{
url += "?";
url += preview_param;
}
let response = http.get(&url, Default::default(), false).await?;
let collab_url = if response.status().is_redirection() {
response
.headers()
.get("Location")
.ok_or_else(|| anyhow!("missing location header in /rpc response"))?
.to_str()
.map_err(EstablishConnectionError::other)?
.to_string()
} else {
Err(anyhow!(
"unexpected /rpc response status {}",
response.status()
))?
};
Url::parse(&collab_url).context("invalid rpc url")
}
fn establish_websocket_connection(
@@ -1167,9 +1144,8 @@ impl Client {
);
let http = self.http.clone();
let rpc_url = self.rpc_url(http, release_channel);
cx.background_executor().spawn(async move {
let mut rpc_url = rpc_url.await?;
let mut rpc_url = Self::get_rpc_url(http, release_channel).await?;
let rpc_host = rpc_url
.host_str()
.zip(rpc_url.port_or_known_default())
@@ -1210,7 +1186,6 @@ impl Client {
cx: &AsyncAppContext,
) -> Task<Result<Credentials>> {
let http = self.http.clone();
let this = self.clone();
cx.spawn(|cx| async move {
let background = cx.background_executor().clone();
@@ -1240,8 +1215,7 @@ impl Client {
{
eprintln!("authenticate as admin {login}, {token}");
return this
.authenticate_as_admin(http, login.clone(), token.clone())
return Self::authenticate_as_admin(http, login.clone(), token.clone())
.await;
}
@@ -1329,7 +1303,6 @@ impl Client {
}
async fn authenticate_as_admin(
self: &Arc<Self>,
http: Arc<HttpClientWithUrl>,
login: String,
mut api_token: String,
@@ -1346,7 +1319,7 @@ impl Client {
// Use the collab server's admin API to retrieve the id
// of the impersonated user.
let mut url = self.rpc_url(http.clone(), None).await?;
let mut url = Self::get_rpc_url(http.clone(), None).await?;
url.set_path("/user");
url.set_query(Some(&format!("github_login={login}")));
let request = Request::get(url.as_str())

View File

@@ -18,5 +18,4 @@ test-support = ["dep:parking_lot"]
[dependencies]
chrono.workspace = true
parking_lot = { workspace = true, optional = true }
serde.workspace = true
smallvec.workspace = true

View File

@@ -1,6 +1,5 @@
mod system_clock;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{
cmp::{self, Ordering},
@@ -17,7 +16,7 @@ pub type Seq = u32;
/// A [Lamport timestamp](https://en.wikipedia.org/wiki/Lamport_timestamp),
/// used to determine the ordering of events in the editor.
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
pub struct Lamport {
pub replica_id: ReplicaId,
pub value: Seq,
@@ -162,10 +161,6 @@ impl Lamport {
}
}
pub fn as_u64(self) -> u64 {
((self.value as u64) << 32) | (self.replica_id as u64)
}
pub fn tick(&mut self) -> Self {
let timestamp = *self;
self.value += 1;

View File

@@ -71,7 +71,6 @@ util.workspace = true
uuid.workspace = true
[dev-dependencies]
assistant = { workspace = true, features = ["test-support"] }
async-trait.workspace = true
audio.workspace = true
call = { workspace = true, features = ["test-support"] }

View File

@@ -562,7 +562,7 @@ fn test_fuzzy_like_string() {
assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
}
#[cfg(target_os = "macos")]
#[cfg(target = "macos")]
#[gpui::test]
async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
let test_db = tests::TestDb::postgres(cx.executor());

View File

@@ -595,14 +595,6 @@ impl Server {
.add_message_handler(user_message_handler(acknowledge_channel_message))
.add_message_handler(user_message_handler(acknowledge_buffer_version))
.add_request_handler(user_handler(get_supermaven_api_key))
.add_request_handler(user_handler(
forward_mutating_project_request::<proto::OpenContext>,
))
.add_request_handler(user_handler(
forward_mutating_project_request::<proto::SynchronizeContexts>,
))
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
.add_message_handler(update_context)
.add_streaming_request_handler({
let app_state = app_state.clone();
move |request, response, session| {
@@ -3064,53 +3056,6 @@ async fn update_buffer(
Ok(())
}
async fn update_context(message: proto::UpdateContext, session: Session) -> Result<()> {
let project_id = ProjectId::from_proto(message.project_id);
let operation = message.operation.as_ref().context("invalid operation")?;
let capability = match operation.variant.as_ref() {
Some(proto::context_operation::Variant::BufferOperation(buffer_op)) => {
if let Some(buffer_op) = buffer_op.operation.as_ref() {
match buffer_op.variant {
None | Some(proto::operation::Variant::UpdateSelections(_)) => {
Capability::ReadOnly
}
_ => Capability::ReadWrite,
}
} else {
Capability::ReadWrite
}
}
Some(_) => Capability::ReadWrite,
None => Capability::ReadOnly,
};
let guard = session
.db()
.await
.connections_for_buffer_update(
project_id,
session.principal_id(),
session.connection_id,
capability,
)
.await?;
let (host, guests) = &*guard;
broadcast(
Some(session.connection_id),
guests.iter().chain([host]).copied(),
|connection_id| {
session
.peer
.forward_send(session.connection_id, connection_id, message.clone())
},
);
Ok(())
}
/// Notify other participants that a project has been updated.
async fn broadcast_project_message_from_host<T: EntityMessage<Entity = ShareProject>>(
request: T,

View File

@@ -6,7 +6,6 @@ use crate::{
},
};
use anyhow::{anyhow, Result};
use assistant::ContextStore;
use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet};
@@ -6450,123 +6449,3 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
assert!(!pane.can_navigate_forward());
});
}
#[gpui::test(iterations = 10)]
async fn test_context_collaboration_with_reconnect(
executor: BackgroundExecutor,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
let mut server = TestServer::start(executor.clone()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
client_a.fs().insert_tree("/a", Default::default()).await;
let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
// Client A sees that a guest has joined.
executor.run_until_parked();
project_a.read_with(cx_a, |project, _| {
assert_eq!(project.collaborators().len(), 1);
});
project_b.read_with(cx_b, |project, _| {
assert_eq!(project.collaborators().len(), 1);
});
let context_store_a = cx_a
.update(|cx| ContextStore::new(project_a.clone(), cx))
.await
.unwrap();
let context_store_b = cx_b
.update(|cx| ContextStore::new(project_b.clone(), cx))
.await
.unwrap();
// Client A creates a new context.
let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
executor.run_until_parked();
// Client B retrieves host's contexts and joins one.
let context_b = context_store_b
.update(cx_b, |store, cx| {
let host_contexts = store.host_contexts().to_vec();
assert_eq!(host_contexts.len(), 1);
store.open_remote_context(host_contexts[0].id.clone(), cx)
})
.await
.unwrap();
// Host and guest make changes
context_a.update(cx_a, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Host change\n")], None, cx)
})
});
context_b.update(cx_b, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Guest change\n")], None, cx)
})
});
executor.run_until_parked();
assert_eq!(
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
"Guest change\nHost change\n"
);
assert_eq!(
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
"Guest change\nHost change\n"
);
// Disconnect client A and make some changes while disconnected.
server.disconnect_client(client_a.peer_id().unwrap());
server.forbid_connections();
context_a.update(cx_a, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Host offline change\n")], None, cx)
})
});
context_b.update(cx_b, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Guest offline change\n")], None, cx)
})
});
executor.run_until_parked();
assert_eq!(
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
"Host offline change\nGuest change\nHost change\n"
);
assert_eq!(
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
"Guest offline change\nGuest change\nHost change\n"
);
// Allow client A to reconnect and verify that contexts converge.
server.allow_connections();
executor.advance_clock(RECEIVE_TIMEOUT);
assert_eq!(
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
);
assert_eq!(
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
);
// Client A disconnects without being able to reconnect. Context B becomes readonly.
server.forbid_connections();
server.disconnect_client(client_a.peer_id().unwrap());
executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
context_b.read_with(cx_b, |context, cx| {
assert!(context.buffer().read(cx).read_only());
});
}

View File

@@ -294,8 +294,6 @@ impl TestServer {
menu::init();
dev_server_projects::init(client.clone(), cx);
settings::KeymapFile::load_asset(os_keymap, cx).unwrap();
assistant::FakeCompletionProvider::setup_test(cx);
assistant::context_store::init(&client);
});
client

View File

@@ -11093,7 +11093,6 @@ impl Editor {
if *singleton_buffer_edited {
if let Some(project) = &self.project {
let project = project.read(cx);
#[allow(clippy::mutable_key_type)]
let languages_affected = multibuffer
.read(cx)
.all_buffers()
@@ -11768,7 +11767,7 @@ fn snippet_completions(
let language = buffer.read(cx).language_at(buffer_position);
let language_name = language.as_ref().map(|language| language.lsp_id());
let snippet_store = project.snippets().read(cx);
let snippets = snippet_store.snippets_for(language_name, cx);
let snippets = snippet_store.snippets_for(language_name);
if snippets.is_empty() {
return vec![];

View File

@@ -42,7 +42,6 @@ semantic_version.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
snippet_provider.workspace = true
theme.workspace = true
toml.workspace = true
ui.workspace = true

View File

@@ -526,11 +526,6 @@ fn populate_defaults(manifest: &mut ExtensionManifest, extension_path: &Path) ->
}
}
let snippets_json_path = extension_path.join("snippets.json");
if snippets_json_path.exists() {
manifest.snippets = Some(snippets_json_path);
}
// For legacy extensions on the v0 schema (aka, using `extension.json`), we want to populate the grammars in
// the manifest using the contents of the `grammars` directory.
if manifest.schema_version.is_v0() {

View File

@@ -78,8 +78,6 @@ pub struct ExtensionManifest {
pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
#[serde(default)]
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
#[serde(default)]
pub snippets: Option<PathBuf>,
}
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -208,6 +206,5 @@ fn manifest_from_old_manifest(
language_servers: Default::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
}
}

View File

@@ -44,7 +44,6 @@ use release_channel::ReleaseChannel;
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use settings::Settings;
use snippet_provider::SnippetRegistry;
use std::ops::RangeInclusive;
use std::str::FromStr;
use std::{
@@ -116,7 +115,6 @@ pub struct ExtensionStore {
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
snippet_registry: Arc<SnippetRegistry>,
modified_extensions: HashSet<Arc<str>>,
wasm_host: Arc<WasmHost>,
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
@@ -195,7 +193,6 @@ pub fn init(
theme_registry,
SlashCommandRegistry::global(cx),
IndexedDocsRegistry::global(cx),
SnippetRegistry::global(cx),
cx,
)
});
@@ -230,7 +227,6 @@ impl ExtensionStore {
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
snippet_registry: Arc<SnippetRegistry>,
cx: &mut ModelContext<Self>,
) -> Self {
let work_dir = extensions_dir.join("work");
@@ -263,7 +259,6 @@ impl ExtensionStore {
theme_registry,
slash_command_registry,
indexed_docs_registry,
snippet_registry,
reload_tx,
tasks: Vec::new(),
};
@@ -1050,7 +1045,6 @@ impl ExtensionStore {
.collect::<Vec<_>>();
let mut grammars_to_add = Vec::new();
let mut themes_to_add = Vec::new();
let mut snippets_to_add = Vec::new();
for extension_id in &extensions_to_load {
let Some(extension) = new_index.extensions.get(extension_id) else {
continue;
@@ -1068,11 +1062,6 @@ impl ExtensionStore {
path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
path
}));
snippets_to_add.extend(extension.manifest.snippets.iter().map(|snippets_path| {
let mut path = self.installed_dir.clone();
path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
path
}));
}
self.language_registry
@@ -1108,7 +1097,6 @@ impl ExtensionStore {
let wasm_host = self.wasm_host.clone();
let root_dir = self.installed_dir.clone();
let theme_registry = self.theme_registry.clone();
let snippet_registry = self.snippet_registry.clone();
let extension_entries = extensions_to_load
.iter()
.filter_map(|name| new_index.extensions.get(name).cloned())
@@ -1129,15 +1117,6 @@ impl ExtensionStore {
.await
.log_err();
}
for snippets_path in &snippets_to_add {
if let Some(snippets_contents) = fs.load(snippets_path).await.log_err()
{
snippet_registry
.register_snippets(snippets_path, &snippets_contents)
.log_err();
}
}
}
})
.await;

View File

@@ -19,7 +19,6 @@ use parking_lot::Mutex;
use project::{Project, DEFAULT_COMPLETION_CONTEXT};
use serde_json::json;
use settings::{Settings as _, SettingsStore};
use snippet_provider::SnippetRegistry;
use std::{
ffi::OsString,
path::{Path, PathBuf},
@@ -161,7 +160,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
}),
dev: false,
},
@@ -187,7 +185,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
}),
dev: false,
},
@@ -261,7 +258,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let slash_command_registry = SlashCommandRegistry::new();
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
let snippet_registry = Arc::new(SnippetRegistry::new());
let node_runtime = FakeNodeRuntime::new();
let store = cx.new_model(|cx| {
@@ -276,7 +272,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
theme_registry.clone(),
slash_command_registry.clone(),
indexed_docs_registry.clone(),
snippet_registry.clone(),
cx,
)
});
@@ -350,7 +345,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
}),
dev: false,
},
@@ -402,7 +396,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
theme_registry.clone(),
slash_command_registry,
indexed_docs_registry,
snippet_registry,
cx,
)
});
@@ -484,7 +477,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let slash_command_registry = SlashCommandRegistry::new();
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
let snippet_registry = Arc::new(SnippetRegistry::new());
let node_runtime = FakeNodeRuntime::new();
let mut status_updates = language_registry.language_server_binary_statuses();
@@ -576,7 +568,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
theme_registry.clone(),
slash_command_registry,
indexed_docs_registry,
snippet_registry,
cx,
)
});

View File

@@ -18,6 +18,7 @@ use language::LanguageRegistry;
use node_runtime::NodeRuntime;
use release_channel::ReleaseChannel;
use semantic_version::SemanticVersion;
use std::sync::atomic::AtomicBool;
use std::{
path::{Path, PathBuf},
sync::{Arc, OnceLock},
@@ -39,6 +40,7 @@ pub(crate) struct WasmHost {
pub(crate) work_dir: PathBuf,
_main_thread_message_task: Task<()>,
main_thread_message_tx: mpsc::UnboundedSender<MainThreadCall>,
enable_binary_downloads: bool,
}
#[derive(Clone)]
@@ -91,7 +93,11 @@ impl WasmHost {
message(&mut cx).await;
}
});
Arc::new(Self {
let enable_binary_downloads =
AtomicBool::new(DownloadConfiguration::get_global(cx).enable_binary_downloads);
let this = Arc::new(Self {
engine: wasm_engine(),
fs,
work_dir,
@@ -101,7 +107,18 @@ impl WasmHost {
release_channel: ReleaseChannel::global(cx),
_main_thread_message_task: task,
main_thread_message_tx: tx,
enable_binary_downloads: enable_binary_downloads.clone(),
});
cx.observe_global::<SettingsStore>(move |cx| {
enable_binary_downloads.store(
DownloadConfiguration::get_global(cx).enable_binary_downloads,
Ordering::SeqCst,
)
})
.detach();
this
}
pub fn load_extension(

View File

@@ -198,6 +198,10 @@ impl nodejs::Host for WasmState {
package_name: String,
version: String,
) -> wasmtime::Result<Result<(), String>> {
if !self.host.enable_binary_downloads.get() {
return Err("binary downloads are disabled".into()).to_wasmtime_result();
}
self.host
.node_runtime
.npm_install_packages(&self.work_dir(), &[(&package_name, &version)])
@@ -372,6 +376,10 @@ impl ExtensionImports for WasmState {
file_type: DownloadedFileType,
) -> wasmtime::Result<Result<(), String>> {
maybe!(async {
if !self.host.enable_binary_downloads.get() {
return Err("binary downloads are disabled".into()).to_wasmtime_result();
}
let path = PathBuf::from(path);
let extension_work_dir = self.host.work_dir.join(self.manifest.id.as_ref());
@@ -435,6 +443,10 @@ impl ExtensionImports for WasmState {
}
async fn make_file_executable(&mut self, path: String) -> wasmtime::Result<Result<(), String>> {
if !self.host.enable_binary_downloads.get() {
return Err("binary downloads are disabled".into()).to_wasmtime_result();
}
#[allow(unused)]
let path = self
.host

View File

@@ -7,7 +7,7 @@ use std::env;
fn main() {
let target = env::var("CARGO_CFG_TARGET_OS");
println!("cargo::rustc-check-cfg=cfg(gles)");
match target.as_deref() {
Ok("macos") => {
#[cfg(target_os = "macos")]

View File

@@ -2,209 +2,63 @@ use gpui::*;
struct WindowContent {
text: SharedString,
bounds: Bounds<Pixels>,
bg: Hsla,
}
impl Render for WindowContent {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let window_bounds = cx.bounds();
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.bg(self.bg)
.bg(rgb(0x1e2025))
.size_full()
.justify_center()
.items_center()
.text_xl()
.text_color(rgb(0xffffff))
.child(self.text.clone())
.child(
div()
.flex()
.flex_col()
.text_sm()
.items_center()
.size_full()
.child(format!(
"origin: {}, {} size: {}, {}",
self.bounds.origin.x,
self.bounds.origin.y,
self.bounds.size.width,
self.bounds.size.height
))
.child(format!(
"cx.bounds() origin: {}, {} size {}, {}",
window_bounds.origin.x,
window_bounds.origin.y,
window_bounds.size.width,
window_bounds.size.height
)),
)
}
}
fn build_window_options(display_id: DisplayId, bounds: Bounds<Pixels>) -> WindowOptions {
WindowOptions {
// Set the bounds of the window in screen coordinates
window_bounds: Some(WindowBounds::Windowed(bounds)),
// Specify the display_id to ensure the window is created on the correct screen
display_id: Some(display_id),
titlebar: None,
window_background: WindowBackgroundAppearance::Transparent,
focus: false,
show: true,
kind: WindowKind::PopUp,
is_movable: false,
app_id: None,
window_min_size: None,
window_decorations: None,
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
// Create several new windows, positioned in the top right corner of each screen
let size = Size {
width: px(350.),
height: px(75.),
};
let margin_offset = px(150.);
for screen in cx.displays() {
let bounds = Bounds {
origin: point(margin_offset, margin_offset),
size,
let options = {
let margin_right = px(16.);
let margin_height = px(-48.);
let size = Size {
width: px(400.),
height: px(72.),
};
let bounds = gpui::Bounds::<Pixels> {
origin: screen.bounds().upper_right()
- point(size.width + margin_right, margin_height),
size,
};
WindowOptions {
// Set the bounds of the window in screen coordinates
window_bounds: Some(WindowBounds::Windowed(bounds)),
// Specify the display_id to ensure the window is created on the correct screen
display_id: Some(screen.id()),
titlebar: None,
window_background: WindowBackgroundAppearance::default(),
focus: false,
show: true,
kind: WindowKind::PopUp,
is_movable: false,
app_id: None,
window_min_size: None,
window_decorations: None,
}
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.open_window(options, |cx| {
cx.new_view(|_| WindowContent {
text: format!("Top Left {:?}", screen.id()).into(),
bg: gpui::red(),
bounds,
})
})
.unwrap();
let bounds = Bounds {
origin: screen.bounds().upper_right()
- point(size.width + margin_offset, -margin_offset),
size,
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.new_view(|_| WindowContent {
text: format!("Top Right {:?}", screen.id()).into(),
bg: gpui::red(),
bounds,
})
})
.unwrap();
let bounds = Bounds {
origin: screen.bounds().lower_left()
- point(-margin_offset, size.height + margin_offset),
size,
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.new_view(|_| WindowContent {
text: format!("Bottom Left {:?}", screen.id()).into(),
bg: gpui::blue(),
bounds,
})
})
.unwrap();
let bounds = Bounds {
origin: screen.bounds().lower_right()
- point(size.width + margin_offset, size.height + margin_offset),
size,
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.new_view(|_| WindowContent {
text: format!("Bottom Right {:?}", screen.id()).into(),
bg: gpui::blue(),
bounds,
})
})
.unwrap();
let bounds = Bounds {
origin: point(screen.bounds().center().x - size.center().x, margin_offset),
size,
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.new_view(|_| WindowContent {
text: format!("Top Center {:?}", screen.id()).into(),
bg: gpui::black(),
bounds,
})
})
.unwrap();
let bounds = Bounds {
origin: point(margin_offset, screen.bounds().center().y - size.center().y),
size,
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.new_view(|_| WindowContent {
text: format!("Left Center {:?}", screen.id()).into(),
bg: gpui::black(),
bounds,
})
})
.unwrap();
let bounds = Bounds {
origin: point(
screen.bounds().center().x - size.center().x,
screen.bounds().center().y - size.center().y,
),
size,
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.new_view(|_| WindowContent {
text: format!("Center {:?}", screen.id()).into(),
bg: gpui::black(),
bounds,
})
})
.unwrap();
let bounds = Bounds {
origin: point(
screen.bounds().size.width - size.width - margin_offset,
screen.bounds().center().y - size.center().y,
),
size,
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.new_view(|_| WindowContent {
text: format!("Right Center {:?}", screen.id()).into(),
bg: gpui::black(),
bounds,
})
})
.unwrap();
let bounds = Bounds {
origin: point(
screen.bounds().center().x - size.center().x,
screen.bounds().size.height - size.height - margin_offset,
),
size,
};
cx.open_window(build_window_options(screen.id(), bounds), |cx| {
cx.new_view(|_| WindowContent {
text: format!("Bottom Center {:?}", screen.id()).into(),
bg: gpui::black(),
bounds,
text: format!("{:?}", screen.id()).into(),
})
})
.unwrap();

View File

@@ -318,7 +318,6 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
) -> Option<oneshot::Receiver<usize>>;
fn activate(&self);
fn is_active(&self) -> bool;
fn is_hovered(&self) -> bool;
fn set_title(&mut self, title: &str);
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
fn minimize(&self);
@@ -328,7 +327,6 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn on_request_frame(&self, callback: Box<dyn FnMut()>);
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
fn on_moved(&self, callback: Box<dyn FnMut()>);
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);

View File

@@ -1403,7 +1403,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
if let Some(window) = get_window(&mut state, &surface.id()) {
state.mouse_focused_window = Some(window.clone());
if state.enter_token.is_some() {
state.enter_token = None;
}
@@ -1417,7 +1416,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
}
}
drop(state);
window.set_hovered(true);
window.set_focused(true);
}
}
wl_pointer::Event::Leave { .. } => {
@@ -1433,7 +1432,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
drop(state);
focused_window.handle_input(input);
focused_window.set_hovered(false);
focused_window.set_focused(false);
}
}
wl_pointer::Event::Motion {

View File

@@ -36,7 +36,6 @@ pub(crate) struct Callbacks {
request_frame: Option<Box<dyn FnMut()>>,
input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>,
active_status_change: Option<Box<dyn FnMut(bool)>>,
hover_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved: Option<Box<dyn FnMut()>>,
should_close: Option<Box<dyn FnMut() -> bool>>,
@@ -98,7 +97,6 @@ pub struct WaylandWindowState {
client: WaylandClientStatePtr,
handle: AnyWindowHandle,
active: bool,
hovered: bool,
in_progress_configure: Option<InProgressConfigure>,
in_progress_window_controls: Option<WindowControls>,
window_controls: WindowControls,
@@ -183,7 +181,6 @@ impl WaylandWindowState {
appearance,
handle,
active: false,
hovered: false,
in_progress_window_controls: None,
// Assume that we can do anything, unless told otherwise
window_controls: WindowControls {
@@ -703,12 +700,6 @@ impl WaylandWindowStatePtr {
}
}
pub fn set_hovered(&self, focus: bool) {
if let Some(ref mut fun) = self.callbacks.borrow_mut().hover_status_change {
fun(focus);
}
}
pub fn set_appearance(&mut self, appearance: WindowAppearance) {
self.state.borrow_mut().appearance = appearance;
@@ -854,10 +845,6 @@ impl PlatformWindow for WaylandWindow {
self.borrow().active
}
fn is_hovered(&self) -> bool {
self.borrow().hovered
}
fn set_title(&mut self, title: &str) {
self.borrow().toplevel.set_title(title.to_string());
}
@@ -912,10 +899,6 @@ impl PlatformWindow for WaylandWindow {
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
}
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.borrow_mut().hover_status_change = Some(callback);
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.callbacks.borrow_mut().resize = Some(callback);
}

View File

@@ -110,8 +110,7 @@ pub struct X11ClientState {
pub(crate) _resource_database: Database,
pub(crate) atoms: XcbAtoms,
pub(crate) windows: HashMap<xproto::Window, WindowRef>,
pub(crate) mouse_focused_window: Option<xproto::Window>,
pub(crate) keyboard_focused_window: Option<xproto::Window>,
pub(crate) focused_window: Option<xproto::Window>,
pub(crate) xkb: xkbc::State,
pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
pub(crate) xim_handler: Option<XimHandler>,
@@ -145,12 +144,7 @@ impl X11ClientStatePtr {
if let Some(window_ref) = state.windows.remove(&x_window) {
state.loop_handle.remove(window_ref.refresh_event_token);
}
if state.mouse_focused_window == Some(x_window) {
state.mouse_focused_window = None;
}
if state.keyboard_focused_window == Some(x_window) {
state.keyboard_focused_window = None;
}
state.cursor_styles.remove(&x_window);
if state.windows.is_empty() {
@@ -347,8 +341,7 @@ impl X11Client {
_resource_database: resource_database,
atoms,
windows: HashMap::default(),
mouse_focused_window: None,
keyboard_focused_window: None,
focused_window: None,
xkb: xkb_state,
ximc,
xim_handler,
@@ -509,7 +502,7 @@ impl X11Client {
.push(AttributeName::ClientWindow, xim_handler.window)
.push(AttributeName::FocusWindow, xim_handler.window);
let window_id = state.keyboard_focused_window;
let window_id = state.focused_window;
drop(state);
if let Some(window_id) = window_id {
let window = self.get_window(window_id).unwrap();
@@ -593,17 +586,17 @@ impl X11Client {
}
Event::FocusIn(event) => {
let window = self.get_window(event.event)?;
window.set_active(true);
window.set_focused(true);
let mut state = self.0.borrow_mut();
state.keyboard_focused_window = Some(event.event);
state.focused_window = Some(event.event);
drop(state);
self.enable_ime();
}
Event::FocusOut(event) => {
let window = self.get_window(event.event)?;
window.set_active(false);
window.set_focused(false);
let mut state = self.0.borrow_mut();
state.keyboard_focused_window = None;
state.focused_window = None;
if let Some(compose_state) = state.compose_state.as_mut() {
compose_state.reset();
}
@@ -627,7 +620,7 @@ impl X11Client {
if state.modifiers == modifiers {
drop(state);
} else {
let focused_window_id = state.keyboard_focused_window?;
let focused_window_id = state.focused_window?;
state.modifiers = modifiers;
drop(state);
@@ -878,18 +871,12 @@ impl X11Client {
valuator_idx += 1;
}
}
Event::XinputEnter(event) if event.mode == xinput::NotifyMode::NORMAL => {
let window = self.get_window(event.event)?;
window.set_hovered(true);
let mut state = self.0.borrow_mut();
state.mouse_focused_window = Some(event.event);
}
Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => {
self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
self.0.borrow_mut().scroll_y = None;
let window = self.get_window(event.event)?;
let mut state = self.0.borrow_mut();
state.mouse_focused_window = None;
let pressed_button = pressed_button_from_mask(event.buttons[0]);
let position = point(
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
@@ -899,13 +886,11 @@ impl X11Client {
state.modifiers = modifiers;
drop(state);
let window = self.get_window(event.event)?;
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
pressed_button,
position,
modifiers,
}));
window.set_hovered(false);
}
_ => {}
};
@@ -1155,7 +1140,7 @@ impl LinuxClient for X11Client {
fn set_cursor_style(&self, style: CursorStyle) {
let mut state = self.0.borrow_mut();
let Some(focused_window) = state.mouse_focused_window else {
let Some(focused_window) = state.focused_window else {
return;
};
let current_style = state
@@ -1287,7 +1272,7 @@ impl LinuxClient for X11Client {
fn active_window(&self) -> Option<AnyWindowHandle> {
let state = self.0.borrow();
state.keyboard_focused_window.and_then(|focused_window| {
state.focused_window.and_then(|focused_window| {
state
.windows
.get(&focused_window)

View File

@@ -211,7 +211,6 @@ pub struct Callbacks {
request_frame: Option<Box<dyn FnMut()>>,
input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
active_status_change: Option<Box<dyn FnMut(bool)>>,
hovered_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved: Option<Box<dyn FnMut()>>,
should_close: Option<Box<dyn FnMut() -> bool>>,
@@ -239,7 +238,6 @@ pub struct X11WindowState {
maximized_horizontal: bool,
hidden: bool,
active: bool,
hovered: bool,
fullscreen: bool,
client_side_decorations_supported: bool,
decorations: WindowDecorations,
@@ -453,7 +451,6 @@ impl X11WindowState {
xinput::XIEventMask::MOTION
| xinput::XIEventMask::BUTTON_PRESS
| xinput::XIEventMask::BUTTON_RELEASE
| xinput::XIEventMask::ENTER
| xinput::XIEventMask::LEAVE,
],
}],
@@ -510,7 +507,6 @@ impl X11WindowState {
atoms: *atoms,
input_handler: None,
active: false,
hovered: false,
fullscreen: false,
maximized_vertical: false,
maximized_horizontal: false,
@@ -781,15 +777,6 @@ impl X11WindowStatePtr {
state.hidden = true;
}
}
let hovered_window = self
.xcb_connection
.query_pointer(state.x_root_window)
.unwrap()
.reply()
.unwrap()
.child;
self.set_hovered(hovered_window == self.x_window);
}
pub fn close(&self) {
@@ -925,18 +912,12 @@ impl X11WindowStatePtr {
}
}
pub fn set_active(&self, focus: bool) {
pub fn set_focused(&self, focus: bool) {
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
fun(focus);
}
}
pub fn set_hovered(&self, focus: bool) {
if let Some(ref mut fun) = self.callbacks.borrow_mut().hovered_status_change {
fun(focus);
}
}
pub fn set_appearance(&mut self, appearance: WindowAppearance) {
let mut state = self.state.borrow_mut();
state.appearance = appearance;
@@ -1065,10 +1046,6 @@ impl PlatformWindow for X11Window {
self.0.state.borrow().active
}
fn is_hovered(&self) -> bool {
self.0.state.borrow().hovered
}
fn set_title(&mut self, title: &str) {
self.0
.xcb_connection
@@ -1185,10 +1162,6 @@ impl PlatformWindow for X11Window {
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
}
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.callbacks.borrow_mut().resize = Some(callback);
}

View File

@@ -452,7 +452,7 @@ impl MacWindowState {
let bounds = Bounds::new(
point(
px((window_frame.origin.x - screen_frame.origin.x) as f32),
px((window_frame.origin.y + screen_frame.origin.y) as f32),
px((window_frame.origin.y - screen_frame.origin.y) as f32),
),
size(
px(window_frame.size.width as f32),
@@ -546,7 +546,7 @@ impl MacWindow {
let count: u64 = cocoa::foundation::NSArray::count(screens);
for i in 0..count {
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
let frame = NSScreen::frame(screen);
let frame = NSScreen::visibleFrame(screen);
let display_id = display_id_for_screen(screen);
if display_id == display.0 {
screen_frame = Some(frame);
@@ -557,7 +557,7 @@ impl MacWindow {
let screen_frame = screen_frame.unwrap_or_else(|| {
let screen = NSScreen::mainScreen(nil);
target_screen = screen;
NSScreen::frame(screen)
NSScreen::visibleFrame(screen)
});
let window_rect = NSRect::new(
@@ -940,11 +940,6 @@ impl PlatformWindow for MacWindow {
unsafe { self.0.lock().native_window.isKeyWindow() == YES }
}
// is_hovered is unused on macOS. See WindowContext::is_window_hovered.
fn is_hovered(&self) -> bool {
false
}
fn set_title(&mut self, title: &str) {
unsafe {
let app = NSApplication::sharedApplication(nil);
@@ -1066,8 +1061,6 @@ impl PlatformWindow for MacWindow {
self.0.as_ref().lock().activate_callback = Some(callback);
}
fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.as_ref().lock().resize_callback = Some(callback);
}

View File

@@ -23,7 +23,6 @@ pub(crate) struct TestWindowState {
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
hover_status_change_callback: Option<Box<dyn FnMut(bool)>>,
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved_callback: Option<Box<dyn FnMut()>>,
input_handler: Option<PlatformInputHandler>,
@@ -67,7 +66,6 @@ impl TestWindow {
should_close_handler: None,
input_callback: None,
active_status_change_callback: None,
hover_status_change_callback: None,
resize_callback: None,
moved_callback: None,
input_handler: None,
@@ -184,10 +182,6 @@ impl PlatformWindow for TestWindow {
false
}
fn is_hovered(&self) -> bool {
false
}
fn set_title(&mut self, title: &str) {
self.0.lock().title = Some(title.to_owned());
}
@@ -231,10 +225,6 @@ impl PlatformWindow for TestWindow {
self.0.lock().active_status_change_callback = Some(callback)
}
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.lock().hover_status_change_callback = Some(callback)
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.lock().resize_callback = Some(callback)
}

View File

@@ -49,7 +49,8 @@ struct DirectWriteComponent {
struct GlyphRenderContext {
params: IDWriteRenderingParams3,
dc_target: ID2D1DeviceContext4,
normal_dc_target: ID2D1DeviceContext4,
emoji_dc_target: ID2D1DeviceContext4,
}
// All use of the IUnknown methods should be "thread-safe".
@@ -127,7 +128,16 @@ impl GlyphRenderContext {
DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC,
grid_fit_mode,
)?;
let dc_target = {
let normal_dc_target = {
let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property(
DXGI_FORMAT_A8_UNORM,
D2D1_ALPHA_MODE_STRAIGHT,
))?;
let target = target.cast::<ID2D1DeviceContext4>()?;
target.SetTextRenderingParams(&params);
target
};
let emoji_dc_target = {
let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED,
@@ -137,7 +147,11 @@ impl GlyphRenderContext {
target
};
Ok(Self { params, dc_target })
Ok(Self {
params,
normal_dc_target,
emoji_dc_target,
})
}
}
}
@@ -557,7 +571,11 @@ impl DirectWriteState {
}
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
let render_target = &self.components.render_context.dc_target;
let render_target = if params.is_emoji {
&self.components.render_context.emoji_dc_target
} else {
&self.components.render_context.normal_dc_target
};
unsafe {
render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS);
render_target.SetDpi(96.0 * params.scale_factor, 96.0 * params.scale_factor);

View File

@@ -503,11 +503,6 @@ impl PlatformWindow for WindowsWindow {
self.0.hwnd == unsafe { GetActiveWindow() }
}
// is_hovered is unused on Windows. See WindowContext::is_window_hovered.
fn is_hovered(&self) -> bool {
false
}
fn set_title(&mut self, title: &str) {
unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
.inspect_err(|e| log::error!("Set title failed: {e}"))
@@ -609,8 +604,6 @@ impl PlatformWindow for WindowsWindow {
self.0.state.borrow_mut().callbacks.active_status_change = Some(callback);
}
fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.state.borrow_mut().callbacks.resize = Some(callback);
}

View File

@@ -1,14 +1,12 @@
use crate::TextStyleRefinement;
use crate::{
self as gpui, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, DefiniteLength,
Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla, JustifyContent, Length,
SharedString, StyleRefinement, WhiteSpace,
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla,
JustifyContent, Length, SharedString, StyleRefinement, Visibility, WhiteSpace,
};
pub use gpui_macros::{
box_shadow_style_methods, cursor_style_methods, margin_style_methods, overflow_style_methods,
padding_style_methods, position_style_methods, visibility_style_methods,
};
use taffy::style::{AlignContent, Display};
use crate::{BoxShadow, TextStyleRefinement};
pub use gpui_macros::{margin_style_methods, padding_style_methods, position_style_methods};
use smallvec::{smallvec, SmallVec};
use taffy::style::{AlignContent, Display, Overflow};
/// A trait for elements that can be styled.
/// Use this to opt-in to a CSS-like styling API.
@@ -17,13 +15,9 @@ pub trait Styled: Sized {
fn style(&mut self) -> &mut StyleRefinement;
gpui_macros::style_helpers!();
gpui_macros::visibility_style_methods!();
gpui_macros::margin_style_methods!();
gpui_macros::padding_style_methods!();
gpui_macros::position_style_methods!();
gpui_macros::overflow_style_methods!();
gpui_macros::cursor_style_methods!();
gpui_macros::box_shadow_style_methods!();
/// Sets the display type of the element to `block`.
/// [Docs](https://tailwindcss.com/docs/display)
@@ -39,6 +33,195 @@ pub trait Styled: Sized {
self
}
/// Sets the visibility of the element to `visible`.
/// [Docs](https://tailwindcss.com/docs/visibility)
fn visible(mut self) -> Self {
self.style().visibility = Some(Visibility::Visible);
self
}
/// Sets the visibility of the element to `hidden`.
/// [Docs](https://tailwindcss.com/docs/visibility)
fn invisible(mut self) -> Self {
self.style().visibility = Some(Visibility::Hidden);
self
}
/// Sets the behavior of content that overflows the container to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
fn overflow_hidden(mut self) -> Self {
self.style().overflow.x = Some(Overflow::Hidden);
self.style().overflow.y = Some(Overflow::Hidden);
self
}
/// Sets the behavior of content that overflows the container on the X axis to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
fn overflow_x_hidden(mut self) -> Self {
self.style().overflow.x = Some(Overflow::Hidden);
self
}
/// Sets the behavior of content that overflows the container on the Y axis to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
fn overflow_y_hidden(mut self) -> Self {
self.style().overflow.y = Some(Overflow::Hidden);
self
}
/// Set the cursor style when hovering over this element
fn cursor(mut self, cursor: CursorStyle) -> Self {
self.style().mouse_cursor = Some(cursor);
self
}
/// Sets the cursor style when hovering an element to `default`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_default(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::Arrow);
self
}
/// Sets the cursor style when hovering an element to `pointer`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_pointer(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::PointingHand);
self
}
/// Sets cursor style when hovering over an element to `text`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_text(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::IBeam);
self
}
/// Sets cursor style when hovering over an element to `move`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_move(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ClosedHand);
self
}
/// Sets cursor style when hovering over an element to `not-allowed`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_not_allowed(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed);
self
}
/// Sets cursor style when hovering over an element to `context-menu`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_context_menu(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ContextualMenu);
self
}
/// Sets cursor style when hovering over an element to `crosshair`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_crosshair(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::Crosshair);
self
}
/// Sets cursor style when hovering over an element to `vertical-text`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_vertical_text(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::IBeamCursorForVerticalLayout);
self
}
/// Sets cursor style when hovering over an element to `alias`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_alias(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::DragLink);
self
}
/// Sets cursor style when hovering over an element to `copy`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_copy(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::DragCopy);
self
}
/// Sets cursor style when hovering over an element to `no-drop`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_no_drop(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed);
self
}
/// Sets cursor style when hovering over an element to `grab`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_grab(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OpenHand);
self
}
/// Sets cursor style when hovering over an element to `grabbing`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_grabbing(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ClosedHand);
self
}
/// Sets cursor style when hovering over an element to `ew-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_ew_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeLeftRight);
self
}
/// Sets cursor style when hovering over an element to `ns-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_ns_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeUpDown);
self
}
/// Sets cursor style when hovering over an element to `col-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_col_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeColumn);
self
}
/// Sets cursor style when hovering over an element to `row-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_row_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeRow);
self
}
/// Sets cursor style when hovering over an element to `n-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_n_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeUp);
self
}
/// Sets cursor style when hovering over an element to `e-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_e_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeRight);
self
}
/// Sets cursor style when hovering over an element to `s-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_s_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeDown);
self
}
/// Sets cursor style when hovering over an element to `w-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_w_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeLeft);
self
}
/// Sets the whitespace of the element to `normal`.
/// [Docs](https://tailwindcss.com/docs/whitespace#normal)
fn whitespace_normal(mut self) -> Self {
@@ -306,6 +489,104 @@ pub trait Styled: Sized {
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow(mut self, shadows: SmallVec<[BoxShadow; 2]>) -> Self {
self.style().box_shadow = Some(shadows);
self
}
/// Clears the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_none(mut self) -> Self {
self.style().box_shadow = Some(Default::default());
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_sm(mut self) -> Self {
self.style().box_shadow = Some(smallvec::smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.05),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
}]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_md(mut self) -> Self {
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0.5, 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-1.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(2.)),
blur_radius: px(4.),
spread_radius: px(-2.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_lg(mut self) -> Self {
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(10.)),
blur_radius: px(15.),
spread_radius: px(-3.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-4.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_xl(mut self) -> Self {
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(20.)),
blur_radius: px(25.),
spread_radius: px(-5.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(8.)),
blur_radius: px(10.),
spread_radius: px(-6.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_2xl(mut self) -> Self {
self.style().box_shadow = Some(smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.25),
offset: point(px(0.), px(25.)),
blur_radius: px(50.),
spread_radius: px(-12.),
}]);
self
}
/// Get the text style that has been configured on this element.
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
let style: &mut StyleRefinement = self.style();

View File

@@ -658,6 +658,26 @@ impl Hash for RenderGlyphParams {
}
}
/// The parameters for rendering an emoji glyph.
#[derive(Clone, Debug, PartialEq)]
pub struct RenderEmojiParams {
pub(crate) font_id: FontId,
pub(crate) glyph_id: GlyphId,
pub(crate) font_size: Pixels,
pub(crate) scale_factor: f32,
}
impl Eq for RenderEmojiParams {}
impl Hash for RenderEmojiParams {
fn hash<H: Hasher>(&self, state: &mut H) {
self.font_id.0.hash(state);
self.glyph_id.0.hash(state);
self.font_size.0.to_bits().hash(state);
self.scale_factor.to_bits().hash(state);
}
}
/// The configuration details for identifying a specific font.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Font {

View File

@@ -49,17 +49,9 @@ impl LineWrapper {
continue;
}
if Self::is_word_char(c) {
if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = ix;
last_candidate_width = width;
}
} else {
// CJK may not be space separated, e.g.: `Hello world你好世界`
if c != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = ix;
last_candidate_width = width;
}
if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = ix;
last_candidate_width = width;
}
if c != ' ' && first_non_whitespace_ix.is_none() {
@@ -98,31 +90,6 @@ impl LineWrapper {
})
}
pub(crate) fn is_word_char(c: char) -> bool {
// ASCII alphanumeric characters, for English, numbers: `Hello123`, etc.
c.is_ascii_alphanumeric() ||
// Latin script in Unicode for French, German, Spanish, etc.
// Latin-1 Supplement
// https://en.wikipedia.org/wiki/Latin-1_Supplement
matches!(c, '\u{00C0}'..='\u{00FF}') ||
// Latin Extended-A
// https://en.wikipedia.org/wiki/Latin_Extended-A
matches!(c, '\u{0100}'..='\u{017F}') ||
// Latin Extended-B
// https://en.wikipedia.org/wiki/Latin_Extended-B
matches!(c, '\u{0180}'..='\u{024F}') ||
// Cyrillic for Russian, Ukrainian, etc.
// https://en.wikipedia.org/wiki/Cyrillic_script_in_Unicode
matches!(c, '\u{0400}'..='\u{04FF}') ||
// Some other known special characters that should be treated as word characters,
// e.g. `a-b`, `var_name`, `I'm`, '@mention`, `#hashtag`, `100%`, `3.1415`, `2^3`, `a~b`, etc.
matches!(c, '-' | '_' | '.' | '\'' | '$' | '%' | '@' | '#' | '^' | '~') ||
// Characters that used in URL, e.g. `https://github.com/zed-industries/zed?a=1&b=2` for better wrapping a long URL.
matches!(c, '/' | ':' | '?' | '&' | '=') ||
// `⋯` character is special used in Zed, to keep this at the end of the line.
matches!(c, '⋯')
}
#[inline(always)]
fn width_for_char(&mut self, c: char) -> Pixels {
if (c as u32) < 128 {
@@ -252,59 +219,6 @@ mod tests {
});
}
#[test]
fn test_is_word_char() {
#[track_caller]
fn assert_word(word: &str) {
for c in word.chars() {
assert!(LineWrapper::is_word_char(c), "assertion failed for '{}'", c);
}
}
#[track_caller]
fn assert_not_word(word: &str) {
let found = word.chars().any(|c| !LineWrapper::is_word_char(c));
assert!(found, "assertion failed for '{}'", word);
}
assert_word("Hello123");
assert_word("non-English");
assert_word("var_name");
assert_word("123456");
assert_word("3.1415");
assert_word("10^2");
assert_word("1~2");
assert_word("100%");
assert_word("@mention");
assert_word("#hashtag");
assert_word("$variable");
assert_word("more⋯");
// Space
assert_not_word("foo bar");
// URL case
assert_word("https://github.com/zed-industries/zed/");
assert_word("github.com");
assert_word("a=1&b=2");
// Latin-1 Supplement
assert_word("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ");
// Latin Extended-A
assert_word("ĀāĂ㥹ĆćĈĉĊċČčĎď");
// Latin Extended-B
assert_word("ƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏ");
// Cyrillic
assert_word("АБВГДЕЖЗИЙКЛМНОП");
// non-word characters
assert_not_word("你好");
assert_not_word("안녕하세요");
assert_not_word("こんにちは");
assert_not_word("😀😁😂");
assert_not_word("()[]{}<>");
}
// For compatibility with the test macro
#[cfg(target_os = "macos")]
use crate as gpui;

View File

@@ -541,7 +541,6 @@ pub struct Window {
appearance: WindowAppearance,
appearance_observers: SubscriberSet<(), AnyObserver>,
active: Rc<Cell<bool>>,
hovered: Rc<Cell<bool>>,
pub(crate) dirty: Rc<Cell<bool>>,
pub(crate) needs_present: Rc<Cell<bool>>,
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
@@ -673,7 +672,6 @@ impl Window {
let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone()));
let dirty = Rc::new(Cell::new(true));
let active = Rc::new(Cell::new(platform_window.is_active()));
let hovered = Rc::new(Cell::new(platform_window.is_hovered()));
let needs_present = Rc::new(Cell::new(false));
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
@@ -780,17 +778,7 @@ impl Window {
.log_err();
}
}));
platform_window.on_hover_status_change(Box::new({
let mut cx = cx.to_async();
move |active| {
handle
.update(&mut cx, |_, cx| {
cx.window.hovered.set(active);
cx.refresh();
})
.log_err();
}
}));
platform_window.on_input({
let mut cx = cx.to_async();
Box::new(move |event| {
@@ -841,7 +829,6 @@ impl Window {
appearance,
appearance_observers: SubscriberSet::new(),
active,
hovered,
dirty,
needs_present,
last_input_timestamp,
@@ -1235,17 +1222,6 @@ impl<'a> WindowContext<'a> {
self.window.active.get()
}
/// Returns whether this window is considered to be the window
/// that currently owns the mouse cursor.
/// On mac, this is equivalent to `is_window_active`.
pub fn is_window_hovered(&self) -> bool {
if cfg!(target_os = "linux") {
self.window.hovered.get()
} else {
self.is_window_active()
}
}
/// Toggle zoom on the window.
pub fn zoom_window(&self) {
self.window.platform_window.zoom();
@@ -3004,7 +2980,7 @@ impl<'a> WindowContext<'a> {
fn reset_cursor_style(&self) {
// Set the cursor only if we're the active window.
if self.is_window_hovered() {
if self.is_window_active() {
let style = self
.window
.rendered_frame

View File

@@ -16,4 +16,4 @@ doctest = false
[dependencies]
proc-macro2 = "1.0.66"
quote = "1.0.9"
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
syn = { version = "1.0.72", features = ["full"] }

View File

@@ -1,7 +1,7 @@
mod derive_into_element;
mod derive_render;
mod register_action;
mod styles;
mod style_helpers;
mod test;
use proc_macro::TokenStream;
@@ -31,49 +31,28 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
#[proc_macro]
#[doc(hidden)]
pub fn style_helpers(input: TokenStream) -> TokenStream {
styles::style_helpers(input)
}
/// Generates methods for visibility styles.
#[proc_macro]
pub fn visibility_style_methods(input: TokenStream) -> TokenStream {
styles::visibility_style_methods(input)
style_helpers::style_helpers(input)
}
/// Generates methods for margin styles.
#[proc_macro]
#[doc(hidden)]
pub fn margin_style_methods(input: TokenStream) -> TokenStream {
styles::margin_style_methods(input)
style_helpers::margin_style_methods(input)
}
/// Generates methods for padding styles.
#[proc_macro]
#[doc(hidden)]
pub fn padding_style_methods(input: TokenStream) -> TokenStream {
styles::padding_style_methods(input)
style_helpers::padding_style_methods(input)
}
/// Generates methods for position styles.
#[proc_macro]
#[doc(hidden)]
pub fn position_style_methods(input: TokenStream) -> TokenStream {
styles::position_style_methods(input)
}
/// Generates methods for overflow styles.
#[proc_macro]
pub fn overflow_style_methods(input: TokenStream) -> TokenStream {
styles::overflow_style_methods(input)
}
/// Generates methods for cursor styles.
#[proc_macro]
pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
styles::cursor_style_methods(input)
}
/// Generates methods for box shadow styles.
#[proc_macro]
pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
styles::box_shadow_style_methods(input)
style_helpers::position_style_methods(input)
}
/// #[gpui::test] can be used to annotate test functions that run with GPUI support.

View File

@@ -47,28 +47,6 @@ pub fn style_helpers(input: TokenStream) -> TokenStream {
output.into()
}
pub fn visibility_style_methods(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as StyleableMacroInput);
let visibility = input.method_visibility;
let output = quote! {
/// Sets the visibility of the element to `visible`.
/// [Docs](https://tailwindcss.com/docs/visibility)
#visibility fn visible(mut self) -> Self {
self.style().visibility = Some(gpui::Visibility::Visible);
self
}
/// Sets the visibility of the element to `hidden`.
/// [Docs](https://tailwindcss.com/docs/visibility)
#visibility fn invisible(mut self) -> Self {
self.style().visibility = Some(gpui::Visibility::Hidden);
self
}
};
output.into()
}
pub fn margin_style_methods(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as StyleableMacroInput);
let methods = generate_box_style_methods(
@@ -126,318 +104,6 @@ pub fn position_style_methods(input: TokenStream) -> TokenStream {
output.into()
}
pub fn overflow_style_methods(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as StyleableMacroInput);
let visibility = input.method_visibility;
let output = quote! {
/// Sets the behavior of content that overflows the container to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
#visibility fn overflow_hidden(mut self) -> Self {
self.style().overflow.x = Some(gpui::Overflow::Hidden);
self.style().overflow.y = Some(gpui::Overflow::Hidden);
self
}
/// Sets the behavior of content that overflows the container on the X axis to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
#visibility fn overflow_x_hidden(mut self) -> Self {
self.style().overflow.x = Some(gpui::Overflow::Hidden);
self
}
/// Sets the behavior of content that overflows the container on the Y axis to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
#visibility fn overflow_y_hidden(mut self) -> Self {
self.style().overflow.y = Some(gpui::Overflow::Hidden);
self
}
};
output.into()
}
pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as StyleableMacroInput);
let visibility = input.method_visibility;
let output = quote! {
/// Set the cursor style when hovering over this element
#visibility fn cursor(mut self, cursor: CursorStyle) -> Self {
self.style().mouse_cursor = Some(cursor);
self
}
/// Sets the cursor style when hovering an element to `default`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_default(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::Arrow);
self
}
/// Sets the cursor style when hovering an element to `pointer`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_pointer(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::PointingHand);
self
}
/// Sets cursor style when hovering over an element to `text`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_text(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::IBeam);
self
}
/// Sets cursor style when hovering over an element to `move`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_move(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ClosedHand);
self
}
/// Sets cursor style when hovering over an element to `not-allowed`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_not_allowed(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::OperationNotAllowed);
self
}
/// Sets cursor style when hovering over an element to `context-menu`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_context_menu(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ContextualMenu);
self
}
/// Sets cursor style when hovering over an element to `crosshair`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_crosshair(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::Crosshair);
self
}
/// Sets cursor style when hovering over an element to `vertical-text`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_vertical_text(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::IBeamCursorForVerticalLayout);
self
}
/// Sets cursor style when hovering over an element to `alias`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_alias(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::DragLink);
self
}
/// Sets cursor style when hovering over an element to `copy`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_copy(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::DragCopy);
self
}
/// Sets cursor style when hovering over an element to `no-drop`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_no_drop(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::OperationNotAllowed);
self
}
/// Sets cursor style when hovering over an element to `grab`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_grab(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::OpenHand);
self
}
/// Sets cursor style when hovering over an element to `grabbing`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_grabbing(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ClosedHand);
self
}
/// Sets cursor style when hovering over an element to `ew-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_ew_resize(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeLeftRight);
self
}
/// Sets cursor style when hovering over an element to `ns-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_ns_resize(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeUpDown);
self
}
/// Sets cursor style when hovering over an element to `col-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_col_resize(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeColumn);
self
}
/// Sets cursor style when hovering over an element to `row-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_row_resize(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeRow);
self
}
/// Sets cursor style when hovering over an element to `n-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_n_resize(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeUp);
self
}
/// Sets cursor style when hovering over an element to `e-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_e_resize(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeRight);
self
}
/// Sets cursor style when hovering over an element to `s-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_s_resize(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeDown);
self
}
/// Sets cursor style when hovering over an element to `w-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
#visibility fn cursor_w_resize(mut self) -> Self {
self.style().mouse_cursor = Some(gpui::CursorStyle::ResizeLeft);
self
}
};
output.into()
}
pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as StyleableMacroInput);
let visibility = input.method_visibility;
let output = quote! {
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
#visibility fn shadow(mut self, shadows: smallvec::SmallVec<[gpui::BoxShadow; 2]>) -> Self {
self.style().box_shadow = Some(shadows);
self
}
/// Clears the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
#visibility fn shadow_none(mut self) -> Self {
self.style().box_shadow = Some(Default::default());
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
#visibility fn shadow_sm(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
use smallvec::smallvec;
self.style().box_shadow = Some(smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.05),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
}]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
#visibility fn shadow_md(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
use smallvec::smallvec;
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0.5, 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-1.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(2.)),
blur_radius: px(4.),
spread_radius: px(-2.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
#visibility fn shadow_lg(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
use smallvec::smallvec;
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(10.)),
blur_radius: px(15.),
spread_radius: px(-3.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-4.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
#visibility fn shadow_xl(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
use smallvec::smallvec;
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(20.)),
blur_radius: px(25.),
spread_radius: px(-5.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(8.)),
blur_radius: px(10.),
spread_radius: px(-6.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
#visibility fn shadow_2xl(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
use smallvec::smallvec;
self.style().box_shadow = Some(smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.25),
offset: point(px(0.), px(25.)),
blur_radius: px(50.),
spread_radius: px(-12.),
}]);
self
}
};
output.into()
}
struct BoxStylePrefix {
prefix: &'static str,
auto_allowed: bool,

View File

@@ -1903,10 +1903,6 @@ impl Buffer {
self.deferred_ops.insert(deferred_ops);
}
pub fn has_deferred_ops(&self) -> bool {
!self.deferred_ops.is_empty() || self.text.has_deferred_ops()
}
fn can_apply_op(&self, operation: &Operation) -> bool {
match operation {
Operation::Buffer(_) => {

View File

@@ -27,6 +27,27 @@ impl<'a> Into<SettingsLocation<'a>> for &'a dyn File {
/// Initializes the language settings.
pub fn init(cx: &mut AppContext) {
AllLanguageSettings::register(cx);
DownloadConfiguration::register(cx);
}
#[derive(Default, Serialize, Deserialize, Clone)]
pub struct DownloadConfiguration {
enable_binary_downloads: bool,
}
#[derive(Deserialize)]
struct DownloadConfigurationContent {
enable_binary_downloads: Option<bool>,
}
impl settings::Settings for DownloadConfiguration {
const KEY: Option<&'static str> = None;
type FileContent = DownloadConfigurationContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()
}
}
/// Returns the settings for the specified language from the provided file.

View File

@@ -1,7 +1,7 @@
//! Handles conversions of `language` items to and from the [`rpc`] protocol.
use crate::{diagnostic_set::DiagnosticEntry, CursorShape, Diagnostic};
use anyhow::{anyhow, Context as _, Result};
use anyhow::{anyhow, Result};
use clock::ReplicaId;
use lsp::{DiagnosticSeverity, LanguageServerId};
use rpc::proto;
@@ -231,21 +231,6 @@ pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
}
}
pub fn serialize_anchor_range(range: Range<Anchor>) -> proto::AnchorRange {
proto::AnchorRange {
start: Some(serialize_anchor(&range.start)),
end: Some(serialize_anchor(&range.end)),
}
}
/// Deserializes an [`Range<Anchor>`] from the RPC representation.
pub fn deserialize_anchor_range(range: proto::AnchorRange) -> Result<Range<Anchor>> {
Ok(
deserialize_anchor(range.start.context("invalid anchor")?).context("invalid anchor")?
..deserialize_anchor(range.end.context("invalid anchor")?).context("invalid anchor")?,
)
}
// This behavior is currently copied in the collab database, for snapshotting channel notes
/// Deserializes an [`crate::Operation`] from the RPC representation.
pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operation> {

View File

@@ -1207,7 +1207,7 @@ fn get_injections(
language_registry: &Arc<LanguageRegistry>,
depth: usize,
changed_ranges: &[Range<usize>],
combined_injection_ranges: &mut HashMap<LanguageId, (Arc<Language>, Vec<tree_sitter::Range>)>,
combined_injection_ranges: &mut HashMap<Arc<Language>, Vec<tree_sitter::Range>>,
queue: &mut BinaryHeap<ParseStep>,
) {
let mut query_cursor = QueryCursorHandle::new();
@@ -1223,7 +1223,7 @@ fn get_injections(
.now_or_never()
.and_then(|language| language.ok())
{
combined_injection_ranges.insert(language.id, (language, Vec::new()));
combined_injection_ranges.insert(language, Vec::new());
}
}
}
@@ -1276,9 +1276,8 @@ fn get_injections(
if let Some(language) = language {
if combined {
combined_injection_ranges
.entry(language.id)
.or_insert_with(|| (language.clone(), vec![]))
.1
.entry(language.clone())
.or_default()
.extend(content_ranges);
} else {
queue.push(ParseStep {
@@ -1304,7 +1303,7 @@ fn get_injections(
}
}
for (_, (language, mut included_ranges)) in combined_injection_ranges.drain() {
for (language, mut included_ranges) in combined_injection_ranges.drain() {
included_ranges.sort_unstable_by(|a, b| {
Ord::cmp(&a.start_byte, &b.start_byte).then_with(|| Ord::cmp(&a.end_byte, &b.end_byte))
});

View File

@@ -518,7 +518,7 @@ impl ContextProvider for GoContextProvider {
"test".into(),
GO_PACKAGE_TASK_VARIABLE.template_value(),
"-run".into(),
format!("'^{}$'", VariableName::Symbol.template_value(),),
format!("^{}$", VariableName::Symbol.template_value(),),
],
tags: vec!["go-test".to_owned()],
..TaskTemplate::default()
@@ -549,7 +549,7 @@ impl ContextProvider for GoContextProvider {
"-v".into(),
"-run".into(),
format!(
"'^{}$/^{}$'",
"^{}$/^{}$",
VariableName::Symbol.template_value(),
GO_SUBTEST_NAME_TASK_VARIABLE.template_value(),
),
@@ -570,7 +570,7 @@ impl ContextProvider for GoContextProvider {
"-benchmem".into(),
"-run=^$".into(),
"-bench".into(),
format!("'^{}$'", VariableName::Symbol.template_value()),
format!("^{}$", VariableName::Symbol.template_value()),
],
tags: vec!["go-benchmark".to_owned()],
..TaskTemplate::default()

View File

@@ -24,6 +24,7 @@ async_zip.workspace = true
futures.workspace = true
http.workspace = true
log.workspace = true
language.workspace = true
paths.workspace = true
semver.workspace = true
serde.workspace = true

View File

@@ -1207,7 +1207,6 @@ impl OutlinePanel {
self.selected_entry = Some(entry_with_selection);
self.update_cached_entries(None, cx);
self.autoscroll(cx);
}
fn render_excerpt(

View File

@@ -355,9 +355,6 @@ pub enum Event {
},
CollaboratorJoined(proto::PeerId),
CollaboratorLeft(proto::PeerId),
HostReshared,
Reshared,
Rejoined,
RefreshInlayHints,
RevealInProjectPanel(ProjectEntryId),
SnippetEdit(BufferId, Vec<(lsp::Range, Snippet)>),
@@ -1719,7 +1716,6 @@ impl Project {
self.shared_buffers.clear();
self.set_collaborators_from_proto(message.collaborators, cx)?;
self.metadata_changed(cx);
cx.emit(Event::Reshared);
Ok(())
}
@@ -1757,7 +1753,6 @@ impl Project {
.collect();
self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
.unwrap();
cx.emit(Event::Rejoined);
cx.notify();
Ok(())
}
@@ -1810,11 +1805,9 @@ impl Project {
}
}
self.client
.send(proto::UnshareProject {
project_id: remote_id,
})
.ok();
self.client.send(proto::UnshareProject {
project_id: remote_id,
})?;
Ok(())
} else {
@@ -4106,7 +4099,6 @@ impl Project {
return;
}
#[allow(clippy::mutable_key_type)]
let language_server_lookup_info: HashSet<(Model<Worktree>, Arc<Language>)> = buffers
.into_iter()
.filter_map(|buffer| {
@@ -8818,7 +8810,6 @@ impl Project {
.retain(|_, buffer| !matches!(buffer, OpenBuffer::Operations(_)));
this.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync)
.unwrap();
cx.emit(Event::HostReshared);
}
cx.emit(Event::CollaboratorUpdated {
@@ -11067,7 +11058,6 @@ async fn populate_labels_for_symbols(
lsp_adapter: Option<Arc<CachedLspAdapter>>,
output: &mut Vec<Symbol>,
) {
#[allow(clippy::mutable_key_type)]
let mut symbols_by_language = HashMap::<Option<Arc<Language>>, Vec<CoreSymbol>>::default();
let mut unknown_path = None;

View File

@@ -444,6 +444,11 @@ mod test_inventory {
use super::{task_source_kind_preference, TaskSourceKind, UnboundedSender};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestTask {
name: String,
}
pub(super) fn static_test_source(
task_names: impl IntoIterator<Item = String>,
updates: UnboundedSender<()>,

View File

@@ -1,7 +1,7 @@
fn main() {
let mut build = prost_build::Config::new();
build
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
.type_attribute(".", "#[derive(serde::Serialize)]")
.compile_protos(&["proto/zed.proto"], &["proto"])
.unwrap();
}

View File

@@ -255,14 +255,7 @@ message Envelope {
TaskTemplates task_templates = 206;
LinkedEditingRange linked_editing_range = 209;
LinkedEditingRangeResponse linked_editing_range_response = 210;
AdvertiseContexts advertise_contexts = 211;
OpenContext open_context = 212;
OpenContextResponse open_context_response = 213;
UpdateContext update_context = 214;
SynchronizeContexts synchronize_contexts = 215;
SynchronizeContextsResponse synchronize_contexts_response = 216; // current max
LinkedEditingRangeResponse linked_editing_range_response = 210; // current max
}
reserved 158 to 161;
@@ -2229,117 +2222,3 @@ message TaskSourceKind {
string name = 1;
}
}
message ContextMessageStatus {
oneof variant {
Done done = 1;
Pending pending = 2;
Error error = 3;
}
message Done {}
message Pending {}
message Error {
string message = 1;
}
}
message ContextMessage {
LamportTimestamp id = 1;
Anchor start = 2;
LanguageModelRole role = 3;
ContextMessageStatus status = 4;
}
message SlashCommandOutputSection {
AnchorRange range = 1;
string icon_name = 2;
string label = 3;
}
message ContextOperation {
oneof variant {
InsertMessage insert_message = 1;
UpdateMessage update_message = 2;
UpdateSummary update_summary = 3;
SlashCommandFinished slash_command_finished = 4;
BufferOperation buffer_operation = 5;
}
message InsertMessage {
ContextMessage message = 1;
repeated VectorClockEntry version = 2;
}
message UpdateMessage {
LamportTimestamp message_id = 1;
LanguageModelRole role = 2;
ContextMessageStatus status = 3;
LamportTimestamp timestamp = 4;
repeated VectorClockEntry version = 5;
}
message UpdateSummary {
string summary = 1;
bool done = 2;
LamportTimestamp timestamp = 3;
repeated VectorClockEntry version = 4;
}
message SlashCommandFinished {
LamportTimestamp id = 1;
AnchorRange output_range = 2;
repeated SlashCommandOutputSection sections = 3;
repeated VectorClockEntry version = 4;
}
message BufferOperation {
Operation operation = 1;
}
}
message Context {
repeated ContextOperation operations = 1;
}
message ContextMetadata {
string context_id = 1;
optional string summary = 2;
}
message AdvertiseContexts {
uint64 project_id = 1;
repeated ContextMetadata contexts = 2;
}
message OpenContext {
uint64 project_id = 1;
string context_id = 2;
}
message OpenContextResponse {
Context context = 1;
}
message UpdateContext {
uint64 project_id = 1;
string context_id = 2;
ContextOperation operation = 3;
}
message ContextVersion {
string context_id = 1;
repeated VectorClockEntry context_version = 2;
repeated VectorClockEntry buffer_version = 3;
}
message SynchronizeContexts {
uint64 project_id = 1;
repeated ContextVersion contexts = 2;
}
message SynchronizeContextsResponse {
repeated ContextVersion contexts = 1;
}

View File

@@ -8,7 +8,7 @@ pub use error::*;
pub use typed_envelope::*;
use collections::HashMap;
pub use prost::{DecodeError, Message};
pub use prost::Message;
use serde::Serialize;
use std::any::{Any, TypeId};
use std::time::Instant;
@@ -337,13 +337,7 @@ messages!(
(OpenNewBuffer, Foreground),
(RestartLanguageServers, Foreground),
(LinkedEditingRange, Background),
(LinkedEditingRangeResponse, Background),
(AdvertiseContexts, Foreground),
(OpenContext, Foreground),
(OpenContextResponse, Foreground),
(UpdateContext, Foreground),
(SynchronizeContexts, Foreground),
(SynchronizeContextsResponse, Foreground),
(LinkedEditingRangeResponse, Background)
);
request_messages!(
@@ -455,9 +449,7 @@ request_messages!(
(DeleteDevServerProject, Ack),
(RegenerateDevServerToken, RegenerateDevServerTokenResponse),
(RenameDevServer, Ack),
(RestartLanguageServers, Ack),
(OpenContext, OpenContextResponse),
(SynchronizeContexts, SynchronizeContextsResponse),
(RestartLanguageServers, Ack)
);
entity_messages!(
@@ -519,10 +511,6 @@ entity_messages!(
UpdateWorktree,
UpdateWorktreeSettings,
LspExtExpandMacro,
AdvertiseContexts,
OpenContext,
UpdateContext,
SynchronizeContexts,
);
entity_messages!(

View File

@@ -20,7 +20,6 @@ search.workspace = true
settings.workspace = true
ui.workspace = true
workspace.workspace = true
repl.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }

View File

@@ -20,11 +20,8 @@ use workspace::{
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
};
mod repl_menu;
pub struct QuickActionBar {
buffer_search_bar: View<BufferSearchBar>,
repl_menu: Option<View<ContextMenu>>,
toggle_settings_menu: Option<View<ContextMenu>>,
toggle_selections_menu: Option<View<ContextMenu>>,
active_item: Option<Box<dyn ItemHandle>>,
@@ -43,7 +40,6 @@ impl QuickActionBar {
buffer_search_bar,
toggle_settings_menu: None,
toggle_selections_menu: None,
repl_menu: None,
active_item: None,
_inlay_hints_enabled_subscription: None,
workspace: workspace.weak_handle(),
@@ -294,13 +290,9 @@ impl Render for QuickActionBar {
.child(
h_flex()
.gap(Spacing::Medium.rems(cx))
.children(self.render_repl_menu(cx))
.children(editor_selections_dropdown)
.child(editor_settings_dropdown),
)
.when_some(self.repl_menu.as_ref(), |el, repl_menu| {
el.child(Self::render_menu_overlay(repl_menu))
})
.when_some(
self.toggle_settings_menu.as_ref(),
|el, toggle_settings_menu| {

View File

@@ -1,116 +0,0 @@
use gpui::AnyElement;
use repl::{
ExecutionState, JupyterSettings, Kernel, KernelSpecification, RuntimePanel, Session,
SessionSupport,
};
use ui::{prelude::*, ButtonLike, IconWithIndicator, IntoElement, Tooltip};
use crate::QuickActionBar;
const ZED_REPL_DOCUMENTATION: &str = "https://zed.dev/docs/repl";
impl QuickActionBar {
pub fn render_repl_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
if !JupyterSettings::enabled(cx) {
return None;
}
let workspace = self.workspace.upgrade()?.read(cx);
let (editor, repl_panel) = if let (Some(editor), Some(repl_panel)) =
(self.active_editor(), workspace.panel::<RuntimePanel>(cx))
{
(editor, repl_panel)
} else {
return None;
};
let session = repl_panel.update(cx, |repl_panel, cx| {
repl_panel.session(editor.downgrade(), cx)
});
let session = match session {
SessionSupport::ActiveSession(session) => session.read(cx),
SessionSupport::Inactive(spec) => {
return self.render_repl_launch_menu(spec, cx);
}
SessionSupport::RequiresSetup(language) => {
return self.render_repl_setup(&language, cx);
}
SessionSupport::Unsupported => return None,
};
let kernel_name: SharedString = session.kernel_specification.name.clone().into();
let kernel_language: SharedString = session
.kernel_specification
.kernelspec
.language
.clone()
.into();
let tooltip = |session: &Session| match &session.kernel {
Kernel::RunningKernel(kernel) => match &kernel.execution_state {
ExecutionState::Idle => {
format!("Run code on {} ({})", kernel_name, kernel_language)
}
ExecutionState::Busy => format!("Interrupt {} ({})", kernel_name, kernel_language),
},
Kernel::StartingKernel(_) => format!("{} is starting", kernel_name),
Kernel::ErroredLaunch(e) => format!("Error with kernel {}: {}", kernel_name, e),
Kernel::ShuttingDown => format!("{} is shutting down", kernel_name),
Kernel::Shutdown => "Nothing running".to_string(),
};
let tooltip_text: SharedString = SharedString::from(tooltip(&session).clone());
let button = ButtonLike::new("toggle_repl_icon")
.child(
IconWithIndicator::new(Icon::new(IconName::Play), Some(session.kernel.dot()))
.indicator_border_color(Some(cx.theme().colors().border)),
)
.size(ButtonSize::Compact)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text(tooltip_text.clone(), cx))
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
.into_any_element();
Some(button)
}
pub fn render_repl_launch_menu(
&self,
kernel_specification: KernelSpecification,
_cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
let tooltip: SharedString =
SharedString::from(format!("Start REPL for {}", kernel_specification.name));
Some(
IconButton::new("toggle_repl_icon", IconName::Play)
.size(ButtonSize::Compact)
.icon_color(Color::Muted)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
.on_click(|_, cx| cx.dispatch_action(Box::new(repl::Run {})))
.into_any_element(),
)
}
pub fn render_repl_setup(
&self,
language: &str,
_cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
let tooltip: SharedString = SharedString::from(format!("Setup Zed REPL for {}", language));
Some(
IconButton::new("toggle_repl_icon", IconName::Play)
.size(ButtonSize::Compact)
.icon_color(Color::Muted)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
.on_click(|_, cx| cx.open_url(ZED_REPL_DOCUMENTATION))
.into_any_element(),
)
}
}

View File

@@ -82,7 +82,7 @@ pub enum Kernel {
}
impl Kernel {
pub fn dot(&self) -> Indicator {
pub fn dot(&mut self) -> Indicator {
match self {
Kernel::RunningKernel(kernel) => match kernel.execution_state {
ExecutionState::Idle => Indicator::dot().color(Color::Success),

View File

@@ -11,11 +11,7 @@ mod session;
mod stdio;
pub use jupyter_settings::JupyterSettings;
pub use kernels::{Kernel, KernelSpecification};
pub use runtime_panel::Run;
pub use runtime_panel::{RuntimePanel, SessionSupport};
pub use runtimelib::ExecutionState;
pub use session::Session;
pub use runtime_panel::RuntimePanel;
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {
struct ZedDispatcher {

View File

@@ -241,17 +241,6 @@ impl RuntimePanel {
Some((selected_text, language_name, anchor_range))
}
pub fn language(
&self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> Option<Arc<str>> {
match self.snippet(editor, cx) {
Some((_, language, _)) => Some(language),
None => None,
}
}
pub fn refresh_kernelspecs(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
let kernel_specifications = kernel_specifications(self.fs.clone());
cx.spawn(|this, mut cx| async move {
@@ -347,50 +336,6 @@ impl RuntimePanel {
}
}
pub enum SessionSupport {
ActiveSession(View<Session>),
Inactive(KernelSpecification),
RequiresSetup(String),
Unsupported,
}
impl RuntimePanel {
pub fn session(
&mut self,
editor: WeakView<Editor>,
cx: &mut ViewContext<Self>,
) -> SessionSupport {
let entity_id = editor.entity_id();
let session = self.sessions.get(&entity_id).cloned();
match session {
Some(session) => SessionSupport::ActiveSession(session),
None => {
let language = self.language(editor, cx);
let language = match language {
Some(language) => language,
None => return SessionSupport::Unsupported,
};
// Check for kernelspec
let kernelspec = self.kernelspec(&language, cx);
match kernelspec {
Some(kernelspec) => SessionSupport::Inactive(kernelspec),
None => {
let language: String = language.to_lowercase();
// If no kernelspec but language is one of typescript, python, r, or julia
// then we return RequiresSetup
match language.as_str() {
"typescript" | "python" => SessionSupport::RequiresSetup(language),
_ => SessionSupport::Unsupported,
}
}
}
}
}
}
}
impl Panel for RuntimePanel {
fn persistent_name() -> &'static str {
"RuntimePanel"

View File

@@ -22,11 +22,11 @@ use theme::{ActiveTheme, ThemeSettings};
use ui::{h_flex, prelude::*, v_flex, ButtonLike, ButtonStyle, Label};
pub struct Session {
pub editor: WeakView<Editor>,
pub kernel: Kernel,
editor: WeakView<Editor>,
kernel: Kernel,
blocks: HashMap<String, EditorBlock>,
pub messaging_task: Task<()>,
pub kernel_specification: KernelSpecification,
messaging_task: Task<()>,
kernel_specification: KernelSpecification,
}
struct EditorBlock {
@@ -310,7 +310,7 @@ impl Session {
}
}
pub fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
fn interrupt(&mut self, cx: &mut ViewContext<Self>) {
match &mut self.kernel {
Kernel::RunningKernel(_kernel) => {
self.send(InterruptRequest {}.into(), cx).ok();
@@ -322,7 +322,7 @@ impl Session {
}
}
pub fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
fn shutdown(&mut self, cx: &mut ViewContext<Self>) {
let kernel = std::mem::replace(&mut self.kernel, Kernel::ShuttingDown);
match kernel {

View File

@@ -14,7 +14,6 @@ collections.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
parking_lot.workspace = true
serde.workspace = true
serde_json.workspace = true
snippet.workspace = true

View File

@@ -1,5 +1,4 @@
mod format;
mod registry;
use std::{
path::{Path, PathBuf},
@@ -13,13 +12,8 @@ use format::VSSnippetsFile;
use fs::Fs;
use futures::stream::StreamExt;
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task, WeakModel};
pub use registry::*;
use util::ResultExt;
pub fn init(cx: &mut AppContext) {
SnippetRegistry::init_global(cx);
}
// Is `None` if the snippet file is global.
type SnippetKind = Option<String>;
fn file_stem_to_key(stem: &str) -> SnippetKind {
@@ -174,37 +168,28 @@ impl SnippetProvider {
Ok(())
})
}
fn lookup_snippets<'a>(
&'a self,
language: &'a SnippetKind,
cx: &AppContext,
) -> Vec<Arc<Snippet>> {
let mut user_snippets: Vec<_> = self
.snippets
.get(&language)
.cloned()
.unwrap_or_default()
.into_iter()
.flat_map(|(_, snippets)| snippets.into_iter())
.collect();
let Some(registry) = SnippetRegistry::try_global(cx) else {
return user_snippets;
};
let registry_snippets = registry.get_snippets(language);
user_snippets.extend(registry_snippets);
user_snippets
) -> Option<impl Iterator<Item = Arc<Snippet>> + 'a> {
Some(
self.snippets
.get(&language)?
.iter()
.flat_map(|(_, snippets)| snippets.iter().cloned()),
)
}
pub fn snippets_for(&self, language: SnippetKind, cx: &AppContext) -> Vec<Arc<Snippet>> {
let mut requested_snippets = self.lookup_snippets(&language, cx);
pub fn snippets_for(&self, language: SnippetKind) -> Vec<Arc<Snippet>> {
let mut requested_snippets: Vec<_> = self
.lookup_snippets(&language)
.map(|snippets| snippets.collect())
.unwrap_or_default();
if language.is_some() {
// Look up global snippets as well.
requested_snippets.extend(self.lookup_snippets(&None, cx));
if let Some(global_snippets) = self.lookup_snippets(&None) {
requested_snippets.extend(global_snippets);
}
}
requested_snippets
}

View File

@@ -1,53 +0,0 @@
use std::{path::Path, sync::Arc};
use anyhow::Result;
use collections::HashMap;
use gpui::{AppContext, Global, ReadGlobal, UpdateGlobal};
use parking_lot::RwLock;
use crate::{file_stem_to_key, Snippet, SnippetKind};
struct GlobalSnippetRegistry(Arc<SnippetRegistry>);
impl Global for GlobalSnippetRegistry {}
#[derive(Default)]
pub struct SnippetRegistry {
snippets: RwLock<HashMap<SnippetKind, Vec<Arc<Snippet>>>>,
}
impl SnippetRegistry {
pub fn global(cx: &AppContext) -> Arc<Self> {
GlobalSnippetRegistry::global(cx).0.clone()
}
pub fn try_global(cx: &AppContext) -> Option<Arc<Self>> {
cx.try_global::<GlobalSnippetRegistry>()
.map(|registry| registry.0.clone())
}
pub fn init_global(cx: &mut AppContext) {
GlobalSnippetRegistry::set_global(cx, GlobalSnippetRegistry(Arc::new(Self::new())))
}
pub fn new() -> Self {
Self {
snippets: RwLock::new(HashMap::default()),
}
}
pub fn register_snippets(&self, file_path: &Path, contents: &str) -> Result<()> {
let snippets_in_file: crate::format::VSSnippetsFile = serde_json::from_str(contents)?;
let kind = file_path
.file_stem()
.and_then(|stem| stem.to_str().and_then(file_stem_to_key));
let snippets = crate::file_to_snippets(snippets_in_file);
self.snippets.write().insert(kind, snippets);
Ok(())
}
pub fn get_snippets(&self, kind: &SnippetKind) -> Vec<Arc<Snippet>> {
self.snippets.read().get(kind).cloned().unwrap_or_default()
}
}

View File

@@ -1,15 +1,12 @@
use std::fmt::Debug;
use clock::ReplicaId;
use collections::{BTreeMap, HashSet};
pub struct Network<T: Clone, R: rand::Rng> {
inboxes: BTreeMap<ReplicaId, Vec<Envelope<T>>>,
disconnected_peers: HashSet<ReplicaId>,
inboxes: std::collections::BTreeMap<ReplicaId, Vec<Envelope<T>>>,
all_messages: Vec<T>,
rng: R,
}
#[derive(Clone, Debug)]
#[derive(Clone)]
struct Envelope<T: Clone> {
message: T,
}
@@ -17,8 +14,8 @@ struct Envelope<T: Clone> {
impl<T: Clone, R: rand::Rng> Network<T, R> {
pub fn new(rng: R) -> Self {
Network {
inboxes: BTreeMap::default(),
disconnected_peers: HashSet::default(),
inboxes: Default::default(),
all_messages: Vec::new(),
rng,
}
}
@@ -27,24 +24,6 @@ impl<T: Clone, R: rand::Rng> Network<T, R> {
self.inboxes.insert(id, Vec::new());
}
pub fn disconnect_peer(&mut self, id: ReplicaId) {
self.disconnected_peers.insert(id);
self.inboxes.get_mut(&id).unwrap().clear();
}
pub fn reconnect_peer(&mut self, id: ReplicaId, replicate_from: ReplicaId) {
assert!(self.disconnected_peers.remove(&id));
self.replicate(replicate_from, id);
}
pub fn is_disconnected(&self, id: ReplicaId) -> bool {
self.disconnected_peers.contains(&id)
}
pub fn contains_disconnected_peers(&self) -> bool {
!self.disconnected_peers.is_empty()
}
pub fn replicate(&mut self, old_replica_id: ReplicaId, new_replica_id: ReplicaId) {
self.inboxes
.insert(new_replica_id, self.inboxes[&old_replica_id].clone());
@@ -55,13 +34,8 @@ impl<T: Clone, R: rand::Rng> Network<T, R> {
}
pub fn broadcast(&mut self, sender: ReplicaId, messages: Vec<T>) {
// Drop messages from disconnected peers.
if self.disconnected_peers.contains(&sender) {
return;
}
for (replica, inbox) in self.inboxes.iter_mut() {
if *replica != sender && !self.disconnected_peers.contains(replica) {
if *replica != sender {
for message in &messages {
// Insert one or more duplicates of this message, potentially *before* the previous
// message sent by this peer to simulate out-of-order delivery.
@@ -77,6 +51,7 @@ impl<T: Clone, R: rand::Rng> Network<T, R> {
}
}
}
self.all_messages.extend(messages);
}
pub fn has_unreceived(&self, receiver: ReplicaId) -> bool {

View File

@@ -1265,10 +1265,6 @@ impl Buffer {
}
}
pub fn has_deferred_ops(&self) -> bool {
!self.deferred_ops.is_empty()
}
pub fn peek_undo_stack(&self) -> Option<&HistoryEntry> {
self.history.undo_stack.last()
}

View File

@@ -1,6 +1,6 @@
use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
use serde::{Deserialize, Serialize};
use strum::{EnumIter, EnumString, IntoStaticStr};
use strum::EnumIter;
use crate::{prelude::*, Indicator};
@@ -90,9 +90,7 @@ impl IconSize {
}
}
#[derive(
Debug, Eq, PartialEq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize,
)]
#[derive(Debug, PartialEq, Copy, Clone, EnumIter, Serialize, Deserialize)]
pub enum IconName {
Ai,
ArrowCircle,
@@ -160,11 +158,11 @@ pub enum IconName {
Font,
FontSize,
FontWeight,
GenericClose,
GenericMaximize,
GenericMinimize,
GenericRestore,
Github,
GenericMinimize,
GenericMaximize,
GenericClose,
GenericRestore,
Hash,
HistoryRerun,
Indicator,
@@ -194,10 +192,6 @@ pub enum IconName {
PullRequest,
Quote,
Regex,
ReplPlay,
ReplOff,
ReplPause,
ReplNeutral,
Replace,
ReplaceAll,
ReplaceNext,
@@ -235,12 +229,12 @@ pub enum IconName {
Trash,
TriangleRight,
Update,
Visible,
WholeWord,
XCircle,
ZedAssistant,
ZedAssistantFilled,
ZedXCopilot,
Visible,
}
impl IconName {
@@ -312,11 +306,11 @@ impl IconName {
IconName::Font => "icons/font.svg",
IconName::FontSize => "icons/font_size.svg",
IconName::FontWeight => "icons/font_weight.svg",
IconName::GenericClose => "icons/generic_close.svg",
IconName::GenericMaximize => "icons/generic_maximize.svg",
IconName::GenericMinimize => "icons/generic_minimize.svg",
IconName::GenericRestore => "icons/generic_restore.svg",
IconName::Github => "icons/github.svg",
IconName::GenericMinimize => "icons/generic_minimize.svg",
IconName::GenericMaximize => "icons/generic_maximize.svg",
IconName::GenericClose => "icons/generic_close.svg",
IconName::GenericRestore => "icons/generic_restore.svg",
IconName::Hash => "icons/hash.svg",
IconName::HistoryRerun => "icons/history_rerun.svg",
IconName::Indicator => "icons/indicator.svg",
@@ -346,10 +340,6 @@ impl IconName {
IconName::PullRequest => "icons/pull_request.svg",
IconName::Quote => "icons/quote.svg",
IconName::Regex => "icons/regex.svg",
IconName::ReplPlay => "icons/repl_play.svg",
IconName::ReplPause => "icons/repl_pause.svg",
IconName::ReplNeutral => "icons/repl_neutral.svg",
IconName::ReplOff => "icons/repl_off.svg",
IconName::Replace => "icons/replace.svg",
IconName::ReplaceAll => "icons/replace_all.svg",
IconName::ReplaceNext => "icons/replace_next.svg",
@@ -387,12 +377,12 @@ impl IconName {
IconName::Trash => "icons/trash.svg",
IconName::TriangleRight => "icons/triangle_right.svg",
IconName::Update => "icons/update.svg",
IconName::Visible => "icons/visible.svg",
IconName::WholeWord => "icons/word_search.svg",
IconName::XCircle => "icons/error.svg",
IconName::ZedAssistant => "icons/zed_assistant.svg",
IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
IconName::Visible => "icons/visible.svg",
}
}
}

View File

@@ -14,6 +14,7 @@ pub struct WorkspaceSettings {
pub restore_on_startup: RestoreOnStartupBehaviour,
pub drop_target_size: f32,
pub when_closing_with_no_tabs: CloseWindowWhenNoItems,
pub download_missing_binaries: bool,
}
#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)]

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.145.0"
version = "0.144.0"
publish = false
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]
@@ -87,7 +87,6 @@ serde_json.workspace = true
settings.workspace = true
simplelog = "0.9"
smol.workspace = true
snippet_provider.workspace = true
tab_switcher.workspace = true
supermaven.workspace = true
task.workspace = true

View File

@@ -54,32 +54,27 @@
<screenshots>
<screenshot type="default">
<caption>Zed with a large project open, showing language server and gitblame support</caption>
<image>https://zed.dev/img/flatpak/flatpak-1.png</image>
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-1.png</image>
</screenshot>
<screenshot>
<caption>Zed with a file open and a channel message thread in the right sidebar</caption>
<image>https://zed.dev/img/flatpak/flatpak-2.png</image>
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-2.png</image>
</screenshot>
<screenshot>
<caption>Example of a channel's shared document</caption>
<image>https://zed.dev/img/flatpak/flatpak-3.png</image>
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-3.png</image>
</screenshot>
<screenshot>
<caption>Zed's extension list</caption>
<image>https://zed.dev/img/flatpak/flatpak-4.png</image>
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-4.png</image>
</screenshot>
<screenshot>
<caption>Theme switcher UI and example theme</caption>
<image>https://zed.dev/img/flatpak/flatpak-5.png</image>
<image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-5.png</image>
</screenshot>
</screenshots>
<releases>
@release_info@
<release version="0.0.0" date="1970-01-01">
<description>
<p>Dummy release to keep flatpak-builder AppStream metadata validation from complaining</p>
</description>
</release>
@release_info@
</releases>
</component>

View File

@@ -10,7 +10,7 @@ Exec=$APP_CLI $APP_ARGS
Icon=$APP_ICON
Categories=Utility;TextEditor;Development;IDE;
Keywords=zed;
MimeType=text/plain;inode/directory;x-scheme-handler/zed;
MimeType=text/plain;inode/directory;
[Desktop Action NewWorkspace]
Exec=$APP_CLI --new $APP_ARGS

View File

@@ -206,7 +206,6 @@ fn init_ui(app_state: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
markdown_preview::init(cx);
welcome::init(cx);
extensions_ui::init(cx);
snippet_provider::init(cx);
// Initialize each completion provider. Settings are used for toggling between them.
let copilot_language_server_id = app_state.languages.next_language_server_id();

View File

@@ -22,7 +22,6 @@
- [Collaboration](./collaboration.md)
- [Tasks](./tasks.md)
- [Remote Development](./remote-development.md)
- [Repl](./repl.md)
# Language Support

View File

@@ -57,7 +57,7 @@ mkdir -p ~/.local
# extract zed to ~/.local/zed.app/
tar -xvf <path/to/download>.tar.gz -C ~/.local
# link the zed binary to ~/.local/bin (or another directory in your $PATH)
ln -sf ~/.local/zed.app/bin/zed ~/.local/bin/zed
ln -sf ~/.local/bin/zed ~/.local/zed.app/bin/zed
```
If you'd like integration with an XDG-compatible desktop environment, you will also need to install the `.desktop` file:

View File

@@ -1,72 +0,0 @@
# REPL
Read. Eval. Print. Loop.
<div class="warning">
This feature is in active development. Details may change. We're delighted to get feedback as the REPL feature evolves.
</div>
The built-in REPL for Zed allows you to run code interactively in your editor similarly to a notebook with your own text files.
<!-- TODO: Include GIF in action -->
To start using the REPL, add the following to your Zed `settings.json` to bring the power of [Jupyter kernels](https://docs.jupyter.org/en/latest/projects/kernels.html) to your editor:
```json
{
"jupyter": {
"enabled": true
}
}
```
After that, install any of the supported kernels:
* [Python](#python)
* [TypeScript via Deno](#deno)
## Python
### Global environment
To setup your current python to have an available kernel, run:
```
python -m ipykernel install --user
```
### Conda Environment
```
source activate myenv
conda install ipykernel
python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
```
### Virtualenv with pip
```
source activate myenv
pip install ipykernel
python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
```
## Deno
[Install Deno](https://docs.deno.com/runtime/manual/getting_started/installation/) and then install the Deno jupyter kernel:
```
deno jupyter --unstable --install
```
## Other languages
* [Julia](https://github.com/JuliaLang/IJulia.jl)
* R
- [Ark Kernel from Positron, formerly RStudio](https://github.com/posit-dev/ark)
- [Xeus-R](https://github.com/jupyter-xeus/xeus-r)
* [Scala](https://almond.sh/docs/quick-start-install)

View File

@@ -12,19 +12,19 @@ channel=$(<crates/zed/RELEASE_CHANNEL)
export CHANNEL="$channel"
export ARCHIVE="$archive"
if [[ "$channel" == "dev" ]]; then
export APP_ID="dev.zed.ZedDev"
export APP_ID="dev.zed.Zed-Dev"
export APP_NAME="Zed Devel"
export BRANDING_LIGHT="#99c1f1"
export BRANDING_DARK="#1a5fb4"
export ICON_FILE="app-icon-dev"
elif [[ "$channel" == "nightly" ]]; then
export APP_ID="dev.zed.ZedNightly"
export APP_ID="dev.zed.Zed-Nightly"
export APP_NAME="Zed Nightly"
export BRANDING_LIGHT="#e9aa6a"
export BRANDING_DARK="#1a5fb4"
export ICON_FILE="app-icon-nightly"
elif [[ "$channel" == "preview" ]]; then
export APP_ID="dev.zed.ZedPreview"
export APP_ID="dev.zed.Zed-Preview"
export APP_NAME="Zed Preview"
export BRANDING_LIGHT="#99c1f1"
export BRANDING_DARK="#1a5fb4"

View File

@@ -1,10 +1,6 @@
#!/usr/bin/env sh
set -eu
# Downloads the latest tarball from https://zed.dev/releases and unpacks it
# into ~/.local/. If you'd prefer to do this manually, instructions are at
# https://zed.dev/docs/linux.
main() {
platform="$(uname -s)"
arch="$(uname -m)"
@@ -15,6 +11,7 @@ main() {
platform="macos"
elif [ "$platform" = "Linux" ]; then
platform="linux"
channel="${ZED_CHANNEL:-preview}"
else
echo "Unsupported platform $platform"
exit 1