Merge branch 'zed-industries:main' into add-edit-channel-message

This commit is contained in:
Remco Smits
2024-03-06 15:40:55 +01:00
committed by GitHub
345 changed files with 8348 additions and 2262 deletions

View File

@@ -10,3 +10,6 @@
# in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it.
[build]
rustflags = ["-D", "warnings"]
[alias]
xtask = "run --package xtask --"

View File

@@ -1,3 +1,6 @@
[build]
# v0 mangling scheme provides more detailed backtraces around closures
rustflags = ["-C", "symbol-mangling-version=v0"]
rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"]
[alias]
xtask = "run --package xtask --"

View File

@@ -1,16 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: Language Request
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E
about: Request a language in the extensions repository
- name: Theme Request
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=theme&projects=&template=0_theme_request.yml&title=%3Cname_of_theme%3E+theme
about: Request a theme in the extensions repository
- name: Top-Ranking Issues
url: https://github.com/zed-industries/zed/issues/5393
about: See an overview of the most popular Zed issues
- name: Platform Support
url: https://github.com/zed-industries/zed/issues/5391
about: A quick note on platform support
- name: Positive Feedback
url: https://github.com/zed-industries/zed/discussions/5397
about: A central location for kind words about Zed
- name: Language Request
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E
about: Request a language in the extensions repository
- name: Theme Request
url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=theme&projects=&template=0_theme_request.yml&title=%3Cname_of_theme%3E+theme
about: Request a theme in the extensions repository
- name: Top-Ranking Issues
url: https://github.com/zed-industries/zed/issues/5393
about: See an overview of the most popular Zed issues
- name: Platform Support
url: https://github.com/zed-industries/zed/issues/5391
about: A quick note on platform support
- name: Positive Feedback
url: https://github.com/zed-industries/zed/discussions/5397
about: A central location for kind words about Zed

View File

@@ -93,8 +93,7 @@ jobs:
fi
- name: cargo clippy
shell: bash -euxo pipefail {0}
run: script/clippy
run: cargo xtask clippy
- name: Run tests
uses: ./.github/actions/run_tests
@@ -126,8 +125,7 @@ jobs:
run: script/linux
- name: cargo clippy
shell: bash -euxo pipefail {0}
run: script/clippy
run: cargo xtask clippy
- name: Build Zed
run: cargo build -p zed
@@ -149,8 +147,7 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: cargo clippy
shell: bash -euxo pipefail {0}
run: script/clippy
run: cargo xtask clippy
- name: Build Zed
run: cargo build -p zed

View File

@@ -28,8 +28,7 @@ jobs:
uses: ./.github/actions/check_style
- name: Run clippy
shell: bash -euxo pipefail {0}
run: script/clippy
run: cargo xtask clippy
tests:
name: Run tests
@@ -105,8 +104,12 @@ jobs:
set -eu
if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
export ZED_KUBE_NAMESPACE=production
export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=10
export ZED_API_LOAD_BALANCER_SIZE_UNIT=2
elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
export ZED_KUBE_NAMESPACE=staging
export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=1
export ZED_API_LOAD_BALANCER_SIZE_UNIT=1
else
echo "cowardly refusing to deploy from an unknown branch"
exit 1
@@ -121,11 +124,13 @@ jobs:
export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
export ZED_SERVICE_NAME=collab
export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
export ZED_SERVICE_NAME=api
export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_API_LOAD_BALANCER_SIZE_UNIT
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch
echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"

View File

@@ -32,8 +32,7 @@ jobs:
uses: ./.github/actions/check_style
- name: Run clippy
shell: bash -euxo pipefail {0}
run: script/clippy
run: cargo xtask clippy
tests:
name: Run tests
if: github.repository_owner == 'zed-industries'

1063
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -93,6 +93,7 @@ members = [
"crates/zed",
"crates/zed_actions",
"extensions/gleam",
"tooling/xtask",
]
default-members = ["crates/zed"]
resolver = "2"
@@ -196,10 +197,11 @@ async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-tar = "0.4.2"
async-trait = "0.1"
bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "43721bf42d298b7cbee2195ee66f73a5f1c7b2fc" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "43721bf42d298b7cbee2195ee66f73a5f1c7b2fc" }
blade-rwh = { package = "raw-window-handle", version = "0.5" }
chrono = { version = "0.4", features = ["serde"] }
clap = "4.4"
clickhouse = { version = "0.11.6" }
ctor = "0.2.6"
core-foundation = { version = "0.9.3" }
@@ -307,6 +309,19 @@ wasmtime-wasi = "18.0"
which = "6.0.0"
sys-locale = "0.3.1"
[workspace.dependencies.windows]
version = "0.53.0"
features = [
"Win32_Graphics_Gdi",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_System_SystemServices",
"Win32_Security",
"Win32_System_Threading",
"Win32_System_DataExchange",
"Win32_System_Ole",
]
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "e4a23971ec3071a09c1e84816954c98f96e98e52" }
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
@@ -324,6 +339,8 @@ debug = "full"
[profile.dev.package]
taffy = { opt-level = 3 }
cranelift-codegen = { opt-level = 3 }
rustybuzz = { opt-level = 3 }
ttf-parser = { opt-level = 3 }
wasmtime-cranelift = { opt-level = 3 }
[profile.release]
@@ -331,5 +348,42 @@ debug = "limited"
lto = "thin"
codegen-units = 1
[workspace.lints.clippy]
dbg_macro = "deny"
todo = "deny"
# These are all of the rules that currently have violations in the Zed
# codebase.
#
# We'll want to drive this list down by either:
# 1. fixing violations of the rule and begin enforcing it
# 2. deciding we want to allow the rule permanently, at which point
# we should codify that separately above.
#
# This list shouldn't be added to; it should only get shorter.
# =============================================================================
# There are a bunch of rules currently failing in the `style` group, so
# allow all of those, for now.
style = "allow"
# Individual rules that have violations in the codebase:
almost_complete_range = "allow"
arc_with_non_send_sync = "allow"
await_holding_lock = "allow"
borrowed_box = "allow"
cast_abs_to_unsigned = "allow"
derive_ord_xor_partial_ord = "allow"
eq_op = "allow"
let_underscore_future = "allow"
map_entry = "allow"
never_loop = "allow"
non_canonical_clone_impl = "allow"
non_canonical_partial_ord_impl = "allow"
reversed_empty_ranges = "allow"
single_range_in_vec_init = "allow"
suspicious_to_owned = "allow"
type_complexity = "allow"
[workspace.metadata.cargo-machete]
ignored = ["bindgen", "cbindgen", "prost_build", "serde"]

View File

@@ -135,10 +135,21 @@
"focus": true
}
],
"alt-\\": "copilot::Suggest",
"ctrl->": "assistant::QuoteSelection"
}
},
{
"context": "Editor && mode == full && copilot_suggestion",
"bindings": {
"alt-]": "copilot::NextSuggestion",
"alt-[": "copilot::PreviousSuggestion",
"ctrl->": "assistant::QuoteSelection"
"alt-right": "editor::AcceptPartialCopilotSuggestion"
}
},
{
"context": "Editor && !copilot_suggestion",
"bindings": {
"alt-\\": "copilot::Suggest"
}
},
{
@@ -354,6 +365,7 @@
"ctrl-alt-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"ctrl-s": "workspace::Save",
"ctrl-k s": "workspace::SaveWithoutFormat",
"ctrl-shift-s": "workspace::SaveAs",
"ctrl-n": "workspace::NewFile",
"ctrl-shift-n": "workspace::NewWindow",

View File

@@ -176,10 +176,21 @@
"focus": false
}
],
"alt-\\": "copilot::Suggest",
"cmd->": "assistant::QuoteSelection"
}
},
{
"context": "Editor && mode == full && copilot_suggestion",
"bindings": {
"alt-]": "copilot::NextSuggestion",
"alt-[": "copilot::PreviousSuggestion",
"cmd->": "assistant::QuoteSelection"
"alt-right": "editor::AcceptPartialCopilotSuggestion"
}
},
{
"context": "Editor && !copilot_suggestion",
"bindings": {
"alt-\\": "copilot::Suggest"
}
},
{
@@ -397,6 +408,7 @@
"alt-cmd-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"cmd-s": "workspace::Save",
"cmd-k s": "workspace::SaveWithoutFormat",
"cmd-shift-s": "workspace::SaveAs",
"cmd-n": "workspace::NewFile",
"cmd-shift-n": "workspace::NewWindow",
@@ -415,8 +427,8 @@
"cmd-j": "workspace::ToggleBottomDock",
"alt-cmd-y": "workspace::CloseAllDocks",
"cmd-shift-f": "pane::DeploySearch",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-t": "project_symbols::Toggle",
"cmd-p": "file_finder::Toggle",
"cmd-shift-p": "command_palette::Toggle",

View File

@@ -218,6 +218,7 @@
// z commands
"z t": "editor::ScrollCursorTop",
"z z": "editor::ScrollCursorCenter",
"z .": ["workspace::SendKeystrokes", "z z ^"],
"z b": "editor::ScrollCursorBottom",
"z c": "editor::Fold",
"z o": "editor::UnfoldLines",
@@ -392,6 +393,7 @@
],
"t": "vim::Tag",
"s": "vim::Sentence",
"p": "vim::Paragraph",
"'": "vim::Quotes",
"`": "vim::BackQuotes",
"\"": "vim::DoubleQuotes",

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/activity_indicator.rs"
doctest = false

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/ai.rs"
doctest = false

View File

@@ -19,11 +19,9 @@ pub struct Embedding(pub Vec<f32>);
impl FromSql for Embedding {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
let bytes = value.as_blob()?;
let embedding: Result<Vec<f32>, Box<bincode::ErrorKind>> = bincode::deserialize(bytes);
if embedding.is_err() {
return Err(rusqlite::types::FromSqlError::Other(embedding.unwrap_err()));
}
Ok(Embedding(embedding.unwrap()))
let embedding =
bincode::deserialize(bytes).map_err(|err| rusqlite::types::FromSqlError::Other(err))?;
Ok(Embedding(embedding))
}
}
@@ -112,7 +110,7 @@ mod tests {
}
fn round_to_decimals(n: OrderedFloat<f32>, decimal_places: i32) -> f32 {
let factor = (10.0 as f32).powi(decimal_places);
let factor = 10.0_f32.powi(decimal_places);
(n * factor).round() / factor
}

View File

@@ -30,7 +30,7 @@ impl PromptArguments {
if self
.language_name
.as_ref()
.and_then(|name| Some(!["Markdown", "Plain Text"].contains(&name.as_str())))
.map(|name| !["Markdown", "Plain Text"].contains(&name.as_str()))
.unwrap_or(true)
{
PromptFileType::Code
@@ -51,8 +51,10 @@ pub trait PromptTemplate {
#[repr(i8)]
#[derive(PartialEq, Eq, Ord)]
pub enum PromptPriority {
Mandatory, // Ignores truncation
Ordered { order: usize }, // Truncates based on priority
/// Ignores truncation.
Mandatory,
/// Truncates based on priority.
Ordered { order: usize },
}
impl PartialOrd for PromptPriority {
@@ -86,7 +88,6 @@ impl PromptChain {
let mut sorted_indices = (0..self.templates.len()).collect::<Vec<_>>();
sorted_indices.sort_by_key(|&i| Reverse(&self.templates[i].0));
// If Truncate
let mut tokens_outstanding = if truncate {
Some(self.args.model.capacity()? - self.args.reserved_tokens)
} else {

View File

@@ -24,11 +24,9 @@ impl PromptCodeSnippet {
let language_name = buffer
.language()
.and_then(|language| Some(language.name().to_string().to_lowercase()));
.map(|language| language.name().to_string().to_lowercase());
let file_path = buffer
.file()
.and_then(|file| Some(file.path().to_path_buf()));
let file_path = buffer.file().map(|file| file.path().to_path_buf());
(content, language_name, file_path)
})?;
@@ -46,7 +44,7 @@ impl ToString for PromptCodeSnippet {
let path = self
.path
.as_ref()
.and_then(|path| Some(path.to_string_lossy().to_string()))
.map(|path| path.to_string_lossy().to_string())
.unwrap_or("".to_string());
let language_name = self.language_name.clone().unwrap_or("".to_string());
let content = self.content.clone();
@@ -67,7 +65,7 @@ impl PromptTemplate for RepositoryContext {
let template = "You are working inside a large repository, here are a few code snippets that may be useful.";
let mut prompt = String::new();
let mut remaining_tokens = max_token_length.clone();
let mut remaining_tokens = max_token_length;
let separator_token_length = args.model.count_tokens("\n")?;
for snippet in &args.snippets {
let mut snippet_prompt = template.to_string();

View File

@@ -54,6 +54,7 @@ impl LanguageModel for FakeLanguageModel {
}
}
#[derive(Default)]
pub struct FakeEmbeddingProvider {
pub embedding_count: AtomicUsize,
}
@@ -66,14 +67,6 @@ impl Clone for FakeEmbeddingProvider {
}
}
impl Default for FakeEmbeddingProvider {
fn default() -> Self {
FakeEmbeddingProvider {
embedding_count: AtomicUsize::default(),
}
}
}
impl FakeEmbeddingProvider {
pub fn embedding_count(&self) -> usize {
self.embedding_count.load(atomic::Ordering::SeqCst)

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[dependencies]
anyhow.workspace = true
gpui.workspace = true

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/assistant.rs"
doctest = false

View File

@@ -652,7 +652,7 @@ impl AssistantPanel {
// If Markdown or No Language is Known, increase the randomness for more creative output
// If Code, decrease temperature to get more deterministic outputs
let temperature = if let Some(language) = language_name.clone() {
if language.to_string() != "Markdown".to_string() {
if *language != *"Markdown" {
0.5
} else {
1.0
@@ -979,7 +979,7 @@ impl AssistantPanel {
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3).into(),
line_height: relative(1.3),
background_color: None,
underline: None,
strikethrough: None,
@@ -1483,7 +1483,7 @@ impl Conversation {
max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
pending_token_count: Task::ready(None),
api_url: Some(api_url),
model: model.clone(),
model,
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
pending_save: Task::ready(Ok(())),
path: None,
@@ -1527,7 +1527,7 @@ impl Conversation {
.as_ref()
.map(|summary| summary.text.clone())
.unwrap_or_default(),
model: self.model.clone(),
model: self.model,
api_url: self.api_url.clone(),
}
}
@@ -1633,26 +1633,23 @@ impl Conversation {
fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
let messages = self
.messages(cx)
.into_iter()
.filter_map(|message| {
Some(tiktoken_rs::ChatCompletionRequestMessage {
role: match message.role {
Role::User => "user".into(),
Role::Assistant => "assistant".into(),
Role::System => "system".into(),
},
content: Some(
self.buffer
.read(cx)
.text_for_range(message.offset_range)
.collect(),
),
name: None,
function_call: None,
})
.map(|message| tiktoken_rs::ChatCompletionRequestMessage {
role: match message.role {
Role::User => "user".into(),
Role::Assistant => "assistant".into(),
Role::System => "system".into(),
},
content: Some(
self.buffer
.read(cx)
.text_for_range(message.offset_range)
.collect(),
),
name: None,
function_call: None,
})
.collect::<Vec<_>>();
let model = self.model.clone();
let model = self.model;
self.pending_token_count = cx.spawn(|this, mut cx| {
async move {
cx.background_executor()
@@ -2835,6 +2832,7 @@ impl FocusableView for InlineAssistant {
}
impl InlineAssistant {
#[allow(clippy::too_many_arguments)]
fn new(
id: usize,
measurements: Rc<Cell<BlockMeasurements>>,
@@ -3200,7 +3198,7 @@ impl InlineAssistant {
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3).into(),
line_height: relative(1.3),
background_color: None,
underline: None,
strikethrough: None,

View File

@@ -111,9 +111,23 @@ impl AssistantSettings {
AiProviderSettings::OpenAi(settings) => {
Ok(settings.default_model.unwrap_or(OpenAiModel::FourTurbo))
}
AiProviderSettings::AzureOpenAi(_settings) => {
// TODO: We need to use an Azure OpenAI model here.
Ok(OpenAiModel::FourTurbo)
AiProviderSettings::AzureOpenAi(settings) => {
let deployment_id = settings
.deployment_id
.as_deref()
.ok_or_else(|| anyhow!("no Azure OpenAI deployment ID"))?;
match deployment_id {
// https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#gpt-4-and-gpt-4-turbo-preview
"gpt-4" | "gpt-4-32k" => Ok(OpenAiModel::Four),
// https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#gpt-35
"gpt-35-turbo" | "gpt-35-turbo-16k" | "gpt-35-turbo-instruct" => {
Ok(OpenAiModel::ThreePointFiveTurbo)
}
_ => Err(anyhow!(
"no matching OpenAI model found for deployment ID: '{deployment_id}'"
)),
}
}
}
}

View File

@@ -297,7 +297,7 @@ fn strip_invalid_spans_from_codeblock(
} else if buffer.starts_with("<|")
|| buffer.starts_with("<|S")
|| buffer.starts_with("<|S|")
|| buffer.ends_with("|")
|| buffer.ends_with('|')
|| buffer.ends_with("|E")
|| buffer.ends_with("|E|")
{
@@ -335,7 +335,7 @@ fn strip_invalid_spans_from_codeblock(
.strip_suffix("|E|>")
.or_else(|| text.strip_suffix("E|>"))
.or_else(|| text.strip_prefix("|>"))
.or_else(|| text.strip_prefix(">"))
.or_else(|| text.strip_prefix('>'))
.unwrap_or(&text)
.to_string();
};

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/audio.rs"
doctest = false

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/auto_update.rs"
doctest = false

View File

@@ -343,8 +343,7 @@ impl AutoUpdater {
));
cx.update(|cx| {
if let Some(param) = ReleaseChannel::try_global(cx)
.map(|release_channel| release_channel.release_query_param())
.flatten()
.and_then(|release_channel| release_channel.release_query_param())
{
url_string += "&";
url_string += param;

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/breadcrumbs.rs"
doctest = false

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/call.rs"
doctest = false

View File

@@ -302,7 +302,7 @@ impl ActiveCall {
return Task::ready(Ok(()));
}
let room_id = call.room_id.clone();
let room_id = call.room_id;
let client = self.client.clone();
let user_store = self.user_store.clone();
let join = self

View File

@@ -1182,19 +1182,10 @@ impl Room {
) -> Task<Result<Model<Project>>> {
let client = self.client.clone();
let user_store = self.user_store.clone();
let role = self.local_participant.role;
cx.emit(Event::RemoteProjectJoined { project_id: id });
cx.spawn(move |this, mut cx| async move {
let project = Project::remote(
id,
client,
user_store,
language_registry,
fs,
role,
cx.clone(),
)
.await?;
let project =
Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
this.update(&mut cx, |this, cx| {
this.joined_projects.retain(|project| {

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/channel.rs"
doctest = false

View File

@@ -11,7 +11,7 @@ pub use channel_chat::{
mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId,
MessageParams,
};
pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore, HostedProjectId};
pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore};
#[cfg(test)]
mod channel_store_tests;

View File

@@ -126,7 +126,7 @@ impl ChannelBuffer {
for (_, old_collaborator) in &self.collaborators {
if !new_collaborators.contains_key(&old_collaborator.peer_id) {
self.buffer.update(cx, |buffer, cx| {
buffer.remove_peer(old_collaborator.replica_id as u16, cx)
buffer.remove_peer(old_collaborator.replica_id, cx)
});
}
}

View File

@@ -759,7 +759,7 @@ pub fn mentions_to_proto(mentions: &[(Range<usize>, UserId)]) -> Vec<proto::Chat
start: range.start as u64,
end: range.end as u64,
}),
user_id: *user_id as u64,
user_id: *user_id,
})
.collect()
}

View File

@@ -3,7 +3,9 @@ mod channel_index;
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
use anyhow::{anyhow, Result};
use channel_index::ChannelIndex;
use client::{ChannelId, Client, Subscription, User, UserId, UserStore};
use client::{
ChannelId, Client, ClientSettings, HostedProjectId, Subscription, User, UserId, UserStore,
};
use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{
@@ -11,11 +13,11 @@ use gpui::{
Task, WeakModel,
};
use language::Capability;
use release_channel::RELEASE_CHANNEL;
use rpc::{
proto::{self, ChannelRole, ChannelVisibility},
TypedEnvelope,
};
use settings::Settings;
use std::{mem, sync::Arc, time::Duration};
use util::{async_maybe, maybe, ResultExt};
@@ -27,9 +29,6 @@ pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppCont
cx.set_global(GlobalChannelStore(channel_store));
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct HostedProjectId(pub u64);
#[derive(Debug, Clone, Default)]
struct NotesVersion {
epoch: u64,
@@ -93,16 +92,17 @@ pub struct ChannelState {
}
impl Channel {
pub fn link(&self) -> String {
RELEASE_CHANNEL.link_prefix().to_owned()
+ "channel/"
+ &Self::slug(&self.name)
+ "-"
+ &self.id.to_string()
pub fn link(&self, cx: &AppContext) -> String {
format!(
"{}/channel/{}-{}",
ClientSettings::get_global(cx).server_url,
Self::slug(&self.name),
self.id
)
}
pub fn notes_link(&self, heading: Option<String>) -> String {
self.link()
pub fn notes_link(&self, heading: Option<String>, cx: &AppContext) -> String {
self.link(cx)
+ "/notes"
+ &heading
.map(|h| format!("#{}", Self::slug(&h)))
@@ -592,7 +592,7 @@ impl ChannelStore {
cx: &mut ModelContext<Self>,
) -> Task<Result<ChannelId>> {
let client = self.client.clone();
let name = name.trim_start_matches("#").to_owned();
let name = name.trim_start_matches('#').to_owned();
cx.spawn(move |this, mut cx| async move {
let response = client
.request(proto::CreateChannel {
@@ -839,12 +839,10 @@ impl ChannelStore {
Ok(users
.into_iter()
.zip(response.members)
.filter_map(|(user, member)| {
Some(ChannelMembership {
user,
role: member.role(),
kind: member.kind(),
})
.map(|(user, member)| ChannelMembership {
user,
role: member.role(),
kind: member.kind(),
})
.collect())
})

View File

@@ -97,9 +97,9 @@ impl<'a> Drop for ChannelPathsInsertGuard<'a> {
}
}
fn channel_path_sorting_key<'a>(
fn channel_path_sorting_key(
id: ChannelId,
channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
channels_by_id: &BTreeMap<ChannelId, Arc<Channel>>,
) -> impl Iterator<Item = (&str, ChannelId)> {
let (parent_path, name) = channels_by_id
.get(&id)

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/cli.rs"
doctest = false
@@ -15,6 +18,7 @@ path = "src/main.rs"
[dependencies]
anyhow.workspace = true
# TODO: Use workspace version of `clap`.
clap = { version = "3.1", features = ["derive"] }
ipc-channel = "0.16"
serde.workspace = true

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/client.rs"
doctest = false

View File

@@ -27,7 +27,7 @@ use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json;
use settings::{Settings, SettingsStore};
use std::{
any::TypeId,
@@ -61,7 +61,7 @@ lazy_static! {
pub static ref ZED_APP_PATH: Option<PathBuf> =
std::env::var("ZED_APP_PATH").ok().map(PathBuf::from);
pub static ref ZED_ALWAYS_ACTIVE: bool =
std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| e.len() > 0);
std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| !e.is_empty());
}
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
@@ -427,7 +427,7 @@ impl Client {
http: Arc<HttpClientWithUrl>,
cx: &mut AppContext,
) -> Arc<Self> {
let client = Arc::new(Self {
Arc::new(Self {
id: AtomicU64::new(0),
peer: Peer::new(0),
telemetry: Telemetry::new(clock, http.clone(), cx),
@@ -438,9 +438,7 @@ impl Client {
authenticate: Default::default(),
#[cfg(any(test, feature = "test-support"))]
establish_connection: Default::default(),
});
client
})
}
pub fn id(&self) -> u64 {
@@ -573,17 +571,18 @@ impl Client {
let mut state = self.state.write();
if state.entities_by_type_and_remote_id.contains_key(&id) {
return Err(anyhow!("already subscribed to entity"));
} else {
state
.entities_by_type_and_remote_id
.insert(id, WeakSubscriber::Pending(Default::default()));
Ok(PendingEntitySubscription {
client: self.clone(),
remote_id,
consumed: false,
_entity_type: PhantomData,
})
}
state
.entities_by_type_and_remote_id
.insert(id, WeakSubscriber::Pending(Default::default()));
Ok(PendingEntitySubscription {
client: self.clone(),
remote_id,
consumed: false,
_entity_type: PhantomData,
})
}
#[track_caller]
@@ -926,7 +925,7 @@ impl Client {
move |cx| async move {
match handle_io.await {
Ok(()) => {
if this.status().borrow().clone()
if *this.status().borrow()
== (Status::Connected {
connection_id,
peer_id,
@@ -1335,7 +1334,7 @@ impl Client {
pending.push(message);
return;
}
Some(weak_subscriber @ _) => match weak_subscriber {
Some(weak_subscriber) => match weak_subscriber {
WeakSubscriber::Entity { handle } => {
subscriber = handle.upgrade();
}
@@ -1438,21 +1437,29 @@ async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
.await
}
const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";
/// prefix for the zed:// url scheme
pub static ZED_URL_SCHEME: &str = "zed";
pub fn encode_worktree_url(id: u64, access_token: &str) -> String {
format!("{}{}/{}", WORKTREE_URL_PREFIX, id, access_token)
}
pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
let path = url.trim().strip_prefix(WORKTREE_URL_PREFIX)?;
let mut parts = path.split('/');
let id = parts.next()?.parse::<u64>().ok()?;
let access_token = parts.next()?;
if access_token.is_empty() {
return None;
/// Parses the given link into a Zed link.
///
/// Returns a [`Some`] containing the unprefixed link if the link is a Zed link.
/// Returns [`None`] otherwise.
pub fn parse_zed_link<'a>(link: &'a str, cx: &AppContext) -> Option<&'a str> {
let server_url = &ClientSettings::get_global(cx).server_url;
if let Some(stripped) = link
.strip_prefix(server_url)
.and_then(|result| result.strip_prefix('/'))
{
return Some(stripped);
}
Some((id, access_token.to_string()))
if let Some(stripped) = link
.strip_prefix(ZED_URL_SCHEME)
.and_then(|result| result.strip_prefix("://"))
{
return Some(stripped);
}
None
}
#[cfg(test)]
@@ -1630,17 +1637,6 @@ mod tests {
assert_eq!(*dropped_auth_count.lock(), 1);
}
#[test]
fn test_encode_and_decode_worktree_url() {
let url = encode_worktree_url(5, "deadbeef");
assert_eq!(decode_worktree_url(&url), Some((5, "deadbeef".to_string())));
assert_eq!(
decode_worktree_url(&format!("\n {}\t", url)),
Some((5, "deadbeef".to_string()))
);
assert_eq!(decode_worktree_url("not://the-right-format"), None);
}
#[gpui::test]
async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
init_test(cx);

View File

@@ -84,7 +84,7 @@ impl Telemetry {
TelemetrySettings::register(cx);
let state = Arc::new(Mutex::new(TelemetryState {
settings: TelemetrySettings::get_global(cx).clone(),
settings: *TelemetrySettings::get_global(cx),
app_metadata: cx.app_metadata(),
architecture: env::consts::ARCH,
release_channel,
@@ -119,7 +119,7 @@ impl Telemetry {
move |cx| {
let mut state = state.lock();
state.settings = TelemetrySettings::get_global(cx).clone();
state.settings = *TelemetrySettings::get_global(cx);
}
})
.detach();
@@ -168,7 +168,7 @@ impl Telemetry {
) {
let mut state = self.state.lock();
state.installation_id = installation_id.map(|id| id.into());
state.session_id = Some(session_id.into());
state.session_id = Some(session_id);
drop(state);
let this = self.clone();
@@ -387,11 +387,9 @@ impl Telemetry {
event,
});
if state.installation_id.is_some() {
if state.events_queue.len() >= state.max_queue_size {
drop(state);
self.flush_events();
}
if state.installation_id.is_some() && state.events_queue.len() >= state.max_queue_size {
drop(state);
self.flush_events();
}
}
@@ -433,7 +431,7 @@ impl Telemetry {
json_bytes.clear();
serde_json::to_writer(&mut json_bytes, event)?;
file.write_all(&json_bytes)?;
file.write(b"\n")?;
file.write_all(b"\n")?;
}
}
@@ -442,7 +440,7 @@ impl Telemetry {
let request_body = EventRequestBody {
installation_id: state.installation_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
is_staff: state.is_staff.clone(),
is_staff: state.is_staff,
app_version: state
.app_metadata
.app_version

View File

@@ -24,6 +24,9 @@ impl std::fmt::Display for ChannelId {
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct HostedProjectId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParticipantIndex(pub u32);
@@ -653,7 +656,7 @@ impl UserStore {
let users = response
.users
.into_iter()
.map(|user| User::new(user))
.map(User::new)
.collect::<Vec<_>>();
this.update(&mut cx, |this, _| {

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/clock.rs"
doctest = false

View File

@@ -7,6 +7,9 @@ version = "0.44.0"
publish = false
license = "AGPL-3.0-or-later"
[lints]
workspace = true
[[bin]]
name = "collab"
@@ -14,12 +17,13 @@ name = "collab"
name = "seed"
[dependencies]
console-subscriber = "0.2"
anyhow.workspace = true
async-tungstenite = "0.16"
aws-config = { version = "1.1.5" }
aws-sdk-s3 = { version = "1.15.0" }
axum = { version = "0.5", features = ["json", "headers", "ws"] }
axum-extra = { version = "0.3", features = ["erased-json"] }
axum = { version = "0.6", features = ["json", "headers", "ws"] }
axum-extra = { version = "0.4", features = ["erased-json"] }
chrono.workspace = true
clock.workspace = true
clickhouse.workspace = true
@@ -28,7 +32,6 @@ dashmap = "5.4"
envy = "0.4.2"
futures.workspace = true
hex.workspace = true
hyper = "0.14"
live_kit_server.workspace = true
log.workspace = true
nanoid = "0.4"
@@ -55,8 +58,7 @@ toml.workspace = true
tower = "0.4"
tower-http = { workspace = true, features = ["trace"] }
tracing = "0.1.34"
tracing-log = "0.1.3"
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json", "registry", "tracing-log"] }
util.workspace = true
uuid.workspace = true

View File

@@ -11,6 +11,8 @@ metadata:
namespace: ${ZED_KUBE_NAMESPACE}
name: ${ZED_SERVICE_NAME}
annotations:
service.beta.kubernetes.io/do-loadbalancer-name: "${ZED_SERVICE_NAME}-${ZED_KUBE_NAMESPACE}"
service.beta.kubernetes.io/do-loadbalancer-size-unit: "${ZED_LOAD_BALANCER_SIZE_UNIT}"
service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443"
service.beta.kubernetes.io/do-loadbalancer-certificate-id: ${ZED_DO_CERTIFICATE_ID}
service.beta.kubernetes.io/do-loadbalancer-disable-lets-encrypt-dns-records: "true"
@@ -33,6 +35,11 @@ metadata:
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: ${ZED_SERVICE_NAME}
@@ -63,6 +70,8 @@ spec:
ports:
- containerPort: 8080
protocol: TCP
- containerPort: 6669
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
@@ -76,6 +85,13 @@ spec:
port: 8080
initialDelaySeconds: 1
periodSeconds: 1
startupProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 1
periodSeconds: 1
failureThreshold: 15
env:
- name: HTTP_PORT
value: "8080"
@@ -176,3 +192,4 @@ spec:
# FIXME - Switch to the more restrictive `PERFMON` capability.
# This capability isn't yet available in a stable version of Debian.
add: ["SYS_ADMIN"]
terminationGracePeriodSeconds: 10

View File

@@ -5,6 +5,7 @@ metadata:
namespace: ${ZED_KUBE_NAMESPACE}
name: postgrest
annotations:
service.beta.kubernetes.io/do-loadbalancer-name: "postgrest-${ZED_KUBE_NAMESPACE}"
service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443"
service.beta.kubernetes.io/do-loadbalancer-certificate-id: ${ZED_DO_CERTIFICATE_ID}
service.beta.kubernetes.io/do-loadbalancer-disable-lets-encrypt-dns-records: "true"

View File

@@ -46,10 +46,11 @@ CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id");
CREATE TABLE "projects" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"room_id" INTEGER REFERENCES rooms (id) ON DELETE CASCADE NOT NULL,
"host_user_id" INTEGER REFERENCES users (id) NOT NULL,
"host_user_id" INTEGER REFERENCES users (id),
"host_connection_id" INTEGER,
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
"hosted_project_id" INTEGER REFERENCES hosted_projects (id)
);
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");

View File

@@ -0,0 +1,3 @@
-- Add migration script here
ALTER TABLE projects ALTER COLUMN host_user_id DROP NOT NULL;
ALTER TABLE projects ADD COLUMN hosted_project_id INTEGER REFERENCES hosted_projects(id) UNIQUE NULL;

View File

@@ -11,7 +11,7 @@ use crate::{
use anyhow::anyhow;
use axum::{
body::Body,
extract::{Path, Query},
extract::{self, Path, Query},
http::{self, Request, StatusCode},
middleware::{self, Next},
response::IntoResponse,
@@ -26,7 +26,7 @@ use tower::ServiceBuilder;
pub use extensions::fetch_extensions_from_blob_store_periodically;
pub fn routes(rpc_server: Option<Arc<rpc::Server>>, state: Arc<AppState>) -> Router<Body> {
pub fn routes(rpc_server: Option<Arc<rpc::Server>>, state: Arc<AppState>) -> Router<(), Body> {
Router::new()
.route("/user", get(get_authenticated_user))
.route("/users/:id/access_tokens", post(create_access_token))
@@ -176,17 +176,16 @@ async fn check_is_contributor(
}
async fn add_contributor(
Json(params): Json<AuthenticatedUserParams>,
Extension(app): Extension<Arc<AppState>>,
extract::Json(params): extract::Json<AuthenticatedUserParams>,
) -> Result<()> {
Ok(app
.db
app.db
.add_contributor(
&params.github_login,
params.github_user_id,
params.github_email.as_deref(),
)
.await?)
.await
}
#[derive(Deserialize)]

View File

@@ -3,9 +3,12 @@ use std::sync::{Arc, OnceLock};
use anyhow::{anyhow, Context};
use aws_sdk_s3::primitives::ByteStream;
use axum::{
body::Bytes, headers::Header, http::HeaderName, routing::post, Extension, Router, TypedHeader,
body::Bytes,
headers::Header,
http::{HeaderMap, HeaderName, StatusCode},
routing::post,
Extension, Router, TypedHeader,
};
use hyper::{HeaderMap, StatusCode};
use serde::{Serialize, Serializer};
use sha2::{Digest, Sha256};
use telemetry_events::{
@@ -81,8 +84,8 @@ impl Header for CloudflareIpCountryHeader {
pub async fn post_crash(
Extension(app): Extension<Arc<AppState>>,
body: Bytes,
headers: HeaderMap,
body: Bytes,
) -> Result<()> {
static CRASH_REPORTS_BUCKET: &str = "zed-crash-reports";
@@ -355,40 +358,68 @@ struct ToUpload {
impl ToUpload {
pub async fn upload(&self, clickhouse_client: &clickhouse::Client) -> anyhow::Result<()> {
Self::upload_to_table("editor_events", &self.editor_events, clickhouse_client)
const EDITOR_EVENTS_TABLE: &str = "editor_events";
Self::upload_to_table(EDITOR_EVENTS_TABLE, &self.editor_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'editor_events'"))?;
Self::upload_to_table("copilot_events", &self.copilot_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'copilot_events'"))?;
.with_context(|| format!("failed to upload to table '{EDITOR_EVENTS_TABLE}'"))?;
const COPILOT_EVENTS_TABLE: &str = "copilot_events";
Self::upload_to_table(
"assistant_events",
COPILOT_EVENTS_TABLE,
&self.copilot_events,
clickhouse_client,
)
.await
.with_context(|| format!("failed to upload to table '{COPILOT_EVENTS_TABLE}'"))?;
const ASSISTANT_EVENTS_TABLE: &str = "assistant_events";
Self::upload_to_table(
ASSISTANT_EVENTS_TABLE,
&self.assistant_events,
clickhouse_client,
)
.await
.with_context(|| format!("failed to upload to table 'assistant_events'"))?;
Self::upload_to_table("call_events", &self.call_events, clickhouse_client)
.with_context(|| format!("failed to upload to table '{ASSISTANT_EVENTS_TABLE}'"))?;
const CALL_EVENTS_TABLE: &str = "call_events";
Self::upload_to_table(CALL_EVENTS_TABLE, &self.call_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'call_events'"))?;
Self::upload_to_table("cpu_events", &self.cpu_events, clickhouse_client)
.with_context(|| format!("failed to upload to table '{CALL_EVENTS_TABLE}'"))?;
const CPU_EVENTS_TABLE: &str = "cpu_events";
Self::upload_to_table(CPU_EVENTS_TABLE, &self.cpu_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'cpu_events'"))?;
Self::upload_to_table("memory_events", &self.memory_events, clickhouse_client)
.with_context(|| format!("failed to upload to table '{CPU_EVENTS_TABLE}'"))?;
const MEMORY_EVENTS_TABLE: &str = "memory_events";
Self::upload_to_table(MEMORY_EVENTS_TABLE, &self.memory_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'memory_events'"))?;
Self::upload_to_table("app_events", &self.app_events, clickhouse_client)
.with_context(|| format!("failed to upload to table '{MEMORY_EVENTS_TABLE}'"))?;
const APP_EVENTS_TABLE: &str = "app_events";
Self::upload_to_table(APP_EVENTS_TABLE, &self.app_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'app_events'"))?;
Self::upload_to_table("setting_events", &self.setting_events, clickhouse_client)
.with_context(|| format!("failed to upload to table '{APP_EVENTS_TABLE}'"))?;
const SETTING_EVENTS_TABLE: &str = "setting_events";
Self::upload_to_table(
SETTING_EVENTS_TABLE,
&self.setting_events,
clickhouse_client,
)
.await
.with_context(|| format!("failed to upload to table '{SETTING_EVENTS_TABLE}'"))?;
const EDIT_EVENTS_TABLE: &str = "edit_events";
Self::upload_to_table(EDIT_EVENTS_TABLE, &self.edit_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'setting_events'"))?;
Self::upload_to_table("edit_events", &self.edit_events, clickhouse_client)
.with_context(|| format!("failed to upload to table '{EDIT_EVENTS_TABLE}'"))?;
const ACTION_EVENTS_TABLE: &str = "action_events";
Self::upload_to_table(ACTION_EVENTS_TABLE, &self.action_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'edit_events'"))?;
Self::upload_to_table("action_events", &self.action_events, clickhouse_client)
.await
.with_context(|| format!("failed to upload to table 'action_events'"))?;
.with_context(|| format!("failed to upload to table '{ACTION_EVENTS_TABLE}'"))?;
Ok(())
}

View File

@@ -7,12 +7,12 @@ use anyhow::{anyhow, Context as _};
use aws_sdk_s3::presigning::PresigningConfig;
use axum::{
extract::{Path, Query},
http::StatusCode,
response::Redirect,
routing::get,
Extension, Json, Router,
};
use collections::HashMap;
use hyper::StatusCode;
use serde::{Deserialize, Serialize};
use std::{sync::Arc, time::Duration};
use time::PrimitiveDateTime;

View File

@@ -45,7 +45,7 @@ impl IpsFile {
pub fn description(&self, panic: Option<&str>) -> String {
let mut desc = if self.body.termination.indicator == "Abort trap: 6" {
match panic {
Some(panic_message) => format!("Panic `{}`", panic_message).into(),
Some(panic_message) => format!("Panic `{}`", panic_message),
None => "Crash `Abort trap: 6` (possible panic)".into(),
}
} else if let Some(msg) = &self.body.exception.message {

View File

@@ -88,9 +88,9 @@ async fn fetch_github<T: DeserializeOwned>(client: &reqwest::Client, url: &str)
.header("user-agent", "zed")
.send()
.await
.expect(&format!("failed to fetch '{}'", url));
.unwrap_or_else(|_| panic!("failed to fetch '{}'", url));
response
.json()
.await
.expect(&format!("failed to deserialize github user from '{}'", url))
.unwrap_or_else(|_| panic!("failed to deserialize github user from '{}'", url))
}

View File

@@ -359,7 +359,7 @@ impl Database {
const SLEEPS: [f32; 10] = [10., 20., 40., 80., 160., 320., 640., 1280., 2560., 5120.];
if is_serialization_error(error) && prev_attempt_count < SLEEPS.len() {
let base_delay = SLEEPS[prev_attempt_count];
let randomized_delay = base_delay as f32 * self.rng.lock().await.gen_range(0.5..=2.0);
let randomized_delay = base_delay * self.rng.lock().await.gen_range(0.5..=2.0);
log::info!(
"retrying transaction after serialization error. delay: {} ms.",
randomized_delay
@@ -670,6 +670,8 @@ pub struct RefreshedChannelBuffer {
}
pub struct Project {
pub id: ProjectId,
pub role: ChannelRole,
pub collaborators: Vec<ProjectCollaborator>,
pub worktrees: BTreeMap<u64, Worktree>,
pub language_servers: Vec<proto::LanguageServer>,
@@ -695,7 +697,7 @@ impl ProjectCollaborator {
#[derive(Debug)]
pub struct LeftProject {
pub id: ProjectId,
pub host_user_id: UserId,
pub host_user_id: Option<UserId>,
pub host_connection_id: Option<ConnectionId>,
pub connection_ids: Vec<ConnectionId>,
}

View File

@@ -18,7 +18,7 @@ impl Database {
connection: ConnectionId,
) -> Result<proto::JoinChannelBufferResponse> {
self.transaction(|tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &tx)
.await?;
@@ -134,10 +134,10 @@ impl Database {
let mut results = Vec::new();
for client_buffer in buffers {
let channel = self
.get_channel_internal(ChannelId::from_proto(client_buffer.channel_id), &*tx)
.get_channel_internal(ChannelId::from_proto(client_buffer.channel_id), &tx)
.await?;
if self
.check_user_is_channel_participant(&channel, user_id, &*tx)
.check_user_is_channel_participant(&channel, user_id, &tx)
.await
.is_err()
{
@@ -145,7 +145,7 @@ impl Database {
continue;
}
let buffer = self.get_channel_buffer(channel.id, &*tx).await?;
let buffer = self.get_channel_buffer(channel.id, &tx).await?;
let mut collaborators = channel_buffer_collaborator::Entity::find()
.filter(channel_buffer_collaborator::Column::ChannelId.eq(channel.id))
.all(&*tx)
@@ -180,7 +180,7 @@ impl Database {
let client_version = version_from_wire(&client_buffer.version);
let serialization_version = self
.get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &*tx)
.get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &tx)
.await?;
let mut rows = buffer_operation::Entity::find()
@@ -283,7 +283,7 @@ impl Database {
connection: ConnectionId,
) -> Result<LeftChannelBuffer> {
self.transaction(|tx| async move {
self.leave_channel_buffer_internal(channel_id, connection, &*tx)
self.leave_channel_buffer_internal(channel_id, connection, &tx)
.await
})
.await
@@ -308,7 +308,7 @@ impl Database {
connection_lost: ActiveValue::set(true),
..Default::default()
})
.exec(&*tx)
.exec(tx)
.await?;
Ok(())
}
@@ -337,7 +337,7 @@ impl Database {
let mut result = Vec::new();
for channel_id in channel_ids {
let left_channel_buffer = self
.leave_channel_buffer_internal(channel_id, connection, &*tx)
.leave_channel_buffer_internal(channel_id, connection, &tx)
.await?;
result.push(left_channel_buffer);
}
@@ -363,7 +363,7 @@ impl Database {
.eq(connection.owner_id as i32),
),
)
.exec(&*tx)
.exec(tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("not a collaborator on this project"))?;
@@ -375,7 +375,7 @@ impl Database {
.filter(
Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.stream(&*tx)
.stream(tx)
.await?;
while let Some(row) = rows.next().await {
let row = row?;
@@ -406,7 +406,7 @@ impl Database {
channel_id: ChannelId,
) -> Result<Vec<UserId>> {
self.transaction(|tx| async move {
self.get_channel_buffer_collaborators_internal(channel_id, &*tx)
self.get_channel_buffer_collaborators_internal(channel_id, &tx)
.await
})
.await
@@ -429,7 +429,7 @@ impl Database {
Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.into_values::<_, QueryUserIds>()
.all(&*tx)
.all(tx)
.await?;
Ok(users)
@@ -447,7 +447,7 @@ impl Database {
Vec<proto::VectorClockEntry>,
)> {
self.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
let channel = self.get_channel_internal(channel_id, &tx).await?;
let mut requires_write_permission = false;
for op in operations.iter() {
@@ -457,10 +457,10 @@ impl Database {
}
}
if requires_write_permission {
self.check_user_is_channel_member(&channel, user, &*tx)
self.check_user_is_channel_member(&channel, user, &tx)
.await?;
} else {
self.check_user_is_channel_participant(&channel, user, &*tx)
self.check_user_is_channel_participant(&channel, user, &tx)
.await?;
}
@@ -471,7 +471,7 @@ impl Database {
.ok_or_else(|| anyhow!("no such buffer"))?;
let serialization_version = self
.get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &*tx)
.get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &tx)
.await?;
let operations = operations
@@ -500,13 +500,13 @@ impl Database {
buffer.epoch,
*max_operation.replica_id.as_ref(),
*max_operation.lamport_timestamp.as_ref(),
&*tx,
&tx,
)
.await?;
channel_members = self.get_channel_participants(&channel, &*tx).await?;
channel_members = self.get_channel_participants(&channel, &tx).await?;
let collaborators = self
.get_channel_buffer_collaborators_internal(channel_id, &*tx)
.get_channel_buffer_collaborators_internal(channel_id, &tx)
.await?;
channel_members.retain(|member| !collaborators.contains(member));
@@ -602,7 +602,7 @@ impl Database {
.select_only()
.column(buffer_snapshot::Column::OperationSerializationVersion)
.into_values::<_, QueryOperationSerializationVersion>()
.one(&*tx)
.one(tx)
.await?
.ok_or_else(|| anyhow!("missing buffer snapshot"))?)
}
@@ -617,7 +617,7 @@ impl Database {
..Default::default()
}
.find_related(buffer::Entity)
.one(&*tx)
.one(tx)
.await?
.ok_or_else(|| anyhow!("no such buffer"))?)
}
@@ -639,7 +639,7 @@ impl Database {
.eq(id)
.and(buffer_snapshot::Column::Epoch.eq(buffer.epoch)),
)
.one(&*tx)
.one(tx)
.await?
.ok_or_else(|| anyhow!("no such snapshot"))?;
@@ -657,7 +657,7 @@ impl Database {
)
.order_by_asc(buffer_operation::Column::LamportTimestamp)
.order_by_asc(buffer_operation::Column::ReplicaId)
.stream(&*tx)
.stream(tx)
.await?;
let mut operations = Vec::new();
@@ -737,7 +737,7 @@ impl Database {
epoch,
component.replica_id as i32,
component.timestamp as i32,
&*tx,
&tx,
)
.await?;
Ok(())
@@ -751,7 +751,7 @@ impl Database {
tx: &DatabaseTransaction,
) -> Result<Vec<proto::ChannelBufferVersion>> {
let latest_operations = self
.get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), &*tx)
.get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), tx)
.await?;
Ok(latest_operations
@@ -781,7 +781,7 @@ impl Database {
observed_buffer_edits::Column::BufferId
.is_in(channel_ids_by_buffer_id.keys().copied()),
)
.all(&*tx)
.all(tx)
.await?;
Ok(observed_operations
@@ -844,7 +844,7 @@ impl Database {
let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
Ok(buffer_operation::Entity::find()
.from_raw_sql(stmt)
.all(&*tx)
.all(tx)
.await?)
}
}

View File

@@ -53,8 +53,8 @@ impl Database {
let mut membership = None;
if let Some(parent_channel_id) = parent_channel_id {
let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?;
self.check_user_is_channel_admin(&parent_channel, admin_id, &*tx)
let parent_channel = self.get_channel_internal(parent_channel_id, &tx).await?;
self.check_user_is_channel_admin(&parent_channel, admin_id, &tx)
.await?;
parent = Some(parent_channel);
}
@@ -105,14 +105,14 @@ impl Database {
connection: ConnectionId,
) -> Result<(JoinRoom, Option<MembershipUpdated>, ChannelRole)> {
self.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
let mut role = self.channel_role_for_user(&channel, user_id, &*tx).await?;
let channel = self.get_channel_internal(channel_id, &tx).await?;
let mut role = self.channel_role_for_user(&channel, user_id, &tx).await?;
let mut accept_invite_result = None;
if role.is_none() {
if let Some(invitation) = self
.pending_invite_for_channel(&channel, user_id, &*tx)
.pending_invite_for_channel(&channel, user_id, &tx)
.await?
{
// note, this may be a parent channel
@@ -125,12 +125,12 @@ impl Database {
.await?;
accept_invite_result = Some(
self.calculate_membership_updated(&channel, user_id, &*tx)
self.calculate_membership_updated(&channel, user_id, &tx)
.await?,
);
debug_assert!(
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
self.channel_role_for_user(&channel, user_id, &tx).await? == role
);
} else if channel.visibility == ChannelVisibility::Public {
role = Some(ChannelRole::Guest);
@@ -145,12 +145,12 @@ impl Database {
.await?;
accept_invite_result = Some(
self.calculate_membership_updated(&channel, user_id, &*tx)
self.calculate_membership_updated(&channel, user_id, &tx)
.await?,
);
debug_assert!(
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
self.channel_role_for_user(&channel, user_id, &tx).await? == role
);
}
}
@@ -162,10 +162,10 @@ impl Database {
let live_kit_room = format!("channel-{}", nanoid::nanoid!(30));
let room_id = self
.get_or_create_channel_room(channel_id, &live_kit_room, &*tx)
.get_or_create_channel_room(channel_id, &live_kit_room, &tx)
.await?;
self.join_channel_room_internal(room_id, user_id, connection, role, &*tx)
self.join_channel_room_internal(room_id, user_id, connection, role, &tx)
.await
.map(|jr| (jr, accept_invite_result, role))
})
@@ -180,13 +180,13 @@ impl Database {
admin_id: UserId,
) -> Result<(Channel, Vec<channel_member::Model>)> {
self.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_admin(&channel, admin_id, &tx)
.await?;
if visibility == ChannelVisibility::Public {
if let Some(parent_id) = channel.parent_id() {
let parent = self.get_channel_internal(parent_id, &*tx).await?;
let parent = self.get_channel_internal(parent_id, &tx).await?;
if parent.visibility != ChannelVisibility::Public {
Err(ErrorCode::BadPublicNesting
@@ -196,7 +196,7 @@ impl Database {
}
} else if visibility == ChannelVisibility::Members {
if self
.get_channel_descendants_excluding_self([&channel], &*tx)
.get_channel_descendants_excluding_self([&channel], &tx)
.await?
.into_iter()
.any(|channel| channel.visibility == ChannelVisibility::Public)
@@ -228,7 +228,7 @@ impl Database {
requires_zed_cla: bool,
) -> Result<()> {
self.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
let channel = self.get_channel_internal(channel_id, &tx).await?;
let mut model = channel.into_active_model();
model.requires_zed_cla = ActiveValue::Set(requires_zed_cla);
model.update(&*tx).await?;
@@ -244,8 +244,8 @@ impl Database {
user_id: UserId,
) -> Result<(Vec<ChannelId>, Vec<UserId>)> {
self.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_admin(&channel, user_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_admin(&channel, user_id, &tx)
.await?;
let members_to_notify: Vec<UserId> = channel_member::Entity::find()
@@ -258,7 +258,7 @@ impl Database {
.await?;
let channels_to_remove = self
.get_channel_descendants_excluding_self([&channel], &*tx)
.get_channel_descendants_excluding_self([&channel], &tx)
.await?
.into_iter()
.map(|channel| channel.id)
@@ -284,8 +284,8 @@ impl Database {
role: ChannelRole,
) -> Result<InviteMemberResult> {
self.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_admin(&channel, inviter_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_admin(&channel, inviter_id, &tx)
.await?;
if !channel.is_root() {
Err(ErrorCode::NotARootChannel.anyhow())?
@@ -312,7 +312,7 @@ impl Database {
inviter_id: inviter_id.to_proto(),
},
true,
&*tx,
&tx,
)
.await?
.into_iter()
@@ -344,8 +344,8 @@ impl Database {
self.transaction(move |tx| async move {
let new_name = Self::sanitize_channel_name(new_name)?.to_string();
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_admin(&channel, admin_id, &tx)
.await?;
let mut model = channel.into_active_model();
@@ -370,7 +370,7 @@ impl Database {
accept: bool,
) -> Result<RespondToChannelInvite> {
self.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
let channel = self.get_channel_internal(channel_id, &tx).await?;
let membership_update = if accept {
let rows_affected = channel_member::Entity::update_many()
@@ -393,7 +393,7 @@ impl Database {
}
Some(
self.calculate_membership_updated(&channel, user_id, &*tx)
self.calculate_membership_updated(&channel, user_id, &tx)
.await?,
)
} else {
@@ -425,7 +425,7 @@ impl Database {
inviter_id: Default::default(),
},
accept,
&*tx,
&tx,
)
.await?
.into_iter()
@@ -441,9 +441,9 @@ impl Database {
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<MembershipUpdated> {
let new_channels = self.get_user_channels(user_id, Some(channel), &*tx).await?;
let new_channels = self.get_user_channels(user_id, Some(channel), tx).await?;
let removed_channels = self
.get_channel_descendants_excluding_self([channel], &*tx)
.get_channel_descendants_excluding_self([channel], tx)
.await?
.into_iter()
.map(|channel| channel.id)
@@ -466,10 +466,10 @@ impl Database {
admin_id: UserId,
) -> Result<RemoveChannelMemberResult> {
self.transaction(|tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
let channel = self.get_channel_internal(channel_id, &tx).await?;
if member_id != admin_id {
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
self.check_user_is_channel_admin(&channel, admin_id, &tx)
.await?;
}
@@ -488,7 +488,7 @@ impl Database {
Ok(RemoveChannelMemberResult {
membership_update: self
.calculate_membership_updated(&channel, member_id, &*tx)
.calculate_membership_updated(&channel, member_id, &tx)
.await?,
notification_id: self
.remove_notification(
@@ -498,7 +498,7 @@ impl Database {
channel_name: Default::default(),
inviter_id: Default::default(),
},
&*tx,
&tx,
)
.await?,
})
@@ -529,10 +529,7 @@ impl Database {
.all(&*tx)
.await?;
let channels = channels
.into_iter()
.filter_map(|channel| Some(Channel::from_model(channel)))
.collect();
let channels = channels.into_iter().map(Channel::from_model).collect();
Ok(channels)
})
@@ -567,16 +564,16 @@ impl Database {
let channel_memberships = channel_member::Entity::find()
.filter(filter)
.all(&*tx)
.all(tx)
.await?;
let channels = channel::Entity::find()
.filter(channel::Column::Id.is_in(channel_memberships.iter().map(|m| m.channel_id)))
.all(&*tx)
.all(tx)
.await?;
let mut descendants = self
.get_channel_descendants_excluding_self(channels.iter(), &*tx)
.get_channel_descendants_excluding_self(channels.iter(), tx)
.await?;
for channel in channels {
@@ -617,7 +614,7 @@ impl Database {
.column(room::Column::ChannelId)
.column(room_participant::Column::UserId)
.into_values::<_, QueryUserIdsAndChannelIds>()
.stream(&*tx)
.stream(tx)
.await?;
while let Some(row) = rows.next().await {
let row: (ChannelId, UserId) = row?;
@@ -630,7 +627,7 @@ impl Database {
let mut channel_ids_by_buffer_id = HashMap::default();
let mut rows = buffer::Entity::find()
.filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
.stream(&*tx)
.stream(tx)
.await?;
while let Some(row) = rows.next().await {
let row = row?;
@@ -639,21 +636,21 @@ impl Database {
drop(rows);
let latest_buffer_versions = self
.latest_channel_buffer_changes(&channel_ids_by_buffer_id, &*tx)
.latest_channel_buffer_changes(&channel_ids_by_buffer_id, tx)
.await?;
let latest_channel_messages = self.latest_channel_messages(&channel_ids, &*tx).await?;
let latest_channel_messages = self.latest_channel_messages(&channel_ids, tx).await?;
let observed_buffer_versions = self
.observed_channel_buffer_changes(&channel_ids_by_buffer_id, user_id, &*tx)
.observed_channel_buffer_changes(&channel_ids_by_buffer_id, user_id, tx)
.await?;
let observed_channel_messages = self
.observed_channel_messages(&channel_ids, user_id, &*tx)
.observed_channel_messages(&channel_ids, user_id, tx)
.await?;
let hosted_projects = self
.get_hosted_projects(&channel_ids, &roles_by_channel_id, &*tx)
.get_hosted_projects(&channel_ids, &roles_by_channel_id, tx)
.await?;
Ok(ChannelsForUser {
@@ -677,8 +674,8 @@ impl Database {
role: ChannelRole,
) -> Result<SetMemberRoleResult> {
self.transaction(|tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_admin(&channel, admin_id, &tx)
.await?;
let membership = channel_member::Entity::find()
@@ -700,7 +697,7 @@ impl Database {
if updated.accepted {
Ok(SetMemberRoleResult::MembershipUpdated(
self.calculate_membership_updated(&channel, for_user, &*tx)
self.calculate_membership_updated(&channel, for_user, &tx)
.await?,
))
} else {
@@ -720,13 +717,13 @@ impl Database {
) -> Result<Vec<proto::ChannelMember>> {
let (role, members) = self
.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
let channel = self.get_channel_internal(channel_id, &tx).await?;
let role = self
.check_user_is_channel_participant(&channel, user_id, &*tx)
.check_user_is_channel_participant(&channel, user_id, &tx)
.await?;
Ok((
role,
self.get_channel_participant_details_internal(&channel, &*tx)
self.get_channel_participant_details_internal(&channel, &tx)
.await?,
))
})
@@ -781,7 +778,7 @@ impl Database {
tx: &DatabaseTransaction,
) -> Result<Vec<UserId>> {
let participants = self
.get_channel_participant_details_internal(channel, &*tx)
.get_channel_participant_details_internal(channel, tx)
.await?;
Ok(participants
.into_iter()
@@ -858,7 +855,7 @@ impl Database {
.filter(channel_member::Column::ChannelId.eq(channel.root_id()))
.filter(channel_member::Column::UserId.eq(user_id))
.filter(channel_member::Column::Accepted.eq(false))
.one(&*tx)
.one(tx)
.await?;
Ok(row)
@@ -878,7 +875,7 @@ impl Database {
.and(channel_member::Column::UserId.eq(user_id))
.and(channel_member::Column::Accepted.eq(true)),
)
.one(&*tx)
.one(tx)
.await?;
let Some(membership) = membership else {
@@ -918,8 +915,8 @@ impl Database {
/// Returns the channel with the given ID.
pub async fn get_channel(&self, channel_id: ChannelId, user_id: UserId) -> Result<Channel> {
self.transaction(|tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &tx)
.await?;
Ok(Channel::from_model(channel))
@@ -933,7 +930,7 @@ impl Database {
tx: &DatabaseTransaction,
) -> Result<channel::Model> {
Ok(channel::Entity::find_by_id(channel_id)
.one(&*tx)
.one(tx)
.await?
.ok_or_else(|| proto::ErrorCode::NoSuchChannel.anyhow())?)
}
@@ -946,7 +943,7 @@ impl Database {
) -> Result<RoomId> {
let room = room::Entity::find()
.filter(room::Column::ChannelId.eq(channel_id))
.one(&*tx)
.one(tx)
.await?;
let room_id = if let Some(room) = room {
@@ -957,7 +954,7 @@ impl Database {
live_kit_room: ActiveValue::Set(live_kit_room.to_string()),
..Default::default()
})
.exec(&*tx)
.exec(tx)
.await?;
result.last_insert_id
@@ -974,10 +971,10 @@ impl Database {
admin_id: UserId,
) -> Result<(Vec<Channel>, Vec<channel_member::Model>)> {
self.transaction(|tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_admin(&channel, admin_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_admin(&channel, admin_id, &tx)
.await?;
let new_parent = self.get_channel_internal(new_parent_id, &*tx).await?;
let new_parent = self.get_channel_internal(new_parent_id, &tx).await?;
if new_parent.root_id() != channel.root_id() {
Err(anyhow!(ErrorCode::WrongMoveTarget))?;

View File

@@ -177,7 +177,7 @@ impl Database {
sender_id: sender_id.to_proto(),
},
true,
&*tx,
&tx,
)
.await?
.into_iter()
@@ -227,7 +227,7 @@ impl Database {
rpc::Notification::ContactRequest {
sender_id: requester_id.to_proto(),
},
&*tx,
&tx,
)
.await?;
}
@@ -335,7 +335,7 @@ impl Database {
sender_id: requester_id.to_proto(),
},
accept,
&*tx,
&tx,
)
.await?,
);
@@ -348,7 +348,7 @@ impl Database {
responder_id: responder_id.to_proto(),
},
true,
&*tx,
&tx,
)
.await?,
);

View File

@@ -72,7 +72,7 @@ impl Database {
github_login,
github_user_id,
github_email,
&*tx,
&tx,
)
.await?;

View File

@@ -1,4 +1,4 @@
use rpc::proto;
use rpc::{proto, ErrorCode};
use super::*;
@@ -11,7 +11,7 @@ impl Database {
) -> Result<Vec<proto::HostedProject>> {
Ok(hosted_project::Entity::find()
.filter(hosted_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0)))
.all(&*tx)
.all(tx)
.await?
.into_iter()
.flat_map(|project| {
@@ -39,4 +39,44 @@ impl Database {
})
.collect())
}
pub async fn get_hosted_project(
&self,
hosted_project_id: HostedProjectId,
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<(hosted_project::Model, ChannelRole)> {
let project = hosted_project::Entity::find_by_id(hosted_project_id)
.one(tx)
.await?
.ok_or_else(|| anyhow!(ErrorCode::NoSuchProject))?;
let channel = channel::Entity::find_by_id(project.channel_id)
.one(tx)
.await?
.ok_or_else(|| anyhow!(ErrorCode::NoSuchChannel))?;
let role = match project.visibility {
ChannelVisibility::Public => {
self.check_user_is_channel_participant(&channel, user_id, tx)
.await?
}
ChannelVisibility::Members => {
self.check_user_is_channel_member(&channel, user_id, tx)
.await?
}
};
Ok((project, role))
}
pub async fn is_hosted_project(&self, project_id: ProjectId) -> Result<bool> {
self.transaction(|tx| async move {
Ok(project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.map(|project| project.hosted_project_id.is_some())
.ok_or_else(|| anyhow!(ErrorCode::NoSuchProject))?)
})
.await
}
}

View File

@@ -12,8 +12,8 @@ impl Database {
user_id: UserId,
) -> Result<()> {
self.transaction(|tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &tx)
.await?;
channel_chat_participant::ActiveModel {
id: ActiveValue::NotSet,
@@ -87,8 +87,8 @@ impl Database {
before_message_id: Option<MessageId>,
) -> Result<Vec<proto::ChannelMessage>> {
self.transaction(|tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &tx)
.await?;
let mut condition =
@@ -105,7 +105,7 @@ impl Database {
.all(&*tx)
.await?;
self.load_channel_messages(rows, &*tx).await
self.load_channel_messages(rows, &tx).await
})
.await
}
@@ -127,16 +127,16 @@ impl Database {
for row in &rows {
channels.insert(
row.channel_id,
self.get_channel_internal(row.channel_id, &*tx).await?,
self.get_channel_internal(row.channel_id, &tx).await?,
);
}
for (_, channel) in channels {
self.check_user_is_channel_participant(&channel, user_id, &*tx)
self.check_user_is_channel_participant(&channel, user_id, &tx)
.await?;
}
let messages = self.load_channel_messages(rows, &*tx).await?;
let messages = self.load_channel_messages(rows, &tx).await?;
Ok(messages)
})
.await
@@ -174,7 +174,7 @@ impl Database {
.filter(channel_message_mention::Column::MessageId.is_in(messages.iter().map(|m| m.id)))
.order_by_asc(channel_message_mention::Column::MessageId)
.order_by_asc(channel_message_mention::Column::StartOffset)
.stream(&*tx)
.stream(tx)
.await?;
let mut message_ix = 0;
@@ -228,6 +228,7 @@ impl Database {
}
/// Creates a new channel message.
#[allow(clippy::too_many_arguments)]
pub async fn create_channel_message(
&self,
channel_id: ChannelId,
@@ -239,8 +240,8 @@ impl Database {
reply_to_message_id: Option<MessageId>,
) -> Result<CreatedChannelMessage> {
self.transaction(|tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &*tx)
let channel = self.get_channel_internal(channel_id, &tx).await?;
self.check_user_is_channel_participant(&channel, user_id, &tx)
.await?;
let mut rows = channel_chat_participant::Entity::find()
@@ -315,13 +316,13 @@ impl Database {
channel_id: channel_id.to_proto(),
},
false,
&*tx,
&tx,
)
.await?,
);
}
self.observe_channel_message_internal(channel_id, user_id, message_id, &*tx)
self.observe_channel_message_internal(channel_id, user_id, message_id, &tx)
.await?;
}
_ => {
@@ -334,7 +335,7 @@ impl Database {
}
}
let mut channel_members = self.get_channel_participants(&channel, &*tx).await?;
let mut channel_members = self.get_channel_participants(&channel, &tx).await?;
channel_members.retain(|member| !participant_user_ids.contains(member));
Ok(CreatedChannelMessage {
@@ -354,7 +355,7 @@ impl Database {
message_id: MessageId,
) -> Result<NotificationBatch> {
self.transaction(|tx| async move {
self.observe_channel_message_internal(channel_id, user_id, message_id, &*tx)
self.observe_channel_message_internal(channel_id, user_id, message_id, &tx)
.await?;
let mut batch = NotificationBatch::default();
batch.extend(
@@ -365,7 +366,7 @@ impl Database {
sender_id: Default::default(),
channel_id: Default::default(),
},
&*tx,
&tx,
)
.await?,
);
@@ -396,7 +397,7 @@ impl Database {
.to_owned(),
)
// TODO: Try to upgrade SeaORM so we don't have to do this hack around their bug
.exec_without_returning(&*tx)
.exec_without_returning(tx)
.await?;
Ok(())
}
@@ -413,7 +414,7 @@ impl Database {
observed_channel_messages::Column::ChannelId
.is_in(channel_ids.iter().map(|id| id.0)),
)
.all(&*tx)
.all(tx)
.await?;
Ok(rows
@@ -464,7 +465,7 @@ impl Database {
let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
let mut last_messages = channel_message::Model::find_by_statement(stmt)
.stream(&*tx)
.stream(tx)
.await?;
let mut results = Vec::new();
@@ -513,9 +514,9 @@ impl Database {
.await?;
if result.rows_affected == 0 {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
let channel = self.get_channel_internal(channel_id, &tx).await?;
if self
.check_user_is_channel_admin(&channel, user_id, &*tx)
.check_user_is_channel_admin(&channel, user_id, &tx)
.await
.is_ok()
{

View File

@@ -95,7 +95,7 @@ impl Database {
content: ActiveValue::Set(proto.content.clone()),
..Default::default()
}
.save(&*tx)
.save(tx)
.await?;
Ok(Some((
@@ -184,7 +184,7 @@ impl Database {
tx: &DatabaseTransaction,
) -> Result<Option<(UserId, proto::Notification)>> {
if let Some(id) = self
.find_notification(recipient_id, notification, &*tx)
.find_notification(recipient_id, notification, tx)
.await?
{
let row = notification::Entity::update(notification::ActiveModel {
@@ -236,7 +236,7 @@ impl Database {
}),
)
.into_values::<_, QueryIds>()
.one(&*tx)
.one(tx)
.await?)
}
}

View File

@@ -57,13 +57,14 @@ impl Database {
}
let project = project::ActiveModel {
room_id: ActiveValue::set(participant.room_id),
host_user_id: ActiveValue::set(participant.user_id),
room_id: ActiveValue::set(Some(participant.room_id)),
host_user_id: ActiveValue::set(Some(participant.user_id)),
host_connection_id: ActiveValue::set(Some(connection.id as i32)),
host_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
..Default::default()
id: ActiveValue::NotSet,
hosted_project_id: ActiveValue::Set(None),
}
.insert(&*tx)
.await?;
@@ -153,8 +154,12 @@ impl Database {
self.update_project_worktrees(project.id, worktrees, &tx)
.await?;
let room_id = project
.room_id
.ok_or_else(|| anyhow!("project not in a room"))?;
let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?;
let room = self.get_room(project.room_id, &tx).await?;
let room = self.get_room(room_id, &tx).await?;
Ok((room, guest_connection_ids))
})
.await
@@ -181,7 +186,7 @@ impl Database {
.update_column(worktree::Column::RootName)
.to_owned(),
)
.exec(&*tx)
.exec(tx)
.await?;
}
@@ -189,7 +194,7 @@ impl Database {
.filter(worktree::Column::ProjectId.eq(project_id).and(
worktree::Column::Id.is_not_in(worktrees.iter().map(|worktree| worktree.id as i64)),
))
.exec(&*tx)
.exec(tx)
.await?;
Ok(())
@@ -382,7 +387,6 @@ impl Database {
language_server_id: ActiveValue::set(summary.language_server_id as i64),
error_count: ActiveValue::set(summary.error_count as i32),
warning_count: ActiveValue::set(summary.warning_count as i32),
..Default::default()
})
.on_conflict(
OnConflict::columns([
@@ -434,7 +438,6 @@ impl Database {
project_id: ActiveValue::set(project_id),
id: ActiveValue::set(server.id as i64),
name: ActiveValue::set(server.name.clone()),
..Default::default()
})
.on_conflict(
OnConflict::columns([
@@ -506,8 +509,30 @@ impl Database {
.await
}
/// Adds the given connection to the specified project.
pub async fn join_project(
/// Adds the given connection to the specified hosted project
pub async fn join_hosted_project(
&self,
id: HostedProjectId,
user_id: UserId,
connection: ConnectionId,
) -> Result<(Project, ReplicaId)> {
self.transaction(|tx| async move {
let (hosted_project, role) = self.get_hosted_project(id, user_id, &tx).await?;
let project = project::Entity::find()
.filter(project::Column::HostedProjectId.eq(hosted_project.id))
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("hosted project is no longer shared"))?;
self.join_project_internal(project, user_id, connection, role, &tx)
.await
})
.await
}
/// Adds the given connection to the specified project
/// in the current room.
pub async fn join_project_in_room(
&self,
project_id: ProjectId,
connection: ConnectionId,
@@ -534,180 +559,240 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
if project.room_id != participant.room_id {
if project.room_id != Some(participant.room_id) {
return Err(anyhow!("no such project"))?;
}
self.join_project_internal(
project,
participant.user_id,
connection,
participant.role.unwrap_or(ChannelRole::Member),
&tx,
)
.await
})
.await
}
let mut collaborators = project
async fn join_project_internal(
&self,
project: project::Model,
user_id: UserId,
connection: ConnectionId,
role: ChannelRole,
tx: &DatabaseTransaction,
) -> Result<(Project, ReplicaId)> {
let mut collaborators = project
.find_related(project_collaborator::Entity)
.all(tx)
.await?;
let replica_ids = collaborators
.iter()
.map(|c| c.replica_id)
.collect::<HashSet<_>>();
let mut replica_id = ReplicaId(1);
while replica_ids.contains(&replica_id) {
replica_id.0 += 1;
}
let new_collaborator = project_collaborator::ActiveModel {
project_id: ActiveValue::set(project.id),
connection_id: ActiveValue::set(connection.id as i32),
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
user_id: ActiveValue::set(user_id),
replica_id: ActiveValue::set(replica_id),
is_host: ActiveValue::set(false),
..Default::default()
}
.insert(tx)
.await?;
collaborators.push(new_collaborator);
let db_worktrees = project.find_related(worktree::Entity).all(tx).await?;
let mut worktrees = db_worktrees
.into_iter()
.map(|db_worktree| {
(
db_worktree.id as u64,
Worktree {
id: db_worktree.id as u64,
abs_path: db_worktree.abs_path,
root_name: db_worktree.root_name,
visible: db_worktree.visible,
entries: Default::default(),
repository_entries: Default::default(),
diagnostic_summaries: Default::default(),
settings_files: Default::default(),
scan_id: db_worktree.scan_id as u64,
completed_scan_id: db_worktree.completed_scan_id as u64,
},
)
})
.collect::<BTreeMap<_, _>>();
// Populate worktree entries.
{
let mut db_entries = worktree_entry::Entity::find()
.filter(
Condition::all()
.add(worktree_entry::Column::ProjectId.eq(project.id))
.add(worktree_entry::Column::IsDeleted.eq(false)),
)
.stream(tx)
.await?;
while let Some(db_entry) = db_entries.next().await {
let db_entry = db_entry?;
if let Some(worktree) = worktrees.get_mut(&(db_entry.worktree_id as u64)) {
worktree.entries.push(proto::Entry {
id: db_entry.id as u64,
is_dir: db_entry.is_dir,
path: db_entry.path,
inode: db_entry.inode as u64,
mtime: Some(proto::Timestamp {
seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32,
}),
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
});
}
}
}
// Populate repository entries.
{
let mut db_repository_entries = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project.id))
.add(worktree_repository::Column::IsDeleted.eq(false)),
)
.stream(tx)
.await?;
while let Some(db_repository_entry) = db_repository_entries.next().await {
let db_repository_entry = db_repository_entry?;
if let Some(worktree) = worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
{
worktree.repository_entries.insert(
db_repository_entry.work_directory_id as u64,
proto::RepositoryEntry {
work_directory_id: db_repository_entry.work_directory_id as u64,
branch: db_repository_entry.branch,
},
);
}
}
}
// Populate worktree diagnostic summaries.
{
let mut db_summaries = worktree_diagnostic_summary::Entity::find()
.filter(worktree_diagnostic_summary::Column::ProjectId.eq(project.id))
.stream(tx)
.await?;
while let Some(db_summary) = db_summaries.next().await {
let db_summary = db_summary?;
if let Some(worktree) = worktrees.get_mut(&(db_summary.worktree_id as u64)) {
worktree
.diagnostic_summaries
.push(proto::DiagnosticSummary {
path: db_summary.path,
language_server_id: db_summary.language_server_id as u64,
error_count: db_summary.error_count as u32,
warning_count: db_summary.warning_count as u32,
});
}
}
}
// Populate worktree settings files
{
let mut db_settings_files = worktree_settings_file::Entity::find()
.filter(worktree_settings_file::Column::ProjectId.eq(project.id))
.stream(tx)
.await?;
while let Some(db_settings_file) = db_settings_files.next().await {
let db_settings_file = db_settings_file?;
if let Some(worktree) = worktrees.get_mut(&(db_settings_file.worktree_id as u64)) {
worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path,
content: db_settings_file.content,
});
}
}
}
// Populate language servers.
let language_servers = project
.find_related(language_server::Entity)
.all(tx)
.await?;
let project = Project {
id: project.id,
role,
collaborators: collaborators
.into_iter()
.map(|collaborator| ProjectCollaborator {
connection_id: collaborator.connection(),
user_id: collaborator.user_id,
replica_id: collaborator.replica_id,
is_host: collaborator.is_host,
})
.collect(),
worktrees,
language_servers: language_servers
.into_iter()
.map(|language_server| proto::LanguageServer {
id: language_server.id as u64,
name: language_server.name,
})
.collect(),
};
Ok((project, replica_id as ReplicaId))
}
pub async fn leave_hosted_project(
&self,
project_id: ProjectId,
connection: ConnectionId,
) -> Result<LeftProject> {
self.transaction(|tx| async move {
let result = project_collaborator::Entity::delete_many()
.filter(
Condition::all()
.add(project_collaborator::Column::ProjectId.eq(project_id))
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
project_collaborator::Column::ConnectionServerId
.eq(connection.owner_id as i32),
),
)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
return Err(anyhow!("not in the project"))?;
}
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let collaborators = project
.find_related(project_collaborator::Entity)
.all(&*tx)
.await?;
let replica_ids = collaborators
.iter()
.map(|c| c.replica_id)
.collect::<HashSet<_>>();
let mut replica_id = ReplicaId(1);
while replica_ids.contains(&replica_id) {
replica_id.0 += 1;
}
let new_collaborator = project_collaborator::ActiveModel {
project_id: ActiveValue::set(project_id),
connection_id: ActiveValue::set(connection.id as i32),
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
user_id: ActiveValue::set(participant.user_id),
replica_id: ActiveValue::set(replica_id),
is_host: ActiveValue::set(false),
..Default::default()
}
.insert(&*tx)
.await?;
collaborators.push(new_collaborator);
let db_worktrees = project.find_related(worktree::Entity).all(&*tx).await?;
let mut worktrees = db_worktrees
let connection_ids = collaborators
.into_iter()
.map(|db_worktree| {
(
db_worktree.id as u64,
Worktree {
id: db_worktree.id as u64,
abs_path: db_worktree.abs_path,
root_name: db_worktree.root_name,
visible: db_worktree.visible,
entries: Default::default(),
repository_entries: Default::default(),
diagnostic_summaries: Default::default(),
settings_files: Default::default(),
scan_id: db_worktree.scan_id as u64,
completed_scan_id: db_worktree.completed_scan_id as u64,
},
)
})
.collect::<BTreeMap<_, _>>();
// Populate worktree entries.
{
let mut db_entries = worktree_entry::Entity::find()
.filter(
Condition::all()
.add(worktree_entry::Column::ProjectId.eq(project_id))
.add(worktree_entry::Column::IsDeleted.eq(false)),
)
.stream(&*tx)
.await?;
while let Some(db_entry) = db_entries.next().await {
let db_entry = db_entry?;
if let Some(worktree) = worktrees.get_mut(&(db_entry.worktree_id as u64)) {
worktree.entries.push(proto::Entry {
id: db_entry.id as u64,
is_dir: db_entry.is_dir,
path: db_entry.path,
inode: db_entry.inode as u64,
mtime: Some(proto::Timestamp {
seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32,
}),
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
});
}
}
}
// Populate repository entries.
{
let mut db_repository_entries = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project_id))
.add(worktree_repository::Column::IsDeleted.eq(false)),
)
.stream(&*tx)
.await?;
while let Some(db_repository_entry) = db_repository_entries.next().await {
let db_repository_entry = db_repository_entry?;
if let Some(worktree) =
worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
{
worktree.repository_entries.insert(
db_repository_entry.work_directory_id as u64,
proto::RepositoryEntry {
work_directory_id: db_repository_entry.work_directory_id as u64,
branch: db_repository_entry.branch,
},
);
}
}
}
// Populate worktree diagnostic summaries.
{
let mut db_summaries = worktree_diagnostic_summary::Entity::find()
.filter(worktree_diagnostic_summary::Column::ProjectId.eq(project_id))
.stream(&*tx)
.await?;
while let Some(db_summary) = db_summaries.next().await {
let db_summary = db_summary?;
if let Some(worktree) = worktrees.get_mut(&(db_summary.worktree_id as u64)) {
worktree
.diagnostic_summaries
.push(proto::DiagnosticSummary {
path: db_summary.path,
language_server_id: db_summary.language_server_id as u64,
error_count: db_summary.error_count as u32,
warning_count: db_summary.warning_count as u32,
});
}
}
}
// Populate worktree settings files
{
let mut db_settings_files = worktree_settings_file::Entity::find()
.filter(worktree_settings_file::Column::ProjectId.eq(project_id))
.stream(&*tx)
.await?;
while let Some(db_settings_file) = db_settings_files.next().await {
let db_settings_file = db_settings_file?;
if let Some(worktree) =
worktrees.get_mut(&(db_settings_file.worktree_id as u64))
{
worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path,
content: db_settings_file.content,
});
}
}
}
// Populate language servers.
let language_servers = project
.find_related(language_server::Entity)
.all(&*tx)
.await?;
let project = Project {
collaborators: collaborators
.into_iter()
.map(|collaborator| ProjectCollaborator {
connection_id: collaborator.connection(),
user_id: collaborator.user_id,
replica_id: collaborator.replica_id,
is_host: collaborator.is_host,
})
.collect(),
worktrees,
language_servers: language_servers
.into_iter()
.map(|language_server| proto::LanguageServer {
id: language_server.id as u64,
name: language_server.name,
})
.collect(),
};
Ok((project, replica_id as ReplicaId))
.map(|collaborator| collaborator.connection())
.collect();
Ok(LeftProject {
id: project.id,
connection_ids,
host_user_id: None,
host_connection_id: None,
})
})
.await
}
@@ -774,7 +859,7 @@ impl Database {
.exec(&*tx)
.await?;
let room = self.get_room(project.room_id, &tx).await?;
let room = self.get_room(room_id, &tx).await?;
let left_project = LeftProject {
id: project_id,
host_user_id: project.host_user_id,
@@ -998,7 +1083,9 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("project {} not found", project_id))?;
Ok(project.room_id)
Ok(project
.room_id
.ok_or_else(|| anyhow!("project not in room"))?)
})
.await
}
@@ -1061,7 +1148,7 @@ impl Database {
.insert(&*tx)
.await?;
let room = self.get_room(room_id, &*tx).await?;
let room = self.get_room(room_id, &tx).await?;
Ok(room)
})
.await
@@ -1095,7 +1182,7 @@ impl Database {
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &*tx).await?;
let room = self.get_room(room_id, &tx).await?;
Ok(room)
})
.await

View File

@@ -321,7 +321,7 @@ impl Database {
}
let participant_index = self
.get_next_participant_index_internal(room_id, &*tx)
.get_next_participant_index_internal(room_id, &tx)
.await?;
let result = room_participant::Entity::update_many()
@@ -374,7 +374,7 @@ impl Database {
.select_only()
.column(room_participant::Column::ParticipantIndex)
.into_values::<_, QueryParticipantIndices>()
.all(&*tx)
.all(tx)
.await?;
let mut participant_index = 0;
@@ -407,7 +407,7 @@ impl Database {
tx: &DatabaseTransaction,
) -> Result<JoinRoom> {
let participant_index = self
.get_next_participant_index_internal(room_id, &*tx)
.get_next_participant_index_internal(room_id, tx)
.await?;
room_participant::Entity::insert_many([room_participant::ActiveModel {
@@ -441,12 +441,12 @@ impl Database {
])
.to_owned(),
)
.exec(&*tx)
.exec(tx)
.await?;
let (channel, room) = self.get_channel_room(room_id, &tx).await?;
let channel = channel.ok_or_else(|| anyhow!("no channel for room"))?;
let channel_members = self.get_channel_participants(&channel, &*tx).await?;
let channel_members = self.get_channel_participants(&channel, tx).await?;
Ok(JoinRoom {
room,
channel_id: Some(channel.id),
@@ -491,7 +491,7 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("project does not exist"))?;
if project.host_user_id != user_id {
if project.host_user_id != Some(user_id) {
return Err(anyhow!("no such project"))?;
}
@@ -851,7 +851,7 @@ impl Database {
}
if collaborator.is_host {
left_project.host_user_id = collaborator.user_id;
left_project.host_user_id = Some(collaborator.user_id);
left_project.host_connection_id = Some(collaborator_connection_id);
}
}
@@ -1010,7 +1010,7 @@ impl Database {
.ok_or_else(|| anyhow!("only admins can set participant role"))?;
if role.requires_cla() {
self.check_user_has_signed_cla(user_id, room_id, &*tx)
self.check_user_has_signed_cla(user_id, room_id, &tx)
.await?;
}
@@ -1021,7 +1021,7 @@ impl Database {
.add(room_participant::Column::UserId.eq(user_id)),
)
.set(room_participant::ActiveModel {
role: ActiveValue::set(Some(ChannelRole::from(role))),
role: ActiveValue::set(Some(role)),
..Default::default()
})
.exec(&*tx)
@@ -1030,7 +1030,7 @@ impl Database {
if result.rows_affected != 1 {
Err(anyhow!("could not update room participant role"))?;
}
Ok(self.get_room(room_id, &tx).await?)
self.get_room(room_id, &tx).await
})
.await
}
@@ -1042,11 +1042,11 @@ impl Database {
tx: &DatabaseTransaction,
) -> Result<()> {
let channel = room::Entity::find_by_id(room_id)
.one(&*tx)
.one(tx)
.await?
.ok_or_else(|| anyhow!("could not find room"))?
.find_related(channel::Entity)
.one(&*tx)
.one(tx)
.await?;
if let Some(channel) = channel {
@@ -1057,13 +1057,13 @@ impl Database {
.is_in(channel.ancestors())
.and(channel::Column::RequiresZedCla.eq(true)),
)
.count(&*tx)
.count(tx)
.await?
> 0;
if requires_zed_cla {
if contributor::Entity::find()
.filter(contributor::Column::UserId.eq(user_id))
.one(&*tx)
.one(tx)
.await?
.is_none()
{
@@ -1076,10 +1076,9 @@ impl Database {
pub async fn connection_lost(&self, connection: ConnectionId) -> Result<()> {
self.transaction(|tx| async move {
self.room_connection_lost(connection, &*tx).await?;
self.channel_buffer_connection_lost(connection, &*tx)
.await?;
self.channel_chat_connection_lost(connection, &*tx).await?;
self.room_connection_lost(connection, &tx).await?;
self.channel_buffer_connection_lost(connection, &tx).await?;
self.channel_chat_connection_lost(connection, &tx).await?;
Ok(())
})
.await
@@ -1099,7 +1098,7 @@ impl Database {
.eq(connection.owner_id as i32),
),
)
.one(&*tx)
.one(tx)
.await?;
if let Some(participant) = participant {
@@ -1107,7 +1106,7 @@ impl Database {
answering_connection_lost: ActiveValue::set(true),
..participant.into_active_model()
})
.exec(&*tx)
.exec(tx)
.await?;
}
Ok(())
@@ -1296,7 +1295,7 @@ impl Database {
drop(db_followers);
let channel = if let Some(channel_id) = db_room.channel_id {
Some(self.get_channel_internal(channel_id, &*tx).await?)
Some(self.get_channel_internal(channel_id, tx).await?)
} else {
None
};

View File

@@ -98,7 +98,7 @@ impl Database {
.add(server::Column::Environment.eq(environment))
.add(server::Column::Id.ne(new_server_id)),
)
.all(&*tx)
.all(tx)
.await?;
Ok(stale_servers.into_iter().map(|server| server.id).collect())
}

View File

@@ -80,7 +80,7 @@ impl Database {
github_login,
github_user_id,
github_email,
&*tx,
&tx,
)
.await
})
@@ -122,7 +122,7 @@ impl Database {
metrics_id: ActiveValue::set(Uuid::new_v4()),
..Default::default()
})
.exec_with_returning(&*tx)
.exec_with_returning(tx)
.await?;
Ok(user)
}

View File

@@ -1,4 +1,4 @@
use crate::db::{ProjectId, Result, RoomId, ServerId, UserId};
use crate::db::{HostedProjectId, ProjectId, Result, RoomId, ServerId, UserId};
use anyhow::anyhow;
use rpc::ConnectionId;
use sea_orm::entity::prelude::*;
@@ -8,10 +8,11 @@ use sea_orm::entity::prelude::*;
pub struct Model {
#[sea_orm(primary_key)]
pub id: ProjectId,
pub room_id: RoomId,
pub host_user_id: UserId,
pub room_id: Option<RoomId>,
pub host_user_id: Option<UserId>,
pub host_connection_id: Option<i32>,
pub host_connection_server_id: Option<ServerId>,
pub hosted_project_id: Option<HostedProjectId>,
}
impl Model {

View File

@@ -23,7 +23,7 @@ pub struct TestDb {
impl TestDb {
pub fn sqlite(background: BackgroundExecutor) -> Self {
let url = format!("sqlite::memory:");
let url = "sqlite::memory:";
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
@@ -109,13 +109,13 @@ macro_rules! test_both_dbs {
($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => {
#[gpui::test]
async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
let test_db = crate::db::TestDb::postgres(cx.executor().clone());
let test_db = $crate::db::TestDb::postgres(cx.executor().clone());
$test_name(test_db.db()).await;
}
#[gpui::test]
async fn $sqlite_test_name(cx: &mut gpui::TestAppContext) {
let test_db = crate::db::TestDb::sqlite(cx.executor().clone());
let test_db = $crate::db::TestDb::sqlite(cx.executor().clone());
$test_name(test_db.db()).await;
}
};
@@ -168,7 +168,7 @@ async fn new_test_user(db: &Arc<Database>, email: &str) -> UserId {
email,
false,
NewUserParams {
github_login: email[0..email.find("@").unwrap()].to_string(),
github_login: email[0..email.find('@').unwrap()].to_string(),
github_user_id: GITHUB_USER_ID.fetch_add(1, SeqCst),
},
)

View File

@@ -68,11 +68,12 @@ async fn test_channel_buffers(db: &Arc<Database>) {
.unwrap();
let mut buffer_a = Buffer::new(0, text::BufferId::new(1).unwrap(), "".to_string());
let mut operations = Vec::new();
operations.push(buffer_a.edit([(0..0, "hello world")]));
operations.push(buffer_a.edit([(5..5, ", cruel")]));
operations.push(buffer_a.edit([(0..5, "goodbye")]));
operations.push(buffer_a.undo().unwrap().1);
let operations = vec![
buffer_a.edit([(0..0, "hello world")]),
buffer_a.edit([(5..5, ", cruel")]),
buffer_a.edit([(0..5, "goodbye")]),
buffer_a.undo().unwrap().1,
];
assert_eq!(buffer_a.text(), "hello, cruel world");
let operations = operations
@@ -222,7 +223,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
.unwrap();
buffers.push(
db.transaction(|tx| async move { db.get_channel_buffer(channel, &*tx).await })
db.transaction(|tx| async move { db.get_channel_buffer(channel, &tx).await })
.await
.unwrap(),
);
@@ -238,7 +239,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
.transaction(|tx| {
let buffers = &buffers;
async move {
db.get_latest_operations_for_buffers([buffers[0].id, buffers[2].id], &*tx)
db.get_latest_operations_for_buffers([buffers[0].id, buffers[2].id], &tx)
.await
}
})
@@ -302,7 +303,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
.transaction(|tx| {
let buffers = &buffers;
async move {
db.get_latest_operations_for_buffers([buffers[1].id, buffers[2].id], &*tx)
db.get_latest_operations_for_buffers([buffers[1].id, buffers[2].id], &tx)
.await
}
})
@@ -320,7 +321,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
.transaction(|tx| {
let buffers = &buffers;
async move {
db.get_latest_operations_for_buffers([buffers[0].id, buffers[1].id], &*tx)
db.get_latest_operations_for_buffers([buffers[0].id, buffers[1].id], &tx)
.await
}
})
@@ -342,7 +343,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
hash.insert(buffers[1].id, buffers[1].channel_id);
hash.insert(buffers[2].id, buffers[2].channel_id);
async move { db.latest_channel_buffer_changes(&hash, &*tx).await }
async move { db.latest_channel_buffer_changes(&hash, &tx).await }
})
.await
.unwrap();

View File

@@ -42,8 +42,8 @@ async fn test_channels(db: &Arc<Database>) {
let mut members = db
.transaction(|tx| async move {
let channel = db.get_channel_internal(replace_id, &*tx).await?;
Ok(db.get_channel_participants(&channel, &*tx).await?)
let channel = db.get_channel_internal(replace_id, &tx).await?;
db.get_channel_participants(&channel, &tx).await
})
.await
.unwrap();
@@ -464,9 +464,9 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
db.transaction(|tx| async move {
db.check_user_is_channel_participant(
&db.get_channel_internal(public_channel_id, &*tx).await?,
&db.get_channel_internal(public_channel_id, &tx).await?,
admin,
&*tx,
&tx,
)
.await
})
@@ -474,9 +474,9 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
.unwrap();
db.transaction(|tx| async move {
db.check_user_is_channel_participant(
&db.get_channel_internal(public_channel_id, &*tx).await?,
&db.get_channel_internal(public_channel_id, &tx).await?,
member,
&*tx,
&tx,
)
.await
})
@@ -517,9 +517,9 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
db.transaction(|tx| async move {
db.check_user_is_channel_participant(
&db.get_channel_internal(public_channel_id, &*tx).await?,
&db.get_channel_internal(public_channel_id, &tx).await?,
guest,
&*tx,
&tx,
)
.await
})
@@ -547,11 +547,11 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
assert!(db
.transaction(|tx| async move {
db.check_user_is_channel_participant(
&db.get_channel_internal(public_channel_id, &*tx)
&db.get_channel_internal(public_channel_id, &tx)
.await
.unwrap(),
guest,
&*tx,
&tx,
)
.await
})
@@ -629,9 +629,9 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
db.transaction(|tx| async move {
db.check_user_is_channel_participant(
&db.get_channel_internal(zed_channel, &*tx).await.unwrap(),
&db.get_channel_internal(zed_channel, &tx).await.unwrap(),
guest,
&*tx,
&tx,
)
.await
})
@@ -640,11 +640,11 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
assert!(db
.transaction(|tx| async move {
db.check_user_is_channel_participant(
&db.get_channel_internal(internal_channel_id, &*tx)
&db.get_channel_internal(internal_channel_id, &tx)
.await
.unwrap(),
guest,
&*tx,
&tx,
)
.await
})
@@ -653,11 +653,11 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
db.transaction(|tx| async move {
db.check_user_is_channel_participant(
&db.get_channel_internal(public_channel_id, &*tx)
&db.get_channel_internal(public_channel_id, &tx)
.await
.unwrap(),
guest,
&*tx,
&tx,
)
.await
})

View File

@@ -10,16 +10,15 @@ test_both_dbs!(
async fn test_contributors(db: &Arc<Database>) {
db.create_user(
&format!("user1@example.com"),
"user1@example.com",
false,
NewUserParams {
github_login: format!("user1"),
github_login: "user1".to_string(),
github_user_id: 1,
},
)
.await
.unwrap()
.user_id;
.unwrap();
assert_eq!(db.get_contributors().await.unwrap(), Vec::<String>::new());

View File

@@ -87,8 +87,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
},
)
.await
.unwrap()
.user_id;
.unwrap();
let user_id2 = db
.create_user(
"user2@example.com",
@@ -495,7 +494,7 @@ async fn test_project_count(db: &Arc<Database>) {
let user1 = db
.create_user(
&format!("admin@example.com"),
"admin@example.com",
true,
NewUserParams {
github_login: "admin".into(),
@@ -506,7 +505,7 @@ async fn test_project_count(db: &Arc<Database>) {
.unwrap();
let user2 = db
.create_user(
&format!("user@example.com"),
"user@example.com",
false,
NewUserParams {
github_login: "user".into(),

View File

@@ -13,10 +13,10 @@ test_both_dbs!(
async fn test_get_user_flags(db: &Arc<Database>) {
let user_1 = db
.create_user(
&format!("user1@example.com"),
"user1@example.com",
false,
NewUserParams {
github_login: format!("user1"),
github_login: "user1".to_string(),
github_user_id: 1,
},
)
@@ -26,10 +26,10 @@ async fn test_get_user_flags(db: &Arc<Database>) {
let user_2 = db
.create_user(
&format!("user2@example.com"),
"user2@example.com",
false,
NewUserParams {
github_login: format!("user2"),
github_login: "user2".to_string(),
github_user_id: 2,
},
)

View File

@@ -297,7 +297,7 @@ async fn test_unseen_channel_messages(db: &Arc<Database>) {
// Check that observer has new messages
let latest_messages = db
.transaction(|tx| async move {
db.latest_channel_messages(&[channel_1, channel_2], &*tx)
db.latest_channel_messages(&[channel_1, channel_2], &tx)
.await
})
.await

View File

@@ -43,8 +43,8 @@ impl From<axum::Error> for Error {
}
}
impl From<hyper::Error> for Error {
fn from(error: hyper::Error) -> Self {
impl From<axum::http::Error> for Error {
fn from(error: axum::http::Error) -> Self {
Self::Internal(error.into())
}
}

View File

@@ -1,11 +1,10 @@
use anyhow::anyhow;
use axum::{extract::MatchedPath, routing::get, Extension, Router};
use axum::{extract::MatchedPath, http::Request, routing::get, Extension, Router};
use collab::{
api::fetch_extensions_from_blob_store_periodically, db, env, executor::Executor, AppState,
Config, MigrateConfig, Result,
};
use db::Database;
use hyper::Request;
use std::{
env::args,
net::{SocketAddr, TcpListener},
@@ -16,8 +15,9 @@ use std::{
use tokio::signal::unix::SignalKind;
use tower_http::trace::{self, TraceLayer};
use tracing::Level;
use tracing_log::LogTracer;
use tracing_subscriber::{filter::EnvFilter, fmt::format::JsonFields, Layer};
use tracing_subscriber::{
filter::EnvFilter, fmt::format::JsonFields, util::SubscriberInitExt, Layer,
};
use util::ResultExt;
const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -111,7 +111,8 @@ async fn main() -> Result<()> {
);
#[cfg(unix)]
axum::Server::from_tcp(listener)?
axum::Server::from_tcp(listener)
.map_err(|e| anyhow!(e))?
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
.with_graceful_shutdown(async move {
let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate())
@@ -128,7 +129,8 @@ async fn main() -> Result<()> {
rpc_server.teardown();
}
})
.await?;
.await
.map_err(|e| anyhow!(e))?;
// todo("windows")
#[cfg(windows)]
@@ -178,11 +180,11 @@ async fn handle_liveness_probe(Extension(state): Extension<Arc<AppState>>) -> Re
pub fn init_tracing(config: &Config) -> Option<()> {
use std::str::FromStr;
use tracing_subscriber::layer::SubscriberExt;
let rust_log = config.rust_log.clone()?;
LogTracer::init().log_err()?;
let filter = EnvFilter::from_str(config.rust_log.as_deref()?).log_err()?;
let subscriber = tracing_subscriber::Registry::default()
tracing_subscriber::registry()
.with(console_subscriber::spawn())
.with(if config.log_json.unwrap_or(false) {
Box::new(
tracing_subscriber::fmt::layer()
@@ -192,17 +194,17 @@ pub fn init_tracing(config: &Config) -> Option<()> {
.json()
.flatten_event(true)
.with_span_list(true),
),
)
.with_filter(filter),
) as Box<dyn Layer<_> + Send + Sync>
} else {
Box::new(
tracing_subscriber::fmt::layer()
.event_format(tracing_subscriber::fmt::format().pretty()),
.event_format(tracing_subscriber::fmt::format().pretty())
.with_filter(filter),
)
})
.with(EnvFilter::from_str(rust_log.as_str()).log_err()?);
tracing::subscriber::set_global_default(subscriber).unwrap();
.init();
None
}

View File

@@ -4,8 +4,9 @@ use crate::{
auth::{self, Impersonator},
db::{
self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database,
InviteMemberResult, MembershipUpdated, MessageId, NotificationId, ProjectId,
RemoveChannelMemberResult, RespondToChannelInvite, RoomId, ServerId, User, UserId,
HostedProjectId, InviteMemberResult, MembershipUpdated, MessageId, NotificationId, Project,
ProjectId, RemoveChannelMemberResult, ReplicaId, RespondToChannelInvite, RoomId, ServerId,
User, UserId,
},
executor::Executor,
AppState, Error, Result,
@@ -66,7 +67,9 @@ use tracing::{field, info_span, instrument, Instrument};
use util::SemanticVersion;
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
// kubernetes gives terminated pods 10s to shutdown gracefully. After they're gone, we can clean up old resources.
pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(15);
const MESSAGE_COUNT_PER_PAGE: usize = 100;
const MAX_MESSAGE_LEN: usize = 1024;
@@ -197,6 +200,7 @@ impl Server {
.add_request_handler(share_project)
.add_message_handler(unshare_project)
.add_request_handler(join_project)
.add_request_handler(join_hosted_project)
.add_message_handler(leave_project)
.add_request_handler(update_project)
.add_request_handler(update_worktree)
@@ -354,7 +358,7 @@ impl Server {
&refreshed_room.room,
&refreshed_room.channel_members,
&peer,
&*pool.lock(),
&pool.lock(),
);
}
contacts_to_update
@@ -463,6 +467,7 @@ impl Server {
TypeId::of::<M>(),
Box::new(move |envelope, session| {
let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
let received_at = envelope.received_at;
let span = info_span!(
"handle message",
payload_type = envelope.payload_type_name()
@@ -477,12 +482,14 @@ impl Server {
let future = (handler)(*envelope, session);
async move {
let result = future.await;
let duration_ms = start_time.elapsed().as_micros() as f64 / 1000.0;
let total_duration_ms = received_at.elapsed().as_micros() as f64 / 1000.0;
let processing_duration_ms = start_time.elapsed().as_micros() as f64 / 1000.0;
let queue_duration_ms = total_duration_ms - processing_duration_ms;
match result {
Err(error) => {
tracing::error!(%error, ?duration_ms, "error handling message")
tracing::error!(%error, ?total_duration_ms, ?processing_duration_ms, ?queue_duration_ms, "error handling message")
}
Ok(()) => tracing::info!(?duration_ms, "finished handling message"),
Ok(()) => tracing::info!(?total_duration_ms, ?processing_duration_ms, ?queue_duration_ms, "finished handling message"),
}
}
.instrument(span)
@@ -544,6 +551,7 @@ impl Server {
})
}
#[allow(clippy::too_many_arguments)]
pub fn handle_connection(
self: &Arc<Self>,
connection: Connection,
@@ -754,13 +762,13 @@ impl<'a> Deref for ConnectionPoolGuard<'a> {
type Target = ConnectionPool;
fn deref(&self) -> &Self::Target {
&*self.guard
&self.guard
}
}
impl<'a> DerefMut for ConnectionPoolGuard<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.guard
&mut self.guard
}
}
@@ -842,7 +850,7 @@ impl Header for AppVersionHeader {
}
}
pub fn routes(server: Arc<Server>) -> Router<Body> {
pub fn routes(server: Arc<Server>) -> Router<(), Body> {
Router::new()
.route("/rpc", get(handle_websocket_request))
.layer(
@@ -1584,22 +1592,46 @@ async fn join_project(
session: Session,
) -> Result<()> {
let project_id = ProjectId::from_proto(request.project_id);
let guest_user_id = session.user_id;
tracing::info!(%project_id, "join project");
let (project, replica_id) = &mut *session
.db()
.await
.join_project(project_id, session.connection_id)
.join_project_in_room(project_id, session.connection_id)
.await?;
join_project_internal(response, session, project, replica_id)
}
trait JoinProjectInternalResponse {
fn send(self, result: proto::JoinProjectResponse) -> Result<()>;
}
impl JoinProjectInternalResponse for Response<proto::JoinProject> {
fn send(self, result: proto::JoinProjectResponse) -> Result<()> {
Response::<proto::JoinProject>::send(self, result)
}
}
impl JoinProjectInternalResponse for Response<proto::JoinHostedProject> {
fn send(self, result: proto::JoinProjectResponse) -> Result<()> {
Response::<proto::JoinHostedProject>::send(self, result)
}
}
fn join_project_internal(
response: impl JoinProjectInternalResponse,
session: Session,
project: &mut Project,
replica_id: &ReplicaId,
) -> Result<()> {
let collaborators = project
.collaborators
.iter()
.filter(|collaborator| collaborator.connection_id != session.connection_id)
.map(|collaborator| collaborator.to_proto())
.collect::<Vec<_>>();
let project_id = project.id;
let guest_user_id = session.user_id;
let worktrees = project
.worktrees
@@ -1631,10 +1663,12 @@ async fn join_project(
// First, we send the metadata associated with each worktree.
response.send(proto::JoinProjectResponse {
project_id: project.id.0 as u64,
worktrees: worktrees.clone(),
replica_id: replica_id.0 as u32,
collaborators: collaborators.clone(),
language_servers: project.language_servers.clone(),
role: project.role.into(), // todo
})?;
for (worktree_id, worktree) in mem::take(&mut project.worktrees) {
@@ -1707,15 +1741,17 @@ async fn join_project(
async fn leave_project(request: proto::LeaveProject, session: Session) -> Result<()> {
let sender_id = session.connection_id;
let project_id = ProjectId::from_proto(request.project_id);
let db = session.db().await;
if db.is_hosted_project(project_id).await? {
let project = db.leave_hosted_project(project_id, sender_id).await?;
project_left(&project, &session);
return Ok(());
}
let (room, project) = &*session
.db()
.await
.leave_project(project_id, sender_id)
.await?;
let (room, project) = &*db.leave_project(project_id, sender_id).await?;
tracing::info!(
%project_id,
host_user_id = %project.host_user_id,
host_user_id = ?project.host_user_id,
host_connection_id = ?project.host_connection_id,
"leave project"
);
@@ -1726,6 +1762,24 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
Ok(())
}
async fn join_hosted_project(
request: proto::JoinHostedProject,
response: Response<proto::JoinHostedProject>,
session: Session,
) -> Result<()> {
let (mut project, replica_id) = session
.db()
.await
.join_hosted_project(
HostedProjectId(request.id as i32),
session.user_id,
session.connection_id,
)
.await?;
join_project_internal(response, session, &mut project, &replica_id)
}
/// Updates other participants with changes to the project
async fn update_project(
request: proto::UpdateProject,
@@ -2226,7 +2280,7 @@ async fn request_contact(
session.peer.send(connection_id, update.clone())?;
}
send_notifications(&*connection_pool, &session.peer, notifications);
send_notifications(&connection_pool, &session.peer, notifications);
response.send(proto::Ack {})?;
Ok(())
@@ -2283,7 +2337,7 @@ async fn respond_to_contact_request(
session.peer.send(connection_id, update.clone())?;
}
send_notifications(&*pool, &session.peer, notifications);
send_notifications(&pool, &session.peer, notifications);
}
response.send(proto::Ack {})?;
@@ -2451,7 +2505,7 @@ async fn invite_channel_member(
session.peer.send(connection_id, update.clone())?;
}
send_notifications(&*connection_pool, &session.peer, notifications);
send_notifications(&connection_pool, &session.peer, notifications);
response.send(proto::Ack {})?;
Ok(())
@@ -2713,7 +2767,7 @@ async fn respond_to_channel_invite(
}
};
send_notifications(&*connection_pool, &session.peer, notifications);
send_notifications(&connection_pool, &session.peer, notifications);
response.send(proto::Ack {})?;
@@ -2883,7 +2937,7 @@ async fn update_channel_buffer(
.flat_map(|user_id| pool.user_connection_ids(*user_id)),
|peer_id| {
session.peer.send(
peer_id.into(),
peer_id,
proto::UpdateChannels {
latest_channel_buffer_versions: vec![proto::ChannelBufferVersion {
channel_id: channel_id.to_proto(),
@@ -2968,8 +3022,8 @@ fn channel_buffer_updated<T: EnvelopedMessage>(
message: &T,
peer: &Peer,
) {
broadcast(Some(sender_id), collaborators.into_iter(), |peer_id| {
peer.send(peer_id.into(), message.clone())
broadcast(Some(sender_id), collaborators, |peer_id| {
peer.send(peer_id, message.clone())
});
}
@@ -3075,7 +3129,7 @@ async fn send_channel_message(
.flat_map(|user_id| pool.user_connection_ids(*user_id)),
|peer_id| {
session.peer.send(
peer_id.into(),
peer_id,
proto::UpdateChannels {
latest_channel_message_ids: vec![proto::ChannelMessageId {
channel_id: channel_id.to_proto(),
@@ -3401,7 +3455,6 @@ fn build_update_user_channels(channels: &ChannelsForUser) -> proto::UpdateUserCh
.collect(),
observed_channel_buffer_version: channels.observed_buffer_versions.clone(),
observed_channel_message_id: channels.observed_channel_messages.clone(),
..Default::default()
}
}
@@ -3478,7 +3531,7 @@ fn room_updated(room: &proto::Room, peer: &Peer) {
.filter_map(|participant| Some(participant.peer_id?.into())),
|peer_id| {
peer.send(
peer_id.into(),
peer_id,
proto::RoomUpdated {
room: Some(room.clone()),
},
@@ -3507,7 +3560,7 @@ fn channel_updated(
.flat_map(|user_id| pool.user_connection_ids(*user_id)),
|peer_id| {
peer.send(
peer_id.into(),
peer_id,
proto::UpdateChannels {
channel_participants: vec![proto::ChannelParticipants {
channel_id: channel_id.to_proto(),
@@ -3656,7 +3709,7 @@ async fn leave_channel_buffers_for_session(session: &Session) -> Result<()> {
fn project_left(project: &db::LeftProject, session: &Session) {
for connection_id in &project.connection_ids {
if project.host_user_id == session.user_id {
if project.host_user_id == Some(session.user_id) {
session
.peer
.send(

View File

@@ -94,21 +94,19 @@ impl ConnectionPool {
self.connected_users
.get(&user_id)
.into_iter()
.map(|state| {
.flat_map(|state| {
state
.connection_ids
.iter()
.flat_map(|cid| self.connections.get(cid))
})
.flatten()
}
pub fn user_connection_ids(&self, user_id: UserId) -> impl Iterator<Item = ConnectionId> + '_ {
self.connected_users
.get(&user_id)
.into_iter()
.map(|state| &state.connection_ids)
.flatten()
.flat_map(|state| &state.connection_ids)
.copied()
}

View File

@@ -1431,7 +1431,7 @@ fn assert_channels(
.ordered_channels()
.map(|(depth, channel)| ExpectedChannel {
depth,
name: channel.name.clone().into(),
name: channel.name.clone(),
id: channel.id,
})
.collect::<Vec<_>>()

View File

@@ -833,7 +833,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
name: "the-language-server".into(),
name: "the-language-server",
..Default::default()
},
);

View File

@@ -373,8 +373,10 @@ async fn test_basic_following(
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
cx_b.background_executor.run_until_parked();
editor_b1.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
});
@@ -387,6 +389,7 @@ async fn test_basic_following(
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
editor.set_scroll_position(point(0., 100.), cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
editor_b1.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), &[3..3]);
@@ -1437,14 +1440,13 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
});
executor.run_until_parked();
let window_b_project_a = cx_b
let window_b_project_a = *cx_b
.windows()
.iter()
.max_by_key(|window| window.window_id())
.unwrap()
.clone();
.unwrap();
let mut cx_b2 = VisualTestContext::from_window(window_b_project_a.clone(), cx_b);
let mut cx_b2 = VisualTestContext::from_window(window_b_project_a, cx_b);
let workspace_b_project_a = window_b_project_a
.downcast::<Workspace>()
@@ -1535,7 +1537,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
executor.run_until_parked();
assert_eq!(visible_push_notifications(cx_a).len(), 1);
cx_a.update(|cx| {
workspace::join_remote_project(
workspace::join_in_room_project(
project_b_id,
client_b.user_id().unwrap(),
client_a.app_state.clone(),
@@ -1548,13 +1550,12 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
executor.run_until_parked();
assert_eq!(visible_push_notifications(cx_a).len(), 0);
let window_a_project_b = cx_a
let window_a_project_b = *cx_a
.windows()
.iter()
.max_by_key(|window| window.window_id())
.unwrap()
.clone();
let cx_a2 = &mut VisualTestContext::from_window(window_a_project_b.clone(), cx_a);
.unwrap();
let cx_a2 = &mut VisualTestContext::from_window(window_a_project_b, cx_a);
let workspace_a_project_b = window_a_project_b
.downcast::<Workspace>()
.unwrap()
@@ -1600,6 +1601,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
cx_a.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), vec![1..1])
@@ -1618,6 +1621,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([2..2]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
cx_a.run_until_parked();
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), vec![1..1])
@@ -1722,6 +1727,7 @@ async fn test_following_into_excluded_file(
// When client B starts following client A, currently visible file is replicated
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
@@ -1743,6 +1749,7 @@ async fn test_following_into_excluded_file(
editor_for_excluded_a.update(cx_a, |editor, cx| {
editor.select_right(&Default::default(), cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
// Changes from B to the excluded file are replicated in A's editor

View File

@@ -22,7 +22,6 @@ use project::{
search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath,
};
use rand::prelude::*;
use rpc::proto::ChannelRole;
use serde_json::json;
use settings::SettingsStore;
use std::{
@@ -3742,7 +3741,6 @@ async fn test_leaving_project(
client_b.user_store().clone(),
client_b.language_registry().clone(),
FakeFs::new(cx.background_executor().clone()),
ChannelRole::Member,
cx,
)
})
@@ -3885,7 +3883,6 @@ async fn test_collaborating_with_diagnostics(
DiagnosticSummary {
error_count: 1,
warning_count: 0,
..Default::default()
},
)]
)
@@ -3922,7 +3919,6 @@ async fn test_collaborating_with_diagnostics(
DiagnosticSummary {
error_count: 1,
warning_count: 0,
..Default::default()
},
)]
);

View File

@@ -996,7 +996,7 @@ impl RandomizedTest for ProjectCollaborationTest {
let statuses = statuses
.iter()
.map(|(path, val)| (path.as_path(), val.clone()))
.map(|(path, val)| (path.as_path(), *val))
.collect::<Vec<_>>();
if client.fs().metadata(&dot_git_dir).await?.is_none() {
@@ -1483,10 +1483,10 @@ fn project_for_root_name(
root_name: &str,
cx: &TestAppContext,
) -> Option<Model<Project>> {
if let Some(ix) = project_ix_for_root_name(&*client.local_projects().deref(), root_name, cx) {
if let Some(ix) = project_ix_for_root_name(client.local_projects().deref(), root_name, cx) {
return Some(client.local_projects()[ix].clone());
}
if let Some(ix) = project_ix_for_root_name(&*client.remote_projects().deref(), root_name, cx) {
if let Some(ix) = project_ix_for_root_name(client.remote_projects().deref(), root_name, cx) {
return Some(client.remote_projects()[ix].clone());
}
None

View File

@@ -464,6 +464,7 @@ impl<T: RandomizedTest> TestPlan<T> {
})
}
#[allow(clippy::too_many_arguments)]
async fn apply_server_operation(
plan: Arc<Mutex<Self>>,
deterministic: BackgroundExecutor,

View File

@@ -138,7 +138,7 @@ impl TestServer {
(server, client_a, client_b, channel_id)
}
pub async fn start1<'a>(cx: &'a mut TestAppContext) -> TestClient {
pub async fn start1(cx: &mut TestAppContext) -> TestClient {
let mut server = Self::start(cx.executor().clone()).await;
server.create_client(cx, "user_a").await
}
@@ -466,7 +466,7 @@ impl TestServer {
let active_call_a = cx_a.read(ActiveCall::global);
for (client_b, cx_b) in right {
let user_id_b = client_b.current_user_id(*cx_b).to_proto();
let user_id_b = client_b.current_user_id(cx_b).to_proto();
active_call_a
.update(*cx_a, |call, cx| call.invite(user_id_b, None, cx))
.await
@@ -589,19 +589,19 @@ impl TestClient {
.await;
}
pub fn local_projects<'a>(&'a self) -> impl Deref<Target = Vec<Model<Project>>> + 'a {
pub fn local_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
Ref::map(self.state.borrow(), |state| &state.local_projects)
}
pub fn remote_projects<'a>(&'a self) -> impl Deref<Target = Vec<Model<Project>>> + 'a {
pub fn remote_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
Ref::map(self.state.borrow(), |state| &state.remote_projects)
}
pub fn local_projects_mut<'a>(&'a self) -> impl DerefMut<Target = Vec<Model<Project>>> + 'a {
pub fn local_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
RefMut::map(self.state.borrow_mut(), |state| &mut state.local_projects)
}
pub fn remote_projects_mut<'a>(&'a self) -> impl DerefMut<Target = Vec<Model<Project>>> + 'a {
pub fn remote_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
RefMut::map(self.state.borrow_mut(), |state| &mut state.remote_projects)
}
@@ -614,16 +614,14 @@ impl TestClient {
})
}
pub fn buffers<'a>(
&'a self,
) -> impl DerefMut<Target = HashMap<Model<Project>, HashSet<Model<language::Buffer>>>> + 'a
pub fn buffers(
&self,
) -> impl DerefMut<Target = HashMap<Model<Project>, HashSet<Model<language::Buffer>>>> + '_
{
RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers)
}
pub fn channel_buffers<'a>(
&'a self,
) -> impl DerefMut<Target = HashSet<Model<ChannelBuffer>>> + 'a {
pub fn channel_buffers(&self) -> impl DerefMut<Target = HashSet<Model<ChannelBuffer>>> + '_ {
RefMut::map(self.state.borrow_mut(), |state| &mut state.channel_buffers)
}

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/collab_ui.rs"
doctest = false

View File

@@ -171,10 +171,8 @@ impl ChannelView {
let this = this.clone();
Some(ui::ContextMenu::build(cx, move |menu, _| {
menu.entry("Copy link to section", None, move |cx| {
this.update(cx, |this, cx| {
this.copy_link_for_position(position.clone(), cx)
})
.ok();
this.update(cx, |this, cx| this.copy_link_for_position(position, cx))
.ok();
})
}))
});
@@ -267,7 +265,7 @@ impl ChannelView {
return;
};
let link = channel.notes_link(closest_heading.map(|heading| heading.text));
let link = channel.notes_link(closest_heading.map(|heading| heading.text), cx);
cx.write_to_clipboard(ClipboardItem::new(link));
self.workspace
.update(cx, |workspace, cx| {
@@ -378,7 +376,7 @@ impl Item for ChannelView {
(_, false) => format!("#{} (disconnected)", channel.name),
}
} else {
format!("channel notes (disconnected)")
"channel notes (disconnected)".to_string()
};
Label::new(label)
.color(if selected {

View File

@@ -447,8 +447,7 @@ impl ChatPanel {
let reply_to_message = message
.reply_to_message_id
.map(|id| active_chat.read(cx).find_loaded_message(id))
.flatten()
.and_then(|id| active_chat.read(cx).find_loaded_message(id))
.cloned();
let replied_to_you =
@@ -635,14 +634,12 @@ impl ChatPanel {
"Copy message text",
None,
cx.handler_for(&this, move |this, cx| {
this.active_chat().map(|active_chat| {
if let Some(message) =
active_chat.read(cx).find_loaded_message(message_id)
{
let text = message.body.clone();
cx.write_to_clipboard(ClipboardItem::new(text))
}
});
if let Some(message) = this.active_chat().and_then(|active_chat| {
active_chat.read(cx).find_loaded_message(message_id)
}) {
let text = message.body.clone();
cx.write_to_clipboard(ClipboardItem::new(text))
}
}),
)
.when(can_modify_message, |menu| {
@@ -944,16 +941,11 @@ impl Render for ChatPanel {
.when_some(reply_to_message_id, |el, reply_to_message_id| {
let reply_message = self
.active_chat()
.map(|active_chat| {
active_chat.read(cx).messages().iter().find_map(|m| {
if m.id == ChannelMessageId::Saved(reply_to_message_id) {
Some(m)
} else {
None
}
.and_then(|active_chat| {
active_chat.read(cx).messages().iter().find(|message| {
message.id == ChannelMessageId::Saved(reply_to_message_id)
})
})
.flatten()
.cloned();
el.when_some(reply_message, |el, reply_message| {

View File

@@ -151,9 +151,9 @@ impl MessageEditor {
) {
self.editor.update(cx, |editor, cx| {
if let Some(channel_name) = channel_name {
editor.set_placeholder_text(format!("Message #{}", channel_name), cx);
editor.set_placeholder_text(format!("Message #{channel_name}"), cx);
} else {
editor.set_placeholder_text(format!("Message Channel"), cx);
editor.set_placeholder_text("Message Channel", cx);
}
});
self.channel_id = Some(channel_id);
@@ -324,7 +324,7 @@ impl MessageEditor {
for range in ranges {
text.clear();
text.extend(buffer.text_for_range(range.clone()));
if let Some(username) = text.strip_prefix("@") {
if let Some(username) = text.strip_prefix('@') {
if let Some(user_id) = this.channel_members.get(username) {
let start = multi_buffer.anchor_after(range.start);
let end = multi_buffer.anchor_after(range.end);
@@ -371,7 +371,7 @@ impl Render for MessageEditor {
font_size: UiTextSize::Small.rems().into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3).into(),
line_height: relative(1.3),
background_color: None,
underline: None,
strikethrough: None,

View File

@@ -7,8 +7,8 @@ use crate::{
CollaborationPanelSettings,
};
use call::ActiveCall;
use channel::{Channel, ChannelEvent, ChannelStore, HostedProjectId};
use client::{ChannelId, Client, Contact, User, UserStore};
use channel::{Channel, ChannelEvent, ChannelStore};
use client::{ChannelId, Client, Contact, HostedProjectId, User, UserStore};
use contact_finder::ContactFinder;
use db::kvp::KEY_VALUE_STORE;
use editor::{Editor, EditorElement, EditorStyle};
@@ -441,17 +441,13 @@ impl CollabPanel {
// Populate remote participants.
self.match_candidates.clear();
self.match_candidates
.extend(
room.remote_participants()
.iter()
.filter_map(|(_, participant)| {
Some(StringMatchCandidate {
id: participant.user.id as usize,
string: participant.user.github_login.clone(),
char_bag: participant.user.github_login.chars().collect(),
})
}),
);
.extend(room.remote_participants().values().map(|participant| {
StringMatchCandidate {
id: participant.user.id as usize,
string: participant.user.github_login.clone(),
char_bag: participant.user.github_login.chars().collect(),
}
}));
let mut matches = executor.block(match_strings(
&self.match_candidates,
&query,
@@ -915,7 +911,7 @@ impl CollabPanel {
this.workspace
.update(cx, |workspace, cx| {
let app_state = workspace.app_state().clone();
workspace::join_remote_project(project_id, host_user_id, app_state, cx)
workspace::join_in_room_project(project_id, host_user_id, app_state, cx)
.detach_and_prompt_err("Failed to join project", cx, |_, _| None);
})
.ok();
@@ -956,7 +952,7 @@ impl CollabPanel {
})
.ok();
}))
.tooltip(move |cx| Tooltip::text(format!("Open shared screen"), cx))
.tooltip(move |cx| Tooltip::text("Open shared screen", cx))
})
}
@@ -1051,8 +1047,15 @@ impl CollabPanel {
.indent_level(2)
.indent_step_size(px(20.))
.selected(is_selected)
.on_click(cx.listener(move |_this, _, _cx| {
// todo()
.on_click(cx.listener(move |this, _, cx| {
if let Some(workspace) = this.workspace.upgrade() {
let app_state = workspace.read(cx).app_state().clone();
workspace::join_hosted_project(id, app_state, cx).detach_and_prompt_err(
"Failed to open project",
cx,
|_, _| None,
)
}
}))
.start_slot(
h_flex()
@@ -1465,7 +1468,7 @@ impl CollabPanel {
} => {
if let Some(workspace) = self.workspace.upgrade() {
let app_state = workspace.read(cx).app_state().clone();
workspace::join_remote_project(
workspace::join_in_room_project(
*project_id,
*host_user_id,
app_state,
@@ -1633,7 +1636,7 @@ impl CollabPanel {
self.toggle_channel_collapsed(id, cx)
}
fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
fn toggle_channel_collapsed(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
match self.collapsed_channels.binary_search(&channel_id) {
Ok(ix) => {
self.collapsed_channels.remove(ix);
@@ -2027,7 +2030,7 @@ impl CollabPanel {
let Some(channel) = channel_store.channel_for_id(channel_id) else {
return;
};
let item = ClipboardItem::new(channel.link());
let item = ClipboardItem::new(channel.link(cx));
cx.write_to_clipboard(item)
}
@@ -2175,7 +2178,7 @@ impl CollabPanel {
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3).into(),
line_height: relative(1.3),
background_color: None,
underline: None,
strikethrough: None,
@@ -2210,7 +2213,7 @@ impl CollabPanel {
let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
channel_link = Some(channel.link());
channel_link = Some(channel.link(cx));
(channel_icon, channel_tooltip_text) = match channel.visibility {
proto::ChannelVisibility::Public => {
(Some("icons/public.svg"), Some("Copy public channel link."))
@@ -2224,7 +2227,7 @@ impl CollabPanel {
});
if let Some(name) = channel_name {
SharedString::from(format!("{}", name))
SharedString::from(name.to_string())
} else {
SharedString::from("Current Call")
}
@@ -2510,7 +2513,7 @@ impl CollabPanel {
.map(|channel| channel.visibility)
== Some(proto::ChannelVisibility::Public);
let disclosed =
has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
has_children.then(|| self.collapsed_channels.binary_search(&channel.id).is_err());
let has_messages_notification = channel_store.has_new_messages(channel_id);
let has_notes_notification = channel_store.has_channel_buffer_changed(channel_id);

View File

@@ -197,7 +197,7 @@ impl Render for ChannelModal {
.read(cx)
.channel_for_id(channel_id)
{
let item = ClipboardItem::new(channel.link());
let item = ClipboardItem::new(channel.link(cx));
cx.write_to_clipboard(item);
}
})),

View File

@@ -403,7 +403,7 @@ impl CollabTitlebarItem {
)
})
.on_click({
let host_peer_id = host.peer_id.clone();
let host_peer_id = host.peer_id;
cx.listener(move |this, _, cx| {
this.workspace
.update(cx, |workspace, cx| {
@@ -478,6 +478,7 @@ impl CollabTitlebarItem {
)
}
#[allow(clippy::too_many_arguments)]
fn render_collaborator(
&self,
user: &Arc<User>,

View File

@@ -778,7 +778,7 @@ fn format_timestamp(
"just now".to_string()
}
} else if date.next_day() == Some(today) {
format!("yesterday")
"yesterday".to_string()
} else {
format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
}

View File

@@ -82,7 +82,7 @@ impl IncomingCallNotificationState {
if let Some(project_id) = initial_project_id {
cx.update(|cx| {
if let Some(app_state) = app_state.upgrade() {
workspace::join_remote_project(
workspace::join_in_room_project(
project_id,
caller_user_id,
app_state,
@@ -119,7 +119,7 @@ impl Render for IncomingCallNotification {
let theme_settings = ThemeSettings::get_global(cx);
(
theme_settings.ui_font.family.clone(),
theme_settings.ui_font_size.clone(),
theme_settings.ui_font_size,
)
};

View File

@@ -98,7 +98,7 @@ impl ProjectSharedNotification {
fn join(&mut self, cx: &mut ViewContext<Self>) {
if let Some(app_state) = self.app_state.upgrade() {
workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx)
.detach_and_log_err(cx);
}
}
@@ -123,7 +123,7 @@ impl Render for ProjectSharedNotification {
let theme_settings = ThemeSettings::get_global(cx);
(
theme_settings.ui_font.family.clone(),
theme_settings.ui_font_size.clone(),
theme_settings.ui_font_size,
)
};

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/collections.rs"
doctest = false

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[features]
default = []

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/command_palette.rs"
doctest = false
@@ -18,7 +21,6 @@ gpui.workspace = true
picker.workspace = true
postage.workspace = true
project.workspace = true
release_channel.workspace = true
serde.workspace = true
settings.workspace = true
theme.workspace = true

View File

@@ -4,7 +4,7 @@ use std::{
time::Duration,
};
use client::telemetry::Telemetry;
use client::{parse_zed_link, telemetry::Telemetry};
use collections::HashMap;
use command_palette_hooks::{
CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor,
@@ -17,7 +17,6 @@ use gpui::{
use picker::{Picker, PickerDelegate};
use postage::{sink::Sink, stream::Stream};
use release_channel::parse_zed_link;
use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ModalView, Workspace};
@@ -26,6 +25,7 @@ use zed_actions::OpenZedUrl;
actions!(command_palette, [Toggle]);
pub fn init(cx: &mut AppContext) {
client::init_settings(cx);
cx.set_global(HitCounts::default());
cx.set_global(CommandPaletteFilter::default());
cx.observe_new_views(CommandPalette::register).detach();
@@ -192,7 +192,7 @@ impl CommandPaletteDelegate {
None
};
if parse_zed_link(&query).is_some() {
if parse_zed_link(&query, cx).is_some() {
intercept_result = Some(CommandInterceptResult {
action: OpenZedUrl { url: query.clone() }.boxed_clone(),
string: query.clone(),
@@ -396,6 +396,7 @@ impl PickerDelegate for CommandPaletteDelegate {
.child(
h_flex()
.w_full()
.py_px()
.justify_between()
.child(HighlightedLabel::new(
command.name.clone(),

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/command_palette_hooks.rs"
doctest = false

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/copilot.rs"
doctest = false

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/copilot_ui.rs"
doctest = false

View File

@@ -119,7 +119,9 @@ impl Render for CopilotButton {
impl CopilotButton {
pub fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
if let Some(copilot) = Copilot::global(cx) {
cx.observe(&copilot, |_, _, cx| cx.notify()).detach()
}
cx.observe_global::<SettingsStore>(move |_, cx| cx.notify())
.detach();
@@ -238,7 +240,7 @@ impl CopilotButton {
impl StatusItemView for CopilotButton {
fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
if let Some(editor) = item.map(|item| item.act_as::<Editor>(cx)).flatten() {
if let Some(editor) = item.and_then(|item| item.act_as::<Editor>(cx)) {
self.editor_subscription = Some((
cx.observe(&editor, Self::update_enabled),
editor.entity_id().as_u64() as usize,
@@ -309,7 +311,7 @@ async fn configure_disabled_globs(
fn toggle_copilot_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
file.defaults.show_copilot_suggestions = Some(!show_copilot_suggestions)
});
}
@@ -330,7 +332,7 @@ fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut AppContext) {
});
}
fn initiate_sign_in(cx: &mut WindowContext) {
pub fn initiate_sign_in(cx: &mut WindowContext) {
let Some(copilot) = Copilot::global(cx) else {
return;
};

Some files were not shown because too many files have changed in this diff Show More