Merge branch 'main' into channel-changes
This commit is contained in:
commit
95342c8c33
69 changed files with 3632 additions and 1895 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1409,6 +1409,7 @@ dependencies = [
|
||||||
"settings",
|
"settings",
|
||||||
"smol",
|
"smol",
|
||||||
"sum_tree",
|
"sum_tree",
|
||||||
|
"sysinfo",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"text",
|
"text",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -2826,7 +2827,6 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"regex",
|
"regex",
|
||||||
"rope",
|
"rope",
|
||||||
|
@ -7648,6 +7648,8 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap 4.4.4",
|
"clap 4.4.4",
|
||||||
|
"fs",
|
||||||
|
"futures 0.3.28",
|
||||||
"gpui2",
|
"gpui2",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
|
@ -7851,9 +7853,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.27.8"
|
version = "0.29.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33"
|
checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"core-foundation-sys 0.8.3",
|
"core-foundation-sys 0.8.3",
|
||||||
|
@ -8872,6 +8874,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"gpui2",
|
"gpui2",
|
||||||
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
|
|
@ -112,6 +112,7 @@ serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||||
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
smol = { version = "1.2" }
|
smol = { version = "1.2" }
|
||||||
|
sysinfo = "0.29.10"
|
||||||
tempdir = { version = "0.3.7" }
|
tempdir = { version = "0.3.7" }
|
||||||
thiserror = { version = "1.0.29" }
|
thiserror = { version = "1.0.29" }
|
||||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||||
|
|
2
Procfile
2
Procfile
|
@ -1,4 +1,4 @@
|
||||||
web: cd ../zed.dev && PORT=3000 npm run dev
|
web: cd ../zed.dev && PORT=3000 npm run dev
|
||||||
collab: cd crates/collab && cargo run serve
|
collab: cd crates/collab && RUST_LOG=${RUST_LOG:-collab=info} cargo run serve
|
||||||
livekit: livekit-server --dev
|
livekit: livekit-server --dev
|
||||||
postgrest: postgrest crates/collab/admin_api.conf
|
postgrest: postgrest crates/collab/admin_api.conf
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod assistant_panel;
|
pub mod assistant_panel;
|
||||||
mod assistant_settings;
|
mod assistant_settings;
|
||||||
mod codegen;
|
mod codegen;
|
||||||
|
mod prompts;
|
||||||
mod streaming_diff;
|
mod streaming_diff;
|
||||||
|
|
||||||
use ai::completion::Role;
|
use ai::completion::Role;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAIModel},
|
assistant_settings::{AssistantDockPosition, AssistantSettings, OpenAIModel},
|
||||||
codegen::{self, Codegen, CodegenKind},
|
codegen::{self, Codegen, CodegenKind},
|
||||||
|
prompts::generate_content_prompt,
|
||||||
MessageId, MessageMetadata, MessageStatus, Role, SavedConversation, SavedConversationMetadata,
|
MessageId, MessageMetadata, MessageStatus, Role, SavedConversation, SavedConversationMetadata,
|
||||||
SavedMessage,
|
SavedMessage,
|
||||||
};
|
};
|
||||||
|
@ -273,13 +274,17 @@ impl AssistantPanel {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let selection = editor.read(cx).selections.newest_anchor().clone();
|
||||||
|
if selection.start.excerpt_id() != selection.end.excerpt_id() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
|
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
|
||||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||||
let provider = Arc::new(OpenAICompletionProvider::new(
|
let provider = Arc::new(OpenAICompletionProvider::new(
|
||||||
api_key,
|
api_key,
|
||||||
cx.background().clone(),
|
cx.background().clone(),
|
||||||
));
|
));
|
||||||
let selection = editor.read(cx).selections.newest_anchor().clone();
|
|
||||||
let codegen_kind = if editor.read(cx).selections.newest::<usize>(cx).is_empty() {
|
let codegen_kind = if editor.read(cx).selections.newest::<usize>(cx).is_empty() {
|
||||||
CodegenKind::Generate {
|
CodegenKind::Generate {
|
||||||
position: selection.start,
|
position: selection.start,
|
||||||
|
@ -541,11 +546,26 @@ impl AssistantPanel {
|
||||||
self.inline_prompt_history.pop_front();
|
self.inline_prompt_history.pop_front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let codegen = pending_assist.codegen.clone();
|
||||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||||
let range = pending_assist.codegen.read(cx).range();
|
let range = codegen.read(cx).range();
|
||||||
let selected_text = snapshot.text_for_range(range.clone()).collect::<String>();
|
let start = snapshot.point_to_buffer_offset(range.start);
|
||||||
|
let end = snapshot.point_to_buffer_offset(range.end);
|
||||||
|
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
|
||||||
|
let (start_buffer, start_buffer_offset) = start;
|
||||||
|
let (end_buffer, end_buffer_offset) = end;
|
||||||
|
if start_buffer.remote_id() == end_buffer.remote_id() {
|
||||||
|
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
|
||||||
|
} else {
|
||||||
|
self.finish_inline_assist(inline_assist_id, false, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.finish_inline_assist(inline_assist_id, false, cx);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let language = snapshot.language_at(range.start);
|
let language = buffer.language_at(range.start);
|
||||||
let language_name = if let Some(language) = language.as_ref() {
|
let language_name = if let Some(language) = language.as_ref() {
|
||||||
if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
|
if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
|
||||||
None
|
None
|
||||||
|
@ -555,96 +575,13 @@ impl AssistantPanel {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let codegen_kind = codegen.read(cx).kind().clone();
|
||||||
|
let user_prompt = user_prompt.to_string();
|
||||||
|
let prompt = cx.background().spawn(async move {
|
||||||
let language_name = language_name.as_deref();
|
let language_name = language_name.as_deref();
|
||||||
|
generate_content_prompt(user_prompt, language_name, &buffer, range, codegen_kind)
|
||||||
let mut prompt = String::new();
|
});
|
||||||
if let Some(language_name) = language_name {
|
|
||||||
writeln!(prompt, "You're an expert {language_name} engineer.").unwrap();
|
|
||||||
}
|
|
||||||
match pending_assist.codegen.read(cx).kind() {
|
|
||||||
CodegenKind::Transform { .. } => {
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"You're currently working inside an editor on this file:"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
if let Some(language_name) = language_name {
|
|
||||||
writeln!(prompt, "```{language_name}").unwrap();
|
|
||||||
} else {
|
|
||||||
writeln!(prompt, "```").unwrap();
|
|
||||||
}
|
|
||||||
for chunk in snapshot.text_for_range(Anchor::min()..Anchor::max()) {
|
|
||||||
write!(prompt, "{chunk}").unwrap();
|
|
||||||
}
|
|
||||||
writeln!(prompt, "```").unwrap();
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"In particular, the user has selected the following text:"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
if let Some(language_name) = language_name {
|
|
||||||
writeln!(prompt, "```{language_name}").unwrap();
|
|
||||||
} else {
|
|
||||||
writeln!(prompt, "```").unwrap();
|
|
||||||
}
|
|
||||||
writeln!(prompt, "{selected_text}").unwrap();
|
|
||||||
writeln!(prompt, "```").unwrap();
|
|
||||||
writeln!(prompt).unwrap();
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"Modify the selected text given the user prompt: {user_prompt}"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"You MUST reply only with the edited selected text, not the entire file."
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
CodegenKind::Generate { .. } => {
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"You're currently working inside an editor on this file:"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
if let Some(language_name) = language_name {
|
|
||||||
writeln!(prompt, "```{language_name}").unwrap();
|
|
||||||
} else {
|
|
||||||
writeln!(prompt, "```").unwrap();
|
|
||||||
}
|
|
||||||
for chunk in snapshot.text_for_range(Anchor::min()..range.start) {
|
|
||||||
write!(prompt, "{chunk}").unwrap();
|
|
||||||
}
|
|
||||||
write!(prompt, "<|>").unwrap();
|
|
||||||
for chunk in snapshot.text_for_range(range.start..Anchor::max()) {
|
|
||||||
write!(prompt, "{chunk}").unwrap();
|
|
||||||
}
|
|
||||||
writeln!(prompt).unwrap();
|
|
||||||
writeln!(prompt, "```").unwrap();
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"Assume the cursor is located where the `<|>` marker is."
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"Text can't be replaced, so assume your answer will be inserted at the cursor."
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
writeln!(
|
|
||||||
prompt,
|
|
||||||
"Complete the text given the user prompt: {user_prompt}"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(language_name) = language_name {
|
|
||||||
writeln!(prompt, "Your answer MUST always be valid {language_name}.").unwrap();
|
|
||||||
}
|
|
||||||
writeln!(prompt, "Always wrap your response in a Markdown codeblock.").unwrap();
|
|
||||||
writeln!(prompt, "Never make remarks about the output.").unwrap();
|
|
||||||
|
|
||||||
let mut messages = Vec::new();
|
let mut messages = Vec::new();
|
||||||
let mut model = settings::get::<AssistantSettings>(cx)
|
let mut model = settings::get::<AssistantSettings>(cx)
|
||||||
.default_open_ai_model
|
.default_open_ai_model
|
||||||
|
@ -660,6 +597,9 @@ impl AssistantPanel {
|
||||||
model = conversation.model.clone();
|
model = conversation.model.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
let prompt = prompt.await;
|
||||||
|
|
||||||
messages.push(RequestMessage {
|
messages.push(RequestMessage {
|
||||||
role: Role::User,
|
role: Role::User,
|
||||||
content: prompt,
|
content: prompt,
|
||||||
|
@ -669,9 +609,9 @@ impl AssistantPanel {
|
||||||
messages,
|
messages,
|
||||||
stream: true,
|
stream: true,
|
||||||
};
|
};
|
||||||
pending_assist
|
codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx));
|
||||||
.codegen
|
})
|
||||||
.update(cx, |codegen, cx| codegen.start(request, cx));
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_highlights_for_editor(
|
fn update_highlights_for_editor(
|
||||||
|
|
404
crates/assistant/src/prompts.rs
Normal file
404
crates/assistant/src/prompts.rs
Normal file
|
@ -0,0 +1,404 @@
|
||||||
|
use crate::codegen::CodegenKind;
|
||||||
|
use language::{BufferSnapshot, OffsetRangeExt, ToOffset};
|
||||||
|
use std::cmp::{self, Reverse};
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
fn summarize(buffer: &BufferSnapshot, selected_range: Range<impl ToOffset>) -> String {
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Match {
|
||||||
|
collapse: Range<usize>,
|
||||||
|
keep: Vec<Range<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let selected_range = selected_range.to_offset(buffer);
|
||||||
|
let mut ts_matches = buffer.matches(0..buffer.len(), |grammar| {
|
||||||
|
Some(&grammar.embedding_config.as_ref()?.query)
|
||||||
|
});
|
||||||
|
let configs = ts_matches
|
||||||
|
.grammars()
|
||||||
|
.iter()
|
||||||
|
.map(|g| g.embedding_config.as_ref().unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut matches = Vec::new();
|
||||||
|
while let Some(mat) = ts_matches.peek() {
|
||||||
|
let config = &configs[mat.grammar_index];
|
||||||
|
if let Some(collapse) = mat.captures.iter().find_map(|cap| {
|
||||||
|
if Some(cap.index) == config.collapse_capture_ix {
|
||||||
|
Some(cap.node.byte_range())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
let mut keep = Vec::new();
|
||||||
|
for capture in mat.captures.iter() {
|
||||||
|
if Some(capture.index) == config.keep_capture_ix {
|
||||||
|
keep.push(capture.node.byte_range());
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts_matches.advance();
|
||||||
|
matches.push(Match { collapse, keep });
|
||||||
|
} else {
|
||||||
|
ts_matches.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matches.sort_unstable_by_key(|mat| (mat.collapse.start, Reverse(mat.collapse.end)));
|
||||||
|
let mut matches = matches.into_iter().peekable();
|
||||||
|
|
||||||
|
let mut summary = String::new();
|
||||||
|
let mut offset = 0;
|
||||||
|
let mut flushed_selection = false;
|
||||||
|
while let Some(mat) = matches.next() {
|
||||||
|
// Keep extending the collapsed range if the next match surrounds
|
||||||
|
// the current one.
|
||||||
|
while let Some(next_mat) = matches.peek() {
|
||||||
|
if mat.collapse.start <= next_mat.collapse.start
|
||||||
|
&& mat.collapse.end >= next_mat.collapse.end
|
||||||
|
{
|
||||||
|
matches.next().unwrap();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset > mat.collapse.start {
|
||||||
|
// Skip collapsed nodes that have already been summarized.
|
||||||
|
offset = cmp::max(offset, mat.collapse.end);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset <= selected_range.start && selected_range.start <= mat.collapse.end {
|
||||||
|
if !flushed_selection {
|
||||||
|
// The collapsed node ends after the selection starts, so we'll flush the selection first.
|
||||||
|
summary.extend(buffer.text_for_range(offset..selected_range.start));
|
||||||
|
summary.push_str("<|START|");
|
||||||
|
if selected_range.end == selected_range.start {
|
||||||
|
summary.push_str(">");
|
||||||
|
} else {
|
||||||
|
summary.extend(buffer.text_for_range(selected_range.clone()));
|
||||||
|
summary.push_str("|END|>");
|
||||||
|
}
|
||||||
|
offset = selected_range.end;
|
||||||
|
flushed_selection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the selection intersects the collapsed node, we won't collapse it.
|
||||||
|
if selected_range.end >= mat.collapse.start {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.extend(buffer.text_for_range(offset..mat.collapse.start));
|
||||||
|
for keep in mat.keep {
|
||||||
|
summary.extend(buffer.text_for_range(keep));
|
||||||
|
}
|
||||||
|
offset = mat.collapse.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush selection if we haven't already done so.
|
||||||
|
if !flushed_selection && offset <= selected_range.start {
|
||||||
|
summary.extend(buffer.text_for_range(offset..selected_range.start));
|
||||||
|
summary.push_str("<|START|");
|
||||||
|
if selected_range.end == selected_range.start {
|
||||||
|
summary.push_str(">");
|
||||||
|
} else {
|
||||||
|
summary.extend(buffer.text_for_range(selected_range.clone()));
|
||||||
|
summary.push_str("|END|>");
|
||||||
|
}
|
||||||
|
offset = selected_range.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.extend(buffer.text_for_range(offset..buffer.len()));
|
||||||
|
summary
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_content_prompt(
|
||||||
|
user_prompt: String,
|
||||||
|
language_name: Option<&str>,
|
||||||
|
buffer: &BufferSnapshot,
|
||||||
|
range: Range<impl ToOffset>,
|
||||||
|
kind: CodegenKind,
|
||||||
|
) -> String {
|
||||||
|
let mut prompt = String::new();
|
||||||
|
|
||||||
|
// General Preamble
|
||||||
|
if let Some(language_name) = language_name {
|
||||||
|
writeln!(prompt, "You're an expert {language_name} engineer.\n").unwrap();
|
||||||
|
} else {
|
||||||
|
writeln!(prompt, "You're an expert engineer.\n").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let outline = summarize(buffer, range);
|
||||||
|
writeln!(
|
||||||
|
prompt,
|
||||||
|
"The file you are currently working on has the following outline:"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
if let Some(language_name) = language_name {
|
||||||
|
let language_name = language_name.to_lowercase();
|
||||||
|
writeln!(prompt, "```{language_name}\n{outline}\n```").unwrap();
|
||||||
|
} else {
|
||||||
|
writeln!(prompt, "```\n{outline}\n```").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
match kind {
|
||||||
|
CodegenKind::Generate { position: _ } => {
|
||||||
|
writeln!(prompt, "In particular, the user's cursor is current on the '<|START|>' span in the above outline, with no text selected.").unwrap();
|
||||||
|
writeln!(
|
||||||
|
prompt,
|
||||||
|
"Assume the cursor is located where the `<|START|` marker is."
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
writeln!(
|
||||||
|
prompt,
|
||||||
|
"Text can't be replaced, so assume your answer will be inserted at the cursor."
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
writeln!(
|
||||||
|
prompt,
|
||||||
|
"Generate text based on the users prompt: {user_prompt}"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
CodegenKind::Transform { range: _ } => {
|
||||||
|
writeln!(prompt, "In particular, the user has selected a section of the text between the '<|START|' and '|END|>' spans.").unwrap();
|
||||||
|
writeln!(
|
||||||
|
prompt,
|
||||||
|
"Modify the users code selected text based upon the users prompt: {user_prompt}"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
writeln!(
|
||||||
|
prompt,
|
||||||
|
"You MUST reply with only the adjusted code (within the '<|START|' and '|END|>' spans), not the entire file."
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(language_name) = language_name {
|
||||||
|
writeln!(prompt, "Your answer MUST always be valid {language_name}").unwrap();
|
||||||
|
}
|
||||||
|
writeln!(prompt, "Always wrap your response in a Markdown codeblock").unwrap();
|
||||||
|
writeln!(prompt, "Never make remarks about the output.").unwrap();
|
||||||
|
|
||||||
|
prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use gpui::AppContext;
|
||||||
|
use indoc::indoc;
|
||||||
|
use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
|
||||||
|
use settings::SettingsStore;
|
||||||
|
|
||||||
|
pub(crate) fn rust_lang() -> Language {
|
||||||
|
Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
name: "Rust".into(),
|
||||||
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::language()),
|
||||||
|
)
|
||||||
|
.with_embedding_query(
|
||||||
|
r#"
|
||||||
|
(
|
||||||
|
[(line_comment) (attribute_item)]* @context
|
||||||
|
.
|
||||||
|
[
|
||||||
|
(struct_item
|
||||||
|
name: (_) @name)
|
||||||
|
|
||||||
|
(enum_item
|
||||||
|
name: (_) @name)
|
||||||
|
|
||||||
|
(impl_item
|
||||||
|
trait: (_)? @name
|
||||||
|
"for"? @name
|
||||||
|
type: (_) @name)
|
||||||
|
|
||||||
|
(trait_item
|
||||||
|
name: (_) @name)
|
||||||
|
|
||||||
|
(function_item
|
||||||
|
name: (_) @name
|
||||||
|
body: (block
|
||||||
|
"{" @keep
|
||||||
|
"}" @keep) @collapse)
|
||||||
|
|
||||||
|
(macro_definition
|
||||||
|
name: (_) @name)
|
||||||
|
] @item
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_outline_for_prompt(cx: &mut AppContext) {
|
||||||
|
cx.set_global(SettingsStore::test(cx));
|
||||||
|
language_settings::init(cx);
|
||||||
|
let text = indoc! {"
|
||||||
|
struct X {
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl X {
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
let a = 1;
|
||||||
|
let b = 2;
|
||||||
|
Self { a, b }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a(&self, param: bool) -> usize {
|
||||||
|
self.a
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn b(&self) -> usize {
|
||||||
|
self.b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
let buffer =
|
||||||
|
cx.add_model(|cx| Buffer::new(0, 0, text).with_language(Arc::new(rust_lang()), cx));
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
summarize(&snapshot, Point::new(1, 4)..Point::new(1, 4)),
|
||||||
|
indoc! {"
|
||||||
|
struct X {
|
||||||
|
<|START|>a: usize,
|
||||||
|
b: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl X {
|
||||||
|
|
||||||
|
fn new() -> Self {}
|
||||||
|
|
||||||
|
pub fn a(&self, param: bool) -> usize {}
|
||||||
|
|
||||||
|
pub fn b(&self) -> usize {}
|
||||||
|
}
|
||||||
|
"}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
summarize(&snapshot, Point::new(8, 12)..Point::new(8, 14)),
|
||||||
|
indoc! {"
|
||||||
|
struct X {
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl X {
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
let <|START|a |END|>= 1;
|
||||||
|
let b = 2;
|
||||||
|
Self { a, b }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a(&self, param: bool) -> usize {}
|
||||||
|
|
||||||
|
pub fn b(&self) -> usize {}
|
||||||
|
}
|
||||||
|
"}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
summarize(&snapshot, Point::new(6, 0)..Point::new(6, 0)),
|
||||||
|
indoc! {"
|
||||||
|
struct X {
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl X {
|
||||||
|
<|START|>
|
||||||
|
fn new() -> Self {}
|
||||||
|
|
||||||
|
pub fn a(&self, param: bool) -> usize {}
|
||||||
|
|
||||||
|
pub fn b(&self) -> usize {}
|
||||||
|
}
|
||||||
|
"}
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
summarize(&snapshot, Point::new(21, 0)..Point::new(21, 0)),
|
||||||
|
indoc! {"
|
||||||
|
struct X {
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl X {
|
||||||
|
|
||||||
|
fn new() -> Self {}
|
||||||
|
|
||||||
|
pub fn a(&self, param: bool) -> usize {}
|
||||||
|
|
||||||
|
pub fn b(&self) -> usize {}
|
||||||
|
}
|
||||||
|
<|START|>"}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure nested functions get collapsed properly.
|
||||||
|
let text = indoc! {"
|
||||||
|
struct X {
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl X {
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
let a = 1;
|
||||||
|
let b = 2;
|
||||||
|
Self { a, b }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a(&self, param: bool) -> usize {
|
||||||
|
let a = 30;
|
||||||
|
fn nested() -> usize {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
self.a + nested()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn b(&self) -> usize {
|
||||||
|
self.b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"};
|
||||||
|
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
assert_eq!(
|
||||||
|
summarize(&snapshot, Point::new(0, 0)..Point::new(0, 0)),
|
||||||
|
indoc! {"
|
||||||
|
<|START|>struct X {
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl X {
|
||||||
|
|
||||||
|
fn new() -> Self {}
|
||||||
|
|
||||||
|
pub fn a(&self, param: bool) -> usize {}
|
||||||
|
|
||||||
|
pub fn b(&self) -> usize {}
|
||||||
|
}
|
||||||
|
"}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -115,13 +115,15 @@ pub fn check(_: &Check, cx: &mut AppContext) {
|
||||||
|
|
||||||
fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
|
fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
|
||||||
if let Some(auto_updater) = AutoUpdater::get(cx) {
|
if let Some(auto_updater) = AutoUpdater::get(cx) {
|
||||||
let server_url = &auto_updater.read(cx).server_url;
|
let auto_updater = auto_updater.read(cx);
|
||||||
|
let server_url = &auto_updater.server_url;
|
||||||
|
let current_version = auto_updater.current_version;
|
||||||
let latest_release_url = if cx.has_global::<ReleaseChannel>()
|
let latest_release_url = if cx.has_global::<ReleaseChannel>()
|
||||||
&& *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview
|
&& *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview
|
||||||
{
|
{
|
||||||
format!("{server_url}/releases/preview/latest")
|
format!("{server_url}/releases/preview/{current_version}")
|
||||||
} else {
|
} else {
|
||||||
format!("{server_url}/releases/stable/latest")
|
format!("{server_url}/releases/stable/{current_version}")
|
||||||
};
|
};
|
||||||
cx.platform().open_url(&latest_release_url);
|
cx.platform().open_url(&latest_release_url);
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,10 +291,10 @@ impl ActiveCall {
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<ModelHandle<Room>>> {
|
||||||
if let Some(room) = self.room().cloned() {
|
if let Some(room) = self.room().cloned() {
|
||||||
if room.read(cx).channel_id() == Some(channel_id) {
|
if room.read(cx).channel_id() == Some(channel_id) {
|
||||||
return Task::ready(Ok(()));
|
return Task::ready(Ok(room));
|
||||||
} else {
|
} else {
|
||||||
room.update(cx, |room, cx| room.clear_state(cx));
|
room.update(cx, |room, cx| room.clear_state(cx));
|
||||||
}
|
}
|
||||||
|
@ -309,7 +309,7 @@ impl ActiveCall {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.report_call_event("join channel", cx)
|
this.report_call_event("join channel", cx)
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(room)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,12 @@ pub enum Event {
|
||||||
RemoteProjectUnshared {
|
RemoteProjectUnshared {
|
||||||
project_id: u64,
|
project_id: u64,
|
||||||
},
|
},
|
||||||
|
RemoteProjectJoined {
|
||||||
|
project_id: u64,
|
||||||
|
},
|
||||||
|
RemoteProjectInvitationDiscarded {
|
||||||
|
project_id: u64,
|
||||||
|
},
|
||||||
Left,
|
Left,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,6 +594,33 @@ impl Room {
|
||||||
.map_or(&[], |v| v.as_slice())
|
.map_or(&[], |v| v.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// projects_to_join returns a list of shared projects sorted such
|
||||||
|
/// that the most 'active' projects appear last.
|
||||||
|
pub fn projects_to_join(&self) -> Vec<(u64, u64)> {
|
||||||
|
let mut projects = HashMap::default();
|
||||||
|
let mut hosts = HashMap::default();
|
||||||
|
for participant in self.remote_participants.values() {
|
||||||
|
match participant.location {
|
||||||
|
ParticipantLocation::SharedProject { project_id } => {
|
||||||
|
*projects.entry(project_id).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
ParticipantLocation::External | ParticipantLocation::UnsharedProject => {}
|
||||||
|
}
|
||||||
|
for project in &participant.projects {
|
||||||
|
*projects.entry(project.id).or_insert(0) += 1;
|
||||||
|
hosts.insert(project.id, participant.user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pairs: Vec<(u64, usize)> = projects.into_iter().collect();
|
||||||
|
pairs.sort_by_key(|(_, count)| 0 - *count as i32);
|
||||||
|
|
||||||
|
pairs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(project_id, _)| (project_id, hosts[&project_id]))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_room_updated(
|
async fn handle_room_updated(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
envelope: TypedEnvelope<proto::RoomUpdated>,
|
envelope: TypedEnvelope<proto::RoomUpdated>,
|
||||||
|
@ -1015,6 +1048,7 @@ impl Room {
|
||||||
) -> Task<Result<ModelHandle<Project>>> {
|
) -> Task<Result<ModelHandle<Project>>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
|
cx.emit(Event::RemoteProjectJoined { project_id: id });
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let project =
|
let project =
|
||||||
Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
|
Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
|
||||||
|
|
|
@ -33,15 +33,16 @@ parking_lot.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_derive.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
sysinfo.workspace = true
|
||||||
|
tempfile = "3"
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
tiny_http = "0.8"
|
tiny_http = "0.8"
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
serde.workspace = true
|
|
||||||
serde_derive.workspace = true
|
|
||||||
tempfile = "3"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
|
|
|
@ -4,6 +4,7 @@ use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
|
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||||
|
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use util::http::HttpClient;
|
use util::http::HttpClient;
|
||||||
use util::{channel::ReleaseChannel, TryFutureExt};
|
use util::{channel::ReleaseChannel, TryFutureExt};
|
||||||
|
@ -88,6 +89,14 @@ pub enum ClickhouseEvent {
|
||||||
kind: AssistantKind,
|
kind: AssistantKind,
|
||||||
model: &'static str,
|
model: &'static str,
|
||||||
},
|
},
|
||||||
|
Cpu {
|
||||||
|
usage_as_percentage: f32,
|
||||||
|
core_count: u32,
|
||||||
|
},
|
||||||
|
Memory {
|
||||||
|
memory_in_bytes: u64,
|
||||||
|
virtual_memory_in_bytes: u64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -136,7 +145,7 @@ impl Telemetry {
|
||||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(self: &Arc<Self>, installation_id: Option<String>) {
|
pub fn start(self: &Arc<Self>, installation_id: Option<String>, cx: &mut AppContext) {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
state.installation_id = installation_id.map(|id| id.into());
|
state.installation_id = installation_id.map(|id| id.into());
|
||||||
let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
|
let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
|
||||||
|
@ -145,6 +154,46 @@ impl Telemetry {
|
||||||
if has_clickhouse_events {
|
if has_clickhouse_events {
|
||||||
self.flush_clickhouse_events();
|
self.flush_clickhouse_events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let this = self.clone();
|
||||||
|
cx.spawn(|mut cx| async move {
|
||||||
|
let mut system = System::new_all();
|
||||||
|
system.refresh_all();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Waiting some amount of time before the first query is important to get a reasonable value
|
||||||
|
// https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage
|
||||||
|
const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(60);
|
||||||
|
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
|
||||||
|
|
||||||
|
system.refresh_memory();
|
||||||
|
system.refresh_processes();
|
||||||
|
|
||||||
|
let current_process = Pid::from_u32(std::process::id());
|
||||||
|
let Some(process) = system.processes().get(¤t_process) else {
|
||||||
|
let process = current_process;
|
||||||
|
log::error!("Failed to find own process {process:?} in system process table");
|
||||||
|
// TODO: Fire an error telemetry event
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let memory_event = ClickhouseEvent::Memory {
|
||||||
|
memory_in_bytes: process.memory(),
|
||||||
|
virtual_memory_in_bytes: process.virtual_memory(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let cpu_event = ClickhouseEvent::Cpu {
|
||||||
|
usage_as_percentage: process.cpu_usage(),
|
||||||
|
core_count: system.cpus().len() as u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
let telemetry_settings = cx.update(|cx| *settings::get::<TelemetrySettings>(cx));
|
||||||
|
|
||||||
|
this.report_clickhouse_event(memory_event, telemetry_settings);
|
||||||
|
this.report_clickhouse_event(cpu_event, telemetry_settings);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_authenticated_user_info(
|
pub fn set_authenticated_user_info(
|
||||||
|
|
|
@ -595,6 +595,10 @@ impl UserStore {
|
||||||
self.load_users(proto::FuzzySearchUsers { query }, cx)
|
self.load_users(proto::FuzzySearchUsers { query }, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_cached_user(&self, user_id: u64) -> Option<Arc<User>> {
|
||||||
|
self.users.get(&user_id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_user(
|
pub fn get_user(
|
||||||
&mut self,
|
&mut self,
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
|
|
|
@ -4,6 +4,7 @@ use gpui::{ModelHandle, TestAppContext};
|
||||||
mod channel_buffer_tests;
|
mod channel_buffer_tests;
|
||||||
mod channel_message_tests;
|
mod channel_message_tests;
|
||||||
mod channel_tests;
|
mod channel_tests;
|
||||||
|
mod following_tests;
|
||||||
mod integration_tests;
|
mod integration_tests;
|
||||||
mod random_channel_buffer_tests;
|
mod random_channel_buffer_tests;
|
||||||
mod random_project_collaboration_tests;
|
mod random_project_collaboration_tests;
|
||||||
|
|
|
@ -706,9 +706,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
||||||
// Client B follows client A.
|
// Client B follows client A.
|
||||||
workspace_b
|
workspace_b
|
||||||
.update(cx_b, |workspace, cx| {
|
.update(cx_b, |workspace, cx| {
|
||||||
workspace
|
workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
|
||||||
.toggle_follow(client_a.peer_id().unwrap(), cx)
|
|
||||||
.unwrap()
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
1306
crates/collab/src/tests/following_tests.rs
Normal file
1306
crates/collab/src/tests/following_tests.rs
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -47,7 +47,7 @@ use util::{iife, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel},
|
dock::{DockPosition, Panel},
|
||||||
item::ItemHandle,
|
item::ItemHandle,
|
||||||
Workspace,
|
FollowNextCollaborator, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
@ -95,6 +95,11 @@ pub struct JoinChannelCall {
|
||||||
pub channel_id: u64,
|
pub channel_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct JoinChannelChat {
|
||||||
|
pub channel_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
struct StartMoveChannelFor {
|
struct StartMoveChannelFor {
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
|
@ -151,6 +156,7 @@ impl_actions!(
|
||||||
ToggleCollapse,
|
ToggleCollapse,
|
||||||
OpenChannelNotes,
|
OpenChannelNotes,
|
||||||
JoinChannelCall,
|
JoinChannelCall,
|
||||||
|
JoinChannelChat,
|
||||||
LinkChannel,
|
LinkChannel,
|
||||||
StartMoveChannelFor,
|
StartMoveChannelFor,
|
||||||
StartLinkChannelFor,
|
StartLinkChannelFor,
|
||||||
|
@ -198,6 +204,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(CollabPanel::collapse_selected_channel);
|
cx.add_action(CollabPanel::collapse_selected_channel);
|
||||||
cx.add_action(CollabPanel::expand_selected_channel);
|
cx.add_action(CollabPanel::expand_selected_channel);
|
||||||
cx.add_action(CollabPanel::open_channel_notes);
|
cx.add_action(CollabPanel::open_channel_notes);
|
||||||
|
cx.add_action(CollabPanel::join_channel_chat);
|
||||||
|
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
|
|panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
|
||||||
|
@ -404,6 +411,7 @@ enum ListEntry {
|
||||||
Header(Section),
|
Header(Section),
|
||||||
CallParticipant {
|
CallParticipant {
|
||||||
user: Arc<User>,
|
user: Arc<User>,
|
||||||
|
peer_id: Option<PeerId>,
|
||||||
is_pending: bool,
|
is_pending: bool,
|
||||||
},
|
},
|
||||||
ParticipantProject {
|
ParticipantProject {
|
||||||
|
@ -470,6 +478,12 @@ impl CollabPanel {
|
||||||
.iter()
|
.iter()
|
||||||
.position(|entry| !matches!(entry, ListEntry::Header(_)));
|
.position(|entry| !matches!(entry, ListEntry::Header(_)));
|
||||||
}
|
}
|
||||||
|
} else if let editor::Event::Blurred = event {
|
||||||
|
let query = this.filter_editor.read(cx).text(cx);
|
||||||
|
if query.is_empty() {
|
||||||
|
this.selection.take();
|
||||||
|
this.update_entries(true, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
@ -508,14 +522,19 @@ impl CollabPanel {
|
||||||
let is_collapsed = this.collapsed_sections.contains(section);
|
let is_collapsed = this.collapsed_sections.contains(section);
|
||||||
this.render_header(*section, &theme, is_selected, is_collapsed, cx)
|
this.render_header(*section, &theme, is_selected, is_collapsed, cx)
|
||||||
}
|
}
|
||||||
ListEntry::CallParticipant { user, is_pending } => {
|
ListEntry::CallParticipant {
|
||||||
Self::render_call_participant(
|
|
||||||
user,
|
user,
|
||||||
|
peer_id,
|
||||||
|
is_pending,
|
||||||
|
} => Self::render_call_participant(
|
||||||
|
user,
|
||||||
|
*peer_id,
|
||||||
|
this.user_store.clone(),
|
||||||
*is_pending,
|
*is_pending,
|
||||||
is_selected,
|
is_selected,
|
||||||
&theme.collab_panel,
|
&theme,
|
||||||
)
|
cx,
|
||||||
}
|
),
|
||||||
ListEntry::ParticipantProject {
|
ListEntry::ParticipantProject {
|
||||||
project_id,
|
project_id,
|
||||||
worktree_root_names,
|
worktree_root_names,
|
||||||
|
@ -528,7 +547,7 @@ impl CollabPanel {
|
||||||
Some(*project_id) == current_project_id,
|
Some(*project_id) == current_project_id,
|
||||||
*is_last,
|
*is_last,
|
||||||
is_selected,
|
is_selected,
|
||||||
&theme.collab_panel,
|
&theme,
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
ListEntry::ParticipantScreen { peer_id, is_last } => {
|
ListEntry::ParticipantScreen { peer_id, is_last } => {
|
||||||
|
@ -549,7 +568,7 @@ impl CollabPanel {
|
||||||
&*channel,
|
&*channel,
|
||||||
*depth,
|
*depth,
|
||||||
path.to_owned(),
|
path.to_owned(),
|
||||||
&theme.collab_panel,
|
&theme,
|
||||||
is_selected,
|
is_selected,
|
||||||
ix,
|
ix,
|
||||||
cx,
|
cx,
|
||||||
|
@ -602,7 +621,7 @@ impl CollabPanel {
|
||||||
contact,
|
contact,
|
||||||
*calling,
|
*calling,
|
||||||
&this.project,
|
&this.project,
|
||||||
&theme.collab_panel,
|
&theme,
|
||||||
is_selected,
|
is_selected,
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
|
@ -762,9 +781,16 @@ impl CollabPanel {
|
||||||
|
|
||||||
let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
|
let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
|
||||||
let old_entries = mem::take(&mut self.entries);
|
let old_entries = mem::take(&mut self.entries);
|
||||||
|
let mut scroll_to_top = false;
|
||||||
|
|
||||||
if let Some(room) = ActiveCall::global(cx).read(cx).room() {
|
if let Some(room) = ActiveCall::global(cx).read(cx).room() {
|
||||||
self.entries.push(ListEntry::Header(Section::ActiveCall));
|
self.entries.push(ListEntry::Header(Section::ActiveCall));
|
||||||
|
if !old_entries
|
||||||
|
.iter()
|
||||||
|
.any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
|
||||||
|
{
|
||||||
|
scroll_to_top = true;
|
||||||
|
}
|
||||||
|
|
||||||
if !self.collapsed_sections.contains(&Section::ActiveCall) {
|
if !self.collapsed_sections.contains(&Section::ActiveCall) {
|
||||||
let room = room.read(cx);
|
let room = room.read(cx);
|
||||||
|
@ -793,6 +819,7 @@ impl CollabPanel {
|
||||||
let user_id = user.id;
|
let user_id = user.id;
|
||||||
self.entries.push(ListEntry::CallParticipant {
|
self.entries.push(ListEntry::CallParticipant {
|
||||||
user,
|
user,
|
||||||
|
peer_id: None,
|
||||||
is_pending: false,
|
is_pending: false,
|
||||||
});
|
});
|
||||||
let mut projects = room.local_participant().projects.iter().peekable();
|
let mut projects = room.local_participant().projects.iter().peekable();
|
||||||
|
@ -830,6 +857,7 @@ impl CollabPanel {
|
||||||
let participant = &room.remote_participants()[&user_id];
|
let participant = &room.remote_participants()[&user_id];
|
||||||
self.entries.push(ListEntry::CallParticipant {
|
self.entries.push(ListEntry::CallParticipant {
|
||||||
user: participant.user.clone(),
|
user: participant.user.clone(),
|
||||||
|
peer_id: Some(participant.peer_id),
|
||||||
is_pending: false,
|
is_pending: false,
|
||||||
});
|
});
|
||||||
let mut projects = participant.projects.iter().peekable();
|
let mut projects = participant.projects.iter().peekable();
|
||||||
|
@ -871,6 +899,7 @@ impl CollabPanel {
|
||||||
self.entries
|
self.entries
|
||||||
.extend(matches.iter().map(|mat| ListEntry::CallParticipant {
|
.extend(matches.iter().map(|mat| ListEntry::CallParticipant {
|
||||||
user: room.pending_participants()[mat.candidate_id].clone(),
|
user: room.pending_participants()[mat.candidate_id].clone(),
|
||||||
|
peer_id: None,
|
||||||
is_pending: true,
|
is_pending: true,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -1129,8 +1158,12 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_scroll_top = self.list_state.logical_scroll_top();
|
let old_scroll_top = self.list_state.logical_scroll_top();
|
||||||
|
|
||||||
self.list_state.reset(self.entries.len());
|
self.list_state.reset(self.entries.len());
|
||||||
|
|
||||||
|
if scroll_to_top {
|
||||||
|
self.list_state.scroll_to(ListOffset::default());
|
||||||
|
} else {
|
||||||
// Attempt to maintain the same scroll position.
|
// Attempt to maintain the same scroll position.
|
||||||
if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
|
if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
|
||||||
let new_scroll_top = self
|
let new_scroll_top = self
|
||||||
|
@ -1168,52 +1201,104 @@ impl CollabPanel {
|
||||||
self.list_state
|
self.list_state
|
||||||
.scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
|
.scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_call_participant(
|
fn render_call_participant(
|
||||||
user: &User,
|
user: &User,
|
||||||
|
peer_id: Option<PeerId>,
|
||||||
|
user_store: ModelHandle<UserStore>,
|
||||||
is_pending: bool,
|
is_pending: bool,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
theme: &theme::CollabPanel,
|
theme: &theme::Theme,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
) -> AnyElement<Self> {
|
) -> AnyElement<Self> {
|
||||||
|
enum CallParticipant {}
|
||||||
|
enum CallParticipantTooltip {}
|
||||||
|
|
||||||
|
let collab_theme = &theme.collab_panel;
|
||||||
|
|
||||||
|
let is_current_user =
|
||||||
|
user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
|
||||||
|
|
||||||
|
let content =
|
||||||
|
MouseEventHandler::new::<CallParticipant, _>(user.id as usize, cx, |mouse_state, _| {
|
||||||
|
let style = if is_current_user {
|
||||||
|
*collab_theme
|
||||||
|
.contact_row
|
||||||
|
.in_state(is_selected)
|
||||||
|
.style_for(&mut Default::default())
|
||||||
|
} else {
|
||||||
|
*collab_theme
|
||||||
|
.contact_row
|
||||||
|
.in_state(is_selected)
|
||||||
|
.style_for(mouse_state)
|
||||||
|
};
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_children(user.avatar.clone().map(|avatar| {
|
.with_children(user.avatar.clone().map(|avatar| {
|
||||||
Image::from_data(avatar)
|
Image::from_data(avatar)
|
||||||
.with_style(theme.contact_avatar)
|
.with_style(collab_theme.contact_avatar)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
}))
|
}))
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(
|
Label::new(
|
||||||
user.github_login.clone(),
|
user.github_login.clone(),
|
||||||
theme.contact_username.text.clone(),
|
collab_theme.contact_username.text.clone(),
|
||||||
)
|
)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.contact_username.container)
|
.with_style(collab_theme.contact_username.container)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.flex(1., true),
|
.flex(1., true),
|
||||||
)
|
)
|
||||||
.with_children(if is_pending {
|
.with_children(if is_pending {
|
||||||
Some(
|
Some(
|
||||||
Label::new("Calling", theme.calling_indicator.text.clone())
|
Label::new("Calling", collab_theme.calling_indicator.text.clone())
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.calling_indicator.container)
|
.with_style(collab_theme.calling_indicator.container)
|
||||||
|
.aligned(),
|
||||||
|
)
|
||||||
|
} else if is_current_user {
|
||||||
|
Some(
|
||||||
|
Label::new("You", collab_theme.calling_indicator.text.clone())
|
||||||
|
.contained()
|
||||||
|
.with_style(collab_theme.calling_indicator.container)
|
||||||
.aligned(),
|
.aligned(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(theme.row_height)
|
.with_height(collab_theme.row_height)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(
|
.with_style(style)
|
||||||
*theme
|
});
|
||||||
.contact_row
|
|
||||||
.in_state(is_selected)
|
if is_current_user || is_pending || peer_id.is_none() {
|
||||||
.style_for(&mut Default::default()),
|
return content.into_any();
|
||||||
|
}
|
||||||
|
|
||||||
|
let tooltip = format!("Follow {}", user.github_login);
|
||||||
|
|
||||||
|
content
|
||||||
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
if let Some(workspace) = this.workspace.upgrade(cx) {
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, cx| workspace.follow(peer_id.unwrap(), cx))
|
||||||
|
.map(|task| task.detach_and_log_err(cx));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.with_tooltip::<CallParticipantTooltip>(
|
||||||
|
user.id as usize,
|
||||||
|
tooltip,
|
||||||
|
Some(Box::new(FollowNextCollaborator)),
|
||||||
|
theme.tooltip.clone(),
|
||||||
|
cx,
|
||||||
)
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
@ -1225,48 +1310,58 @@ impl CollabPanel {
|
||||||
is_current: bool,
|
is_current: bool,
|
||||||
is_last: bool,
|
is_last: bool,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
theme: &theme::CollabPanel,
|
theme: &theme::Theme,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> AnyElement<Self> {
|
) -> AnyElement<Self> {
|
||||||
enum JoinProject {}
|
enum JoinProject {}
|
||||||
|
enum JoinProjectTooltip {}
|
||||||
|
|
||||||
let host_avatar_width = theme
|
let collab_theme = &theme.collab_panel;
|
||||||
|
let host_avatar_width = collab_theme
|
||||||
.contact_avatar
|
.contact_avatar
|
||||||
.width
|
.width
|
||||||
.or(theme.contact_avatar.height)
|
.or(collab_theme.contact_avatar.height)
|
||||||
.unwrap_or(0.);
|
.unwrap_or(0.);
|
||||||
let tree_branch = theme.tree_branch;
|
let tree_branch = collab_theme.tree_branch;
|
||||||
let project_name = if worktree_root_names.is_empty() {
|
let project_name = if worktree_root_names.is_empty() {
|
||||||
"untitled".to_string()
|
"untitled".to_string()
|
||||||
} else {
|
} else {
|
||||||
worktree_root_names.join(", ")
|
worktree_root_names.join(", ")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let content =
|
||||||
MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
|
MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
|
||||||
let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
|
let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
|
||||||
let row = theme
|
let row = if is_current {
|
||||||
|
collab_theme
|
||||||
|
.project_row
|
||||||
|
.in_state(true)
|
||||||
|
.style_for(&mut Default::default())
|
||||||
|
} else {
|
||||||
|
collab_theme
|
||||||
.project_row
|
.project_row
|
||||||
.in_state(is_selected)
|
.in_state(is_selected)
|
||||||
.style_for(mouse_state);
|
.style_for(mouse_state)
|
||||||
|
};
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(render_tree_branch(
|
.with_child(render_tree_branch(
|
||||||
tree_branch,
|
tree_branch,
|
||||||
&row.name.text,
|
&row.name.text,
|
||||||
is_last,
|
is_last,
|
||||||
vec2f(host_avatar_width, theme.row_height),
|
vec2f(host_avatar_width, collab_theme.row_height),
|
||||||
cx.font_cache(),
|
cx.font_cache(),
|
||||||
))
|
))
|
||||||
.with_child(
|
.with_child(
|
||||||
Svg::new("icons/file_icons/folder.svg")
|
Svg::new("icons/file_icons/folder.svg")
|
||||||
.with_color(theme.channel_hash.color)
|
.with_color(collab_theme.channel_hash.color)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(theme.channel_hash.width)
|
.with_width(collab_theme.channel_hash.width)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left(),
|
.left(),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(project_name, row.name.text.clone())
|
Label::new(project_name.clone(), row.name.text.clone())
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.contained()
|
.contained()
|
||||||
|
@ -1274,24 +1369,31 @@ impl CollabPanel {
|
||||||
.flex(1., false),
|
.flex(1., false),
|
||||||
)
|
)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(theme.row_height)
|
.with_height(collab_theme.row_height)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(row.container)
|
.with_style(row.container)
|
||||||
})
|
});
|
||||||
.with_cursor_style(if !is_current {
|
|
||||||
CursorStyle::PointingHand
|
if is_current {
|
||||||
} else {
|
return content.into_any();
|
||||||
CursorStyle::Arrow
|
}
|
||||||
})
|
|
||||||
|
content
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
if !is_current {
|
|
||||||
if let Some(workspace) = this.workspace.upgrade(cx) {
|
if let Some(workspace) = this.workspace.upgrade(cx) {
|
||||||
let app_state = workspace.read(cx).app_state().clone();
|
let app_state = workspace.read(cx).app_state().clone();
|
||||||
workspace::join_remote_project(project_id, host_user_id, app_state, cx)
|
workspace::join_remote_project(project_id, host_user_id, app_state, cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
.with_tooltip::<JoinProjectTooltip>(
|
||||||
|
project_id as usize,
|
||||||
|
format!("Open {}", project_name),
|
||||||
|
None,
|
||||||
|
theme.tooltip.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1556,16 +1658,20 @@ impl CollabPanel {
|
||||||
contact: &Contact,
|
contact: &Contact,
|
||||||
calling: bool,
|
calling: bool,
|
||||||
project: &ModelHandle<Project>,
|
project: &ModelHandle<Project>,
|
||||||
theme: &theme::CollabPanel,
|
theme: &theme::Theme,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> AnyElement<Self> {
|
) -> AnyElement<Self> {
|
||||||
|
enum ContactTooltip {}
|
||||||
|
|
||||||
|
let collab_theme = &theme.collab_panel;
|
||||||
let online = contact.online;
|
let online = contact.online;
|
||||||
let busy = contact.busy || calling;
|
let busy = contact.busy || calling;
|
||||||
let user_id = contact.user.id;
|
let user_id = contact.user.id;
|
||||||
let github_login = contact.user.github_login.clone();
|
let github_login = contact.user.github_login.clone();
|
||||||
let initial_project = project.clone();
|
let initial_project = project.clone();
|
||||||
let mut event_handler =
|
|
||||||
|
let event_handler =
|
||||||
MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
|
MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_children(contact.user.avatar.clone().map(|avatar| {
|
.with_children(contact.user.avatar.clone().map(|avatar| {
|
||||||
|
@ -1575,9 +1681,9 @@ impl CollabPanel {
|
||||||
.collapsed()
|
.collapsed()
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(if busy {
|
.with_style(if busy {
|
||||||
theme.contact_status_busy
|
collab_theme.contact_status_busy
|
||||||
} else {
|
} else {
|
||||||
theme.contact_status_free
|
collab_theme.contact_status_free
|
||||||
})
|
})
|
||||||
.aligned(),
|
.aligned(),
|
||||||
)
|
)
|
||||||
|
@ -1587,7 +1693,7 @@ impl CollabPanel {
|
||||||
Stack::new()
|
Stack::new()
|
||||||
.with_child(
|
.with_child(
|
||||||
Image::from_data(avatar)
|
Image::from_data(avatar)
|
||||||
.with_style(theme.contact_avatar)
|
.with_style(collab_theme.contact_avatar)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left(),
|
.left(),
|
||||||
)
|
)
|
||||||
|
@ -1596,20 +1702,22 @@ impl CollabPanel {
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(
|
Label::new(
|
||||||
contact.user.github_login.clone(),
|
contact.user.github_login.clone(),
|
||||||
theme.contact_username.text.clone(),
|
collab_theme.contact_username.text.clone(),
|
||||||
)
|
)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.contact_username.container)
|
.with_style(collab_theme.contact_username.container)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.flex(1., true),
|
.flex(1., true),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_children(if state.hovered() {
|
||||||
|
Some(
|
||||||
MouseEventHandler::new::<Cancel, _>(
|
MouseEventHandler::new::<Cancel, _>(
|
||||||
contact.user.id as usize,
|
contact.user.id as usize,
|
||||||
cx,
|
cx,
|
||||||
|mouse_state, _| {
|
|mouse_state, _| {
|
||||||
let button_style = theme.contact_button.style_for(mouse_state);
|
let button_style =
|
||||||
|
collab_theme.contact_button.style_for(mouse_state);
|
||||||
render_icon_button(button_style, "icons/x.svg")
|
render_icon_button(button_style, "icons/x.svg")
|
||||||
.aligned()
|
.aligned()
|
||||||
.flex_float()
|
.flex_float()
|
||||||
|
@ -1622,32 +1730,66 @@ impl CollabPanel {
|
||||||
})
|
})
|
||||||
.flex_float(),
|
.flex_float(),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
.with_children(if calling {
|
.with_children(if calling {
|
||||||
Some(
|
Some(
|
||||||
Label::new("Calling", theme.calling_indicator.text.clone())
|
Label::new("Calling", collab_theme.calling_indicator.text.clone())
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.calling_indicator.container)
|
.with_style(collab_theme.calling_indicator.container)
|
||||||
.aligned(),
|
.aligned(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(theme.row_height)
|
.with_height(collab_theme.row_height)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(*theme.contact_row.in_state(is_selected).style_for(state))
|
.with_style(
|
||||||
})
|
*collab_theme
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
.contact_row
|
||||||
if online && !busy {
|
.in_state(is_selected)
|
||||||
this.call(user_id, Some(initial_project.clone()), cx);
|
.style_for(state),
|
||||||
}
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if online {
|
if online && !busy {
|
||||||
event_handler = event_handler.with_cursor_style(CursorStyle::PointingHand);
|
let room = ActiveCall::global(cx).read(cx).room();
|
||||||
}
|
let label = if room.is_some() {
|
||||||
|
format!("Invite {} to join call", contact.user.github_login)
|
||||||
|
} else {
|
||||||
|
format!("Call {}", contact.user.github_login)
|
||||||
|
};
|
||||||
|
|
||||||
event_handler.into_any()
|
event_handler
|
||||||
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
this.call(user_id, Some(initial_project.clone()), cx);
|
||||||
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.with_tooltip::<ContactTooltip>(
|
||||||
|
contact.user.id as usize,
|
||||||
|
label,
|
||||||
|
None,
|
||||||
|
theme.tooltip.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.into_any()
|
||||||
|
} else {
|
||||||
|
event_handler
|
||||||
|
.with_tooltip::<ContactTooltip>(
|
||||||
|
contact.user.id as usize,
|
||||||
|
format!(
|
||||||
|
"{} is {}",
|
||||||
|
contact.user.github_login,
|
||||||
|
if busy { "on a call" } else { "offline" }
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
theme.tooltip.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_contact_placeholder(
|
fn render_contact_placeholder(
|
||||||
|
@ -1750,12 +1892,13 @@ impl CollabPanel {
|
||||||
channel: &Channel,
|
channel: &Channel,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
path: ChannelPath,
|
path: ChannelPath,
|
||||||
theme: &theme::CollabPanel,
|
theme: &theme::Theme,
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> AnyElement<Self> {
|
) -> AnyElement<Self> {
|
||||||
let channel_id = channel.id;
|
let channel_id = channel.id;
|
||||||
|
let collab_theme = &theme.collab_panel;
|
||||||
let has_children = self.channel_store.read(cx).has_children(channel_id);
|
let has_children = self.channel_store.read(cx).has_children(channel_id);
|
||||||
let other_selected =
|
let other_selected =
|
||||||
self.selected_channel().map(|channel| channel.0.id) == Some(channel.id);
|
self.selected_channel().map(|channel| channel.0.id) == Some(channel.id);
|
||||||
|
@ -1775,6 +1918,8 @@ impl CollabPanel {
|
||||||
|
|
||||||
enum ChannelCall {}
|
enum ChannelCall {}
|
||||||
enum ChannelNote {}
|
enum ChannelNote {}
|
||||||
|
enum IconTooltip {}
|
||||||
|
enum ChannelTooltip {}
|
||||||
|
|
||||||
let mut is_dragged_over = false;
|
let mut is_dragged_over = false;
|
||||||
if cx
|
if cx
|
||||||
|
@ -1810,27 +1955,34 @@ impl CollabPanel {
|
||||||
Flex::<Self>::row()
|
Flex::<Self>::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
Svg::new("icons/hash.svg")
|
Svg::new("icons/hash.svg")
|
||||||
.with_color(theme.channel_hash.color)
|
.with_color(collab_theme.channel_hash.color)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(theme.channel_hash.width)
|
.with_width(collab_theme.channel_hash.width)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left(),
|
.left(),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child({
|
||||||
Label::new(
|
let style = collab_theme
|
||||||
channel.name.clone(),
|
|
||||||
theme
|
|
||||||
.channel_name
|
.channel_name
|
||||||
.in_state(channel.unseen_message_id.is_some())
|
.in_state(channel.unseen_note_version.is_some());
|
||||||
.text
|
Label::new(channel.name.clone(), style.text.clone())
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.channel_name.container)
|
.with_style(style.container)
|
||||||
.aligned()
|
.aligned()
|
||||||
.left()
|
.left()
|
||||||
.flex(1., true),
|
.with_tooltip::<ChannelTooltip>(
|
||||||
|
channel_id as usize,
|
||||||
|
if is_active {
|
||||||
|
"Open channel notes"
|
||||||
|
} else {
|
||||||
|
"Join channel"
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
theme.tooltip.clone(),
|
||||||
|
cx,
|
||||||
)
|
)
|
||||||
|
.flex(1., true)
|
||||||
|
})
|
||||||
.with_child(
|
.with_child(
|
||||||
MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |_, cx| {
|
MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |_, cx| {
|
||||||
let participants =
|
let participants =
|
||||||
|
@ -1838,14 +1990,14 @@ impl CollabPanel {
|
||||||
if !participants.is_empty() {
|
if !participants.is_empty() {
|
||||||
let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
|
let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
|
||||||
|
|
||||||
FacePile::new(theme.face_overlap)
|
FacePile::new(collab_theme.face_overlap)
|
||||||
.with_children(
|
.with_children(
|
||||||
participants
|
participants
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
Some(
|
Some(
|
||||||
Image::from_data(user.avatar.clone()?)
|
Image::from_data(user.avatar.clone()?)
|
||||||
.with_style(theme.channel_avatar),
|
.with_style(collab_theme.channel_avatar),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.take(FACEPILE_LIMIT),
|
.take(FACEPILE_LIMIT),
|
||||||
|
@ -1853,26 +2005,50 @@ impl CollabPanel {
|
||||||
.with_children((extra_count > 0).then(|| {
|
.with_children((extra_count > 0).then(|| {
|
||||||
Label::new(
|
Label::new(
|
||||||
format!("+{}", extra_count),
|
format!("+{}", extra_count),
|
||||||
theme.extra_participant_label.text.clone(),
|
collab_theme.extra_participant_label.text.clone(),
|
||||||
)
|
)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.extra_participant_label.container)
|
.with_style(collab_theme.extra_participant_label.container)
|
||||||
}))
|
}))
|
||||||
|
.with_tooltip::<IconTooltip>(
|
||||||
|
channel_id as usize,
|
||||||
|
if is_active {
|
||||||
|
"Open Channel Notes"
|
||||||
|
} else {
|
||||||
|
"Join channel"
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
theme.tooltip.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
} else if row_hovered {
|
} else if row_hovered {
|
||||||
Svg::new("icons/speaker-loud.svg")
|
Svg::new("icons/file.svg")
|
||||||
.with_color(theme.channel_hash.color)
|
.with_color(collab_theme.channel_hash.color)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(theme.channel_hash.width)
|
.with_width(collab_theme.channel_hash.width)
|
||||||
.contained()
|
.contained()
|
||||||
.with_margin_right(theme.channel_hash.container.margin.left)
|
.with_margin_right(collab_theme.channel_hash.container.margin.left)
|
||||||
|
.with_tooltip::<IconTooltip>(
|
||||||
|
channel_id as usize,
|
||||||
|
"Open channel notes",
|
||||||
|
None,
|
||||||
|
theme.tooltip.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
.into_any()
|
.into_any()
|
||||||
} else {
|
} else {
|
||||||
Empty::new().into_any()
|
Empty::new().into_any()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
this.join_channel_call(channel_id, cx);
|
let participants =
|
||||||
|
this.channel_store.read(cx).channel_participants(channel_id);
|
||||||
|
if is_active || participants.is_empty() {
|
||||||
|
this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
|
||||||
|
} else {
|
||||||
|
this.join_channel(channel_id, cx);
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
|
@ -1880,17 +2056,17 @@ impl CollabPanel {
|
||||||
let participants =
|
let participants =
|
||||||
self.channel_store.read(cx).channel_participants(channel_id);
|
self.channel_store.read(cx).channel_participants(channel_id);
|
||||||
if participants.is_empty() {
|
if participants.is_empty() {
|
||||||
if channel.unseen_note_version.is_some() {
|
if channel.unseen_message_id.is_some() {
|
||||||
Svg::new("icons/terminal.svg")
|
Svg::new("icons/conversations.svg")
|
||||||
.with_color(theme.channel_note_active_color)
|
.with_color(collab_theme.channel_note_active_color)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(theme.channel_hash.width)
|
.with_width(collab_theme.channel_hash.width)
|
||||||
.into_any()
|
.into_any()
|
||||||
} else if row_hovered {
|
} else if row_hovered {
|
||||||
Svg::new("icons/terminal.svg")
|
Svg::new("icons/conversations.svg")
|
||||||
.with_color(theme.channel_hash.color)
|
.with_color(collab_theme.channel_hash.color)
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_width(theme.channel_hash.width)
|
.with_width(collab_theme.channel_hash.width)
|
||||||
.into_any()
|
.into_any()
|
||||||
} else {
|
} else {
|
||||||
Empty::new().into_any()
|
Empty::new().into_any()
|
||||||
|
@ -1900,7 +2076,7 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
|
this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.align_children_center()
|
.align_children_center()
|
||||||
|
@ -1912,24 +2088,28 @@ impl CollabPanel {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.with_id(ix)
|
.with_id(ix)
|
||||||
.with_style(theme.disclosure.clone())
|
.with_style(collab_theme.disclosure.clone())
|
||||||
.element()
|
.element()
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(theme.row_height)
|
.with_height(collab_theme.row_height)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(select_state(
|
.with_style(select_state(
|
||||||
theme
|
collab_theme
|
||||||
.channel_row
|
.channel_row
|
||||||
.in_state(is_selected || is_active || is_dragged_over),
|
.in_state(is_selected || is_active || is_dragged_over),
|
||||||
))
|
))
|
||||||
.with_padding_left(
|
.with_padding_left(
|
||||||
theme.channel_row.default_style().padding.left
|
collab_theme.channel_row.default_style().padding.left
|
||||||
+ theme.channel_indent * depth as f32,
|
+ collab_theme.channel_indent * depth as f32,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
if this.drag_target_channel.take().is_none() {
|
if this.drag_target_channel.take().is_none() {
|
||||||
this.join_channel_chat(channel_id, cx);
|
if is_active {
|
||||||
|
this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
|
||||||
|
} else {
|
||||||
|
this.join_channel(channel_id, cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_click(MouseButton::Right, {
|
.on_click(MouseButton::Right, {
|
||||||
|
@ -2353,6 +2533,13 @@ impl CollabPanel {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
|
items.push(ContextMenuItem::action(
|
||||||
|
"Open Chat",
|
||||||
|
JoinChannelChat {
|
||||||
|
channel_id: path.channel_id(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
if self.channel_store.read(cx).is_user_admin(path.channel_id()) {
|
if self.channel_store.read(cx).is_user_admin(path.channel_id()) {
|
||||||
let parent_id = path.parent_id();
|
let parent_id = path.parent_id();
|
||||||
|
|
||||||
|
@ -2549,7 +2736,28 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ListEntry::Channel { channel, .. } => {
|
ListEntry::Channel { channel, .. } => {
|
||||||
self.join_channel_chat(channel.id, cx);
|
let is_active = iife!({
|
||||||
|
let call_channel = ActiveCall::global(cx)
|
||||||
|
.read(cx)
|
||||||
|
.room()?
|
||||||
|
.read(cx)
|
||||||
|
.channel_id()?;
|
||||||
|
|
||||||
|
dbg!(call_channel, channel.id);
|
||||||
|
Some(call_channel == channel.id)
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
dbg!(is_active);
|
||||||
|
if is_active {
|
||||||
|
self.open_channel_notes(
|
||||||
|
&OpenChannelNotes {
|
||||||
|
channel_id: channel.id,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.join_channel(channel.id, cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
|
ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -2952,13 +3160,58 @@ impl CollabPanel {
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_channel_call(&self, channel: u64, cx: &mut ViewContext<Self>) {
|
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
|
||||||
ActiveCall::global(cx)
|
let workspace = self.workspace.clone();
|
||||||
.update(cx, |call, cx| call.join_channel(channel, cx))
|
let window = cx.window();
|
||||||
|
let active_call = ActiveCall::global(cx);
|
||||||
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
if active_call.read_with(&mut cx, |active_call, _| active_call.room().is_some()) {
|
||||||
|
let answer = window.prompt(
|
||||||
|
PromptLevel::Warning,
|
||||||
|
"Do you want to leave the current call?",
|
||||||
|
&["Yes, Join Channel", "Cancel"],
|
||||||
|
&mut cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(mut answer) = answer {
|
||||||
|
if answer.next().await == Some(1) {
|
||||||
|
return anyhow::Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let room = active_call
|
||||||
|
.update(&mut cx, |call, cx| call.join_channel(channel_id, cx))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let tasks = room.update(&mut cx, |room, cx| {
|
||||||
|
let Some(workspace) = workspace.upgrade(cx) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let projects = room.projects_to_join();
|
||||||
|
|
||||||
|
if projects.is_empty() {
|
||||||
|
ChannelView::open(channel_id, workspace, cx).detach();
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
room.projects_to_join()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(project_id, user_id)| {
|
||||||
|
let app_state = workspace.read(cx).app_state().clone();
|
||||||
|
workspace::join_remote_project(project_id, user_id, app_state, cx)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
for task in tasks {
|
||||||
|
task.await?;
|
||||||
|
}
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_channel_chat(&mut self, channel_id: u64, cx: &mut ViewContext<Self>) {
|
fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext<Self>) {
|
||||||
|
let channel_id = action.channel_id;
|
||||||
if let Some(workspace) = self.workspace.upgrade(cx) {
|
if let Some(workspace) = self.workspace.upgrade(cx) {
|
||||||
cx.app_context().defer(move |cx| {
|
cx.app_context().defer(move |cx| {
|
||||||
workspace.update(cx, |workspace, cx| {
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
|
|
@ -215,7 +215,13 @@ impl CollabTitlebarItem {
|
||||||
let git_style = theme.titlebar.git_menu_button.clone();
|
let git_style = theme.titlebar.git_menu_button.clone();
|
||||||
let item_spacing = theme.titlebar.item_spacing;
|
let item_spacing = theme.titlebar.item_spacing;
|
||||||
|
|
||||||
let mut ret = Flex::row().with_child(
|
let mut ret = Flex::row();
|
||||||
|
|
||||||
|
if let Some(project_host) = self.collect_project_host(theme.clone(), cx) {
|
||||||
|
ret = ret.with_child(project_host)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ret.with_child(
|
||||||
Stack::new()
|
Stack::new()
|
||||||
.with_child(
|
.with_child(
|
||||||
MouseEventHandler::new::<ToggleProjectMenu, _>(0, cx, |mouse_state, cx| {
|
MouseEventHandler::new::<ToggleProjectMenu, _>(0, cx, |mouse_state, cx| {
|
||||||
|
@ -283,6 +289,71 @@ impl CollabTitlebarItem {
|
||||||
ret.into_any()
|
ret.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn collect_project_host(
|
||||||
|
&self,
|
||||||
|
theme: Arc<Theme>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<AnyElement<Self>> {
|
||||||
|
if ActiveCall::global(cx).read(cx).room().is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let project = self.project.read(cx);
|
||||||
|
let user_store = self.user_store.read(cx);
|
||||||
|
|
||||||
|
if project.is_local() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(host) = project.host() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let (Some(host_user), Some(participant_index)) = (
|
||||||
|
user_store.get_cached_user(host.user_id),
|
||||||
|
user_store.participant_indices().get(&host.user_id),
|
||||||
|
) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ProjectHost {}
|
||||||
|
enum ProjectHostTooltip {}
|
||||||
|
|
||||||
|
let host_style = theme.titlebar.project_host.clone();
|
||||||
|
let selection_style = theme
|
||||||
|
.editor
|
||||||
|
.selection_style_for_room_participant(participant_index.0);
|
||||||
|
let peer_id = host.peer_id.clone();
|
||||||
|
|
||||||
|
Some(
|
||||||
|
MouseEventHandler::new::<ProjectHost, _>(0, cx, |mouse_state, _| {
|
||||||
|
let mut host_style = host_style.style_for(mouse_state).clone();
|
||||||
|
host_style.text.color = selection_style.cursor;
|
||||||
|
Label::new(host_user.github_login.clone(), host_style.text)
|
||||||
|
.contained()
|
||||||
|
.with_style(host_style.container)
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
if let Some(workspace) = this.workspace.upgrade(cx) {
|
||||||
|
if let Some(task) =
|
||||||
|
workspace.update(cx, |workspace, cx| workspace.follow(peer_id, cx))
|
||||||
|
{
|
||||||
|
task.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_tooltip::<ProjectHostTooltip>(
|
||||||
|
0,
|
||||||
|
host_user.github_login.clone() + " is sharing this project. Click to follow.",
|
||||||
|
None,
|
||||||
|
theme.tooltip.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.into_any_named("project-host"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
fn window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
||||||
let project = if active {
|
let project = if active {
|
||||||
Some(self.project.clone())
|
Some(self.project.clone())
|
||||||
|
@ -877,7 +948,7 @@ impl CollabTitlebarItem {
|
||||||
fn render_face_pile(
|
fn render_face_pile(
|
||||||
&self,
|
&self,
|
||||||
user: &User,
|
user: &User,
|
||||||
replica_id: Option<ReplicaId>,
|
_replica_id: Option<ReplicaId>,
|
||||||
peer_id: PeerId,
|
peer_id: PeerId,
|
||||||
location: Option<ParticipantLocation>,
|
location: Option<ParticipantLocation>,
|
||||||
muted: bool,
|
muted: bool,
|
||||||
|
@ -1019,55 +1090,30 @@ impl CollabTitlebarItem {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
match (replica_id, location) {
|
if Some(peer_id) == self_peer_id {
|
||||||
// If the user's location isn't known, do nothing.
|
return content.into_any();
|
||||||
(_, None) => content.into_any(),
|
}
|
||||||
|
|
||||||
// If the user is not in this project, but is in another share project,
|
content
|
||||||
// join that project.
|
|
||||||
(None, Some(ParticipantLocation::SharedProject { project_id })) => content
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
if let Some(workspace) = this.workspace.upgrade(cx) {
|
let Some(workspace) = this.workspace.upgrade(cx) else {
|
||||||
let app_state = workspace.read(cx).app_state().clone();
|
return;
|
||||||
workspace::join_remote_project(project_id, user_id, app_state, cx)
|
};
|
||||||
.detach_and_log_err(cx);
|
if let Some(task) =
|
||||||
}
|
workspace.update(cx, |workspace, cx| workspace.follow(peer_id, cx))
|
||||||
})
|
|
||||||
.with_tooltip::<TitlebarParticipant>(
|
|
||||||
peer_id.as_u64() as usize,
|
|
||||||
format!("Follow {} into external project", user.github_login),
|
|
||||||
Some(Box::new(FollowNextCollaborator)),
|
|
||||||
theme.tooltip.clone(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.into_any(),
|
|
||||||
|
|
||||||
// Otherwise, follow the user in the current window.
|
|
||||||
_ => content
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.on_click(MouseButton::Left, move |_, item, cx| {
|
|
||||||
if let Some(workspace) = item.workspace.upgrade(cx) {
|
|
||||||
if let Some(task) = workspace
|
|
||||||
.update(cx, |workspace, cx| workspace.toggle_follow(peer_id, cx))
|
|
||||||
{
|
{
|
||||||
task.detach_and_log_err(cx);
|
task.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.with_tooltip::<TitlebarParticipant>(
|
.with_tooltip::<TitlebarParticipant>(
|
||||||
peer_id.as_u64() as usize,
|
peer_id.as_u64() as usize,
|
||||||
if self_following {
|
format!("Follow {}", user.github_login),
|
||||||
format!("Unfollow {}", user.github_login)
|
|
||||||
} else {
|
|
||||||
format!("Follow {}", user.github_login)
|
|
||||||
},
|
|
||||||
Some(Box::new(FollowNextCollaborator)),
|
Some(Box::new(FollowNextCollaborator)),
|
||||||
theme.tooltip.clone(),
|
theme.tooltip.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.into_any(),
|
.into_any()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn location_style(
|
fn location_style(
|
||||||
|
|
|
@ -7,7 +7,7 @@ mod face_pile;
|
||||||
mod incoming_call_notification;
|
mod incoming_call_notification;
|
||||||
mod notifications;
|
mod notifications;
|
||||||
mod panel_settings;
|
mod panel_settings;
|
||||||
mod project_shared_notification;
|
pub mod project_shared_notification;
|
||||||
mod sharing_status_indicator;
|
mod sharing_status_indicator;
|
||||||
|
|
||||||
use call::{report_call_event_for_room, ActiveCall, Room};
|
use call::{report_call_event_for_room, ActiveCall, Room};
|
||||||
|
|
|
@ -40,7 +40,9 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||||
.push(window);
|
.push(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
room::Event::RemoteProjectUnshared { project_id } => {
|
room::Event::RemoteProjectUnshared { project_id }
|
||||||
|
| room::Event::RemoteProjectJoined { project_id }
|
||||||
|
| room::Event::RemoteProjectInvitationDiscarded { project_id } => {
|
||||||
if let Some(windows) = notification_windows.remove(&project_id) {
|
if let Some(windows) = notification_windows.remove(&project_id) {
|
||||||
for window in windows {
|
for window in windows {
|
||||||
window.remove(cx);
|
window.remove(cx);
|
||||||
|
@ -82,7 +84,6 @@ impl ProjectSharedNotification {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join(&mut self, cx: &mut ViewContext<Self>) {
|
fn join(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
cx.remove_window();
|
|
||||||
if let Some(app_state) = self.app_state.upgrade() {
|
if let Some(app_state) = self.app_state.upgrade() {
|
||||||
workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
|
workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
|
@ -90,7 +91,15 @@ impl ProjectSharedNotification {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
cx.remove_window();
|
if let Some(active_room) =
|
||||||
|
ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
|
||||||
|
{
|
||||||
|
active_room.update(cx, |_, cx| {
|
||||||
|
cx.emit(room::Event::RemoteProjectInvitationDiscarded {
|
||||||
|
project_id: self.project_id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
|
|
|
@ -1057,7 +1057,8 @@ impl CompletionsMenu {
|
||||||
item_ix: Some(item_ix),
|
item_ix: Some(item_ix),
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
)
|
||||||
|
.map(|task| task.detach());
|
||||||
})
|
})
|
||||||
.into_any(),
|
.into_any(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,7 +33,7 @@ lazy_static.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
sysinfo = "0.27.1"
|
sysinfo.workspace = true
|
||||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@ path = "src/fs.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
gpui = { path = "../gpui" }
|
|
||||||
lsp = { path = "../lsp" }
|
|
||||||
rope = { path = "../rope" }
|
rope = { path = "../rope" }
|
||||||
text = { path = "../text" }
|
text = { path = "../text" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
@ -34,8 +32,10 @@ log.workspace = true
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
|
|
||||||
|
gpui = { path = "../gpui", optional = true}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test-support = []
|
test-support = ["gpui/test-support"]
|
||||||
|
|
|
@ -93,33 +93,6 @@ pub struct Metadata {
|
||||||
pub is_dir: bool,
|
pub is_dir: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<lsp::CreateFileOptions> for CreateOptions {
|
|
||||||
fn from(options: lsp::CreateFileOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
overwrite: options.overwrite.unwrap_or(false),
|
|
||||||
ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<lsp::RenameFileOptions> for RenameOptions {
|
|
||||||
fn from(options: lsp::RenameFileOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
overwrite: options.overwrite.unwrap_or(false),
|
|
||||||
ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<lsp::DeleteFileOptions> for RemoveOptions {
|
|
||||||
fn from(options: lsp::DeleteFileOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
recursive: options.recursive.unwrap_or(false),
|
|
||||||
ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RealFs;
|
pub struct RealFs;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
|
@ -11,7 +11,7 @@ path = "src/gpui.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test-support = ["backtrace", "dhat", "env_logger", "collections/test-support"]
|
test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
|
|
|
@ -103,6 +103,7 @@ pub struct Platform {
|
||||||
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
||||||
cursor: Mutex<CursorStyle>,
|
cursor: Mutex<CursorStyle>,
|
||||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||||
|
active_screen: Screen,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform {
|
impl Platform {
|
||||||
|
@ -113,6 +114,7 @@ impl Platform {
|
||||||
current_clipboard_item: Default::default(),
|
current_clipboard_item: Default::default(),
|
||||||
cursor: Mutex::new(CursorStyle::Arrow),
|
cursor: Mutex::new(CursorStyle::Arrow),
|
||||||
active_window: Default::default(),
|
active_window: Default::default(),
|
||||||
|
active_screen: Screen::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,12 +138,16 @@ impl super::Platform for Platform {
|
||||||
|
|
||||||
fn quit(&self) {}
|
fn quit(&self) {}
|
||||||
|
|
||||||
fn screen_by_id(&self, _id: uuid::Uuid) -> Option<Rc<dyn crate::platform::Screen>> {
|
fn screen_by_id(&self, uuid: uuid::Uuid) -> Option<Rc<dyn crate::platform::Screen>> {
|
||||||
|
if self.active_screen.uuid == uuid {
|
||||||
|
Some(Rc::new(self.active_screen.clone()))
|
||||||
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn screens(&self) -> Vec<Rc<dyn crate::platform::Screen>> {
|
fn screens(&self) -> Vec<Rc<dyn crate::platform::Screen>> {
|
||||||
Default::default()
|
vec![Rc::new(self.active_screen.clone())]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_window(
|
fn open_window(
|
||||||
|
@ -158,6 +164,7 @@ impl super::Platform for Platform {
|
||||||
WindowBounds::Fixed(rect) => rect.size(),
|
WindowBounds::Fixed(rect) => rect.size(),
|
||||||
},
|
},
|
||||||
self.active_window.clone(),
|
self.active_window.clone(),
|
||||||
|
Rc::new(self.active_screen.clone()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +177,7 @@ impl super::Platform for Platform {
|
||||||
handle,
|
handle,
|
||||||
vec2f(24., 24.),
|
vec2f(24., 24.),
|
||||||
self.active_window.clone(),
|
self.active_window.clone(),
|
||||||
|
Rc::new(self.active_screen.clone()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,8 +246,18 @@ impl super::Platform for Platform {
|
||||||
fn restart(&self) {}
|
fn restart(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Screen;
|
pub struct Screen {
|
||||||
|
uuid: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screen {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: uuid::Uuid::new_v4(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl super::Screen for Screen {
|
impl super::Screen for Screen {
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
@ -255,7 +273,7 @@ impl super::Screen for Screen {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_uuid(&self) -> Option<uuid::Uuid> {
|
fn display_uuid(&self) -> Option<uuid::Uuid> {
|
||||||
Some(uuid::Uuid::new_v4())
|
Some(self.uuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,6 +293,7 @@ pub struct Window {
|
||||||
pub(crate) edited: bool,
|
pub(crate) edited: bool,
|
||||||
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
|
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
|
||||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||||
|
screen: Rc<Screen>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
@ -282,6 +301,7 @@ impl Window {
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
size: Vector2F,
|
size: Vector2F,
|
||||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||||
|
screen: Rc<Screen>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
handle,
|
handle,
|
||||||
|
@ -299,6 +319,7 @@ impl Window {
|
||||||
edited: false,
|
edited: false,
|
||||||
pending_prompts: Default::default(),
|
pending_prompts: Default::default(),
|
||||||
active_window,
|
active_window,
|
||||||
|
screen,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +350,7 @@ impl super::Window for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screen(&self) -> Rc<dyn crate::platform::Screen> {
|
fn screen(&self) -> Rc<dyn crate::platform::Screen> {
|
||||||
Rc::new(Screen)
|
self.screen.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_position(&self) -> Vector2F {
|
fn mouse_position(&self) -> Vector2F {
|
||||||
|
|
|
@ -8,8 +8,8 @@ use crate::{
|
||||||
language_settings::{language_settings, LanguageSettings},
|
language_settings::{language_settings, LanguageSettings},
|
||||||
outline::OutlineItem,
|
outline::OutlineItem,
|
||||||
syntax_map::{
|
syntax_map::{
|
||||||
SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot,
|
SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
|
||||||
ToTreeSitterPoint,
|
SyntaxSnapshot, ToTreeSitterPoint,
|
||||||
},
|
},
|
||||||
CodeLabel, LanguageScope, Outline,
|
CodeLabel, LanguageScope, Outline,
|
||||||
};
|
};
|
||||||
|
@ -2467,6 +2467,14 @@ impl BufferSnapshot {
|
||||||
Some(items)
|
Some(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn matches(
|
||||||
|
&self,
|
||||||
|
range: Range<usize>,
|
||||||
|
query: fn(&Grammar) -> Option<&tree_sitter::Query>,
|
||||||
|
) -> SyntaxMapMatches {
|
||||||
|
self.syntax.matches(range, self, query)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns bracket range pairs overlapping or adjacent to `range`
|
/// Returns bracket range pairs overlapping or adjacent to `range`
|
||||||
pub fn bracket_ranges<'a, T: ToOffset>(
|
pub fn bracket_ranges<'a, T: ToOffset>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|
|
@ -975,6 +975,10 @@ impl Project {
|
||||||
&self.collaborators
|
&self.collaborators
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn host(&self) -> Option<&Collaborator> {
|
||||||
|
self.collaborators.values().find(|c| c.replica_id == 0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Collect all worktrees, including ones that don't appear in the project panel
|
/// Collect all worktrees, including ones that don't appear in the project panel
|
||||||
pub fn worktrees<'a>(
|
pub fn worktrees<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
@ -4957,7 +4961,15 @@ impl Project {
|
||||||
if abs_path.ends_with("/") {
|
if abs_path.ends_with("/") {
|
||||||
fs.create_dir(&abs_path).await?;
|
fs.create_dir(&abs_path).await?;
|
||||||
} else {
|
} else {
|
||||||
fs.create_file(&abs_path, op.options.map(Into::into).unwrap_or_default())
|
fs.create_file(
|
||||||
|
&abs_path,
|
||||||
|
op.options
|
||||||
|
.map(|options| fs::CreateOptions {
|
||||||
|
overwrite: options.overwrite.unwrap_or(false),
|
||||||
|
ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4974,7 +4986,12 @@ impl Project {
|
||||||
fs.rename(
|
fs.rename(
|
||||||
&source_abs_path,
|
&source_abs_path,
|
||||||
&target_abs_path,
|
&target_abs_path,
|
||||||
op.options.map(Into::into).unwrap_or_default(),
|
op.options
|
||||||
|
.map(|options| fs::RenameOptions {
|
||||||
|
overwrite: options.overwrite.unwrap_or(false),
|
||||||
|
ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
|
||||||
|
})
|
||||||
|
.unwrap_or_default(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
@ -4984,7 +5001,13 @@ impl Project {
|
||||||
.uri
|
.uri
|
||||||
.to_file_path()
|
.to_file_path()
|
||||||
.map_err(|_| anyhow!("can't convert URI to path"))?;
|
.map_err(|_| anyhow!("can't convert URI to path"))?;
|
||||||
let options = op.options.map(Into::into).unwrap_or_default();
|
let options = op
|
||||||
|
.options
|
||||||
|
.map(|options| fs::RemoveOptions {
|
||||||
|
recursive: options.recursive.unwrap_or(false),
|
||||||
|
ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
if abs_path.ends_with("/") {
|
if abs_path.ends_with("/") {
|
||||||
fs.remove_dir(&abs_path, options).await?;
|
fs.remove_dir(&abs_path, options).await?;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -305,6 +305,11 @@ async fn test_code_context_retrieval_rust() {
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct D {
|
||||||
|
name: String
|
||||||
|
}
|
||||||
"
|
"
|
||||||
.unindent();
|
.unindent();
|
||||||
|
|
||||||
|
@ -361,6 +366,15 @@ async fn test_code_context_retrieval_rust() {
|
||||||
.unindent(),
|
.unindent(),
|
||||||
text.find("fn function_2").unwrap(),
|
text.find("fn function_2").unwrap(),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct D {
|
||||||
|
name: String
|
||||||
|
}"
|
||||||
|
.unindent(),
|
||||||
|
text.find("struct D").unwrap(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1422,6 +1436,9 @@ fn rust_lang() -> Arc<Language> {
|
||||||
name: (_) @name)
|
name: (_) @name)
|
||||||
] @item
|
] @item
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(attribute_item) @collapse
|
||||||
|
(use_declaration) @collapse
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
|
@ -12,6 +12,8 @@ path = "src/storybook.rs"
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
clap = { version = "4.4", features = ["derive", "string"] }
|
clap = { version = "4.4", features = ["derive", "string"] }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
fs = { path = "../fs" }
|
||||||
|
futures.workspace = true
|
||||||
gpui2 = { path = "../gpui2" }
|
gpui2 = { path = "../gpui2" }
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::Breadcrumb;
|
use ui::{Breadcrumb, HighlightedText, Symbol};
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -8,9 +11,35 @@ pub struct BreadcrumbStory {}
|
||||||
|
|
||||||
impl BreadcrumbStory {
|
impl BreadcrumbStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, Breadcrumb>(cx))
|
.child(Story::title_for::<_, Breadcrumb>(cx))
|
||||||
.child(Story::label(cx, "Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(Breadcrumb::new())
|
.child(Breadcrumb::new(
|
||||||
|
PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
|
||||||
|
vec![
|
||||||
|
Symbol(vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "impl ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "BreadcrumbStory".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
Symbol(vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "fn ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "render".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,10 @@ pub struct BufferStory {}
|
||||||
|
|
||||||
impl BufferStory {
|
impl BufferStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, Buffer<V>>(cx))
|
.child(Story::title_for::<_, Buffer>(cx))
|
||||||
.child(Story::label(cx, "Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
|
.child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
|
||||||
.child(Story::label(cx, "Hello World (Rust)"))
|
.child(Story::label(cx, "Hello World (Rust)"))
|
||||||
|
@ -21,14 +23,14 @@ impl BufferStory {
|
||||||
div()
|
div()
|
||||||
.w(rems(64.))
|
.w(rems(64.))
|
||||||
.h_96()
|
.h_96()
|
||||||
.child(hello_world_rust_buffer_example(cx)),
|
.child(hello_world_rust_buffer_example(&theme)),
|
||||||
)
|
)
|
||||||
.child(Story::label(cx, "Hello World (Rust) with Status"))
|
.child(Story::label(cx, "Hello World (Rust) with Status"))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.w(rems(64.))
|
.w(rems(64.))
|
||||||
.h_96()
|
.h_96()
|
||||||
.child(hello_world_rust_buffer_with_status_example(cx)),
|
.child(hello_world_rust_buffer_with_status_example(&theme)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::{ChatMessage, ChatPanel};
|
use ui::{ChatMessage, ChatPanel, Panel};
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -12,9 +12,17 @@ impl ChatPanelStory {
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, ChatPanel<V>>(cx))
|
.child(Story::title_for::<_, ChatPanel<V>>(cx))
|
||||||
.child(Story::label(cx, "Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(ChatPanel::new(ScrollState::default()))
|
.child(Panel::new(
|
||||||
|
ScrollState::default(),
|
||||||
|
|_, _| vec![ChatPanel::new(ScrollState::default()).into_any()],
|
||||||
|
Box::new(()),
|
||||||
|
))
|
||||||
.child(Story::label(cx, "With Mesages"))
|
.child(Story::label(cx, "With Mesages"))
|
||||||
.child(ChatPanel::new(ScrollState::default()).with_messages(vec![
|
.child(Panel::new(
|
||||||
|
ScrollState::default(),
|
||||||
|
|_, _| {
|
||||||
|
vec![ChatPanel::new(ScrollState::default())
|
||||||
|
.with_messages(vec![
|
||||||
ChatMessage::new(
|
ChatMessage::new(
|
||||||
"osiewicz".to_string(),
|
"osiewicz".to_string(),
|
||||||
"is this thing on?".to_string(),
|
"is this thing on?".to_string(),
|
||||||
|
@ -29,6 +37,10 @@ impl ChatPanelStory {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.naive_local(),
|
.naive_local(),
|
||||||
),
|
),
|
||||||
]))
|
])
|
||||||
|
.into_any()]
|
||||||
|
},
|
||||||
|
Box::new(()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ impl FacepileStory {
|
||||||
let players = static_players();
|
let players = static_players();
|
||||||
|
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, ui::Facepile>(cx))
|
.child(Story::title_for::<_, Facepile>(cx))
|
||||||
.child(Story::label(cx, "Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
|
|
@ -14,9 +14,10 @@ impl PanelStory {
|
||||||
.child(Panel::new(
|
.child(Panel::new(
|
||||||
ScrollState::default(),
|
ScrollState::default(),
|
||||||
|_, _| {
|
|_, _| {
|
||||||
(0..100)
|
vec![div()
|
||||||
.map(|ix| Label::new(format!("Item {}", ix + 1)).into_any())
|
.overflow_y_scroll(ScrollState::default())
|
||||||
.collect()
|
.children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1))))
|
||||||
|
.into_any()]
|
||||||
},
|
},
|
||||||
Box::new(()),
|
Box::new(()),
|
||||||
))
|
))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::ProjectPanel;
|
use ui::{Panel, ProjectPanel};
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -11,6 +11,10 @@ impl ProjectPanelStory {
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, ProjectPanel<V>>(cx))
|
.child(Story::title_for::<_, ProjectPanel<V>>(cx))
|
||||||
.child(Story::label(cx, "Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(ProjectPanel::new(ScrollState::default()))
|
.child(Panel::new(
|
||||||
|
ScrollState::default(),
|
||||||
|
|_, _| vec![ProjectPanel::new(ScrollState::default()).into_any()],
|
||||||
|
Box::new(()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::TabBar;
|
use ui::{Tab, TabBar};
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -11,6 +11,36 @@ impl TabBarStory {
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, TabBar<V>>(cx))
|
.child(Story::title_for::<_, TabBar<V>>(cx))
|
||||||
.child(Story::label(cx, "Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(TabBar::new(ScrollState::default()))
|
.child(TabBar::new(vec![
|
||||||
|
Tab::new()
|
||||||
|
.title("Cargo.toml".to_string())
|
||||||
|
.current(false)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
Tab::new()
|
||||||
|
.title("Channels Panel".to_string())
|
||||||
|
.current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("channels_panel.rs".to_string())
|
||||||
|
.current(true)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
Tab::new()
|
||||||
|
.title("workspace.rs".to_string())
|
||||||
|
.current(false)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
Tab::new()
|
||||||
|
.title("icon_button.rs".to_string())
|
||||||
|
.current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("storybook.rs".to_string())
|
||||||
|
.current(false)
|
||||||
|
.git_status(GitStatus::Created),
|
||||||
|
Tab::new().title("theme.rs".to_string()).current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("theme_registry.rs".to_string())
|
||||||
|
.current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("styleable_helpers.rs".to_string())
|
||||||
|
.current(false),
|
||||||
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::Toolbar;
|
use ui::{theme, Breadcrumb, HighlightColor, HighlightedText, Icon, IconButton, Symbol, Toolbar};
|
||||||
|
|
||||||
use crate::story::Story;
|
use crate::story::Story;
|
||||||
|
|
||||||
|
@ -8,9 +12,59 @@ pub struct ToolbarStory {}
|
||||||
|
|
||||||
impl ToolbarStory {
|
impl ToolbarStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
struct LeftItemsPayload {
|
||||||
|
pub theme: Arc<Theme>,
|
||||||
|
}
|
||||||
|
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, Toolbar>(cx))
|
.child(Story::title_for::<_, Toolbar<V>>(cx))
|
||||||
.child(Story::label(cx, "Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(Toolbar::new())
|
.child(Toolbar::new(
|
||||||
|
|_, payload| {
|
||||||
|
let payload = payload.downcast_ref::<LeftItemsPayload>().unwrap();
|
||||||
|
|
||||||
|
let theme = payload.theme.clone();
|
||||||
|
|
||||||
|
vec![Breadcrumb::new(
|
||||||
|
PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
|
||||||
|
vec![
|
||||||
|
Symbol(vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "impl ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "ToolbarStory".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
Symbol(vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "fn ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "render".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.into_any()]
|
||||||
|
},
|
||||||
|
Box::new(LeftItemsPayload {
|
||||||
|
theme: theme.clone(),
|
||||||
|
}),
|
||||||
|
|_, _| {
|
||||||
|
vec![
|
||||||
|
IconButton::new(Icon::InlayHint).into_any(),
|
||||||
|
IconButton::new(Icon::MagnifyingGlass).into_any(),
|
||||||
|
IconButton::new(Icon::MagicWand).into_any(),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Box::new(()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub struct AvatarStory {}
|
||||||
impl AvatarStory {
|
impl AvatarStory {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, ui::Avatar>(cx))
|
.child(Story::title_for::<_, Avatar>(cx))
|
||||||
.child(Story::label(cx, "Default"))
|
.child(Story::label(cx, "Default"))
|
||||||
.child(Avatar::new(
|
.child(Avatar::new(
|
||||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||||
|
|
|
@ -12,7 +12,7 @@ impl IconStory {
|
||||||
let icons = Icon::iter();
|
let icons = Icon::iter();
|
||||||
|
|
||||||
Story::container(cx)
|
Story::container(cx)
|
||||||
.child(Story::title_for::<_, ui::IconElement>(cx))
|
.child(Story::title_for::<_, IconElement>(cx))
|
||||||
.child(Story::label(cx, "All Icons"))
|
.child(Story::label(cx, "All Icons"))
|
||||||
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
|
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,5 +19,8 @@ impl KitchenSinkStory {
|
||||||
.child(div().flex().flex_col().children_any(element_stories))
|
.child(div().flex().flex_col().children_any(element_stories))
|
||||||
.child(Story::label(cx, "Components"))
|
.child(Story::label(cx, "Components"))
|
||||||
.child(div().flex().flex_col().children_any(component_stories))
|
.child(div().flex().flex_col().children_any(component_stories))
|
||||||
|
// Add a bit of space at the bottom of the kitchen sink so elements
|
||||||
|
// don't end up squished right up against the bottom of the screen.
|
||||||
|
.child(div().p_4())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ mod stories;
|
||||||
mod story;
|
mod story;
|
||||||
mod story_selector;
|
mod story_selector;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::{process::Command, sync::Arc};
|
||||||
|
|
||||||
use ::theme as legacy_theme;
|
use ::theme as legacy_theme;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
@ -38,11 +38,44 @@ struct Args {
|
||||||
theme: Option<String>,
|
theme: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn watch_zed_changes(fs: Arc<dyn fs::Fs>) -> Option<()> {
|
||||||
|
if std::env::var("ZED_HOT_RELOAD").is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
use futures::StreamExt;
|
||||||
|
let mut events = fs
|
||||||
|
.watch(".".as_ref(), std::time::Duration::from_millis(100))
|
||||||
|
.await;
|
||||||
|
let mut current_child: Option<std::process::Child> = None;
|
||||||
|
while let Some(events) = events.next().await {
|
||||||
|
if !events.iter().any(|event| {
|
||||||
|
event
|
||||||
|
.path
|
||||||
|
.to_str()
|
||||||
|
.map(|path| path.contains("/crates/"))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let child = current_child.take().map(|mut child| child.kill());
|
||||||
|
log::info!("Storybook changed, rebuilding...");
|
||||||
|
current_child = Some(
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(["run", "-p", "storybook"])
|
||||||
|
.spawn()
|
||||||
|
.ok()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let fs = Arc::new(fs::RealFs);
|
||||||
|
|
||||||
gpui2::App::new(Assets).unwrap().run(move |cx| {
|
gpui2::App::new(Assets).unwrap().run(move |cx| {
|
||||||
let mut store = SettingsStore::default();
|
let mut store = SettingsStore::default();
|
||||||
store
|
store
|
||||||
|
@ -63,6 +96,10 @@ fn main() {
|
||||||
})
|
})
|
||||||
.and_then(|theme_name| theme_registry.get(&theme_name).ok());
|
.and_then(|theme_name| theme_registry.get(&theme_name).ok());
|
||||||
|
|
||||||
|
cx.spawn(|_| async move {
|
||||||
|
watch_zed_changes(fs).await;
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
cx.add_window(
|
cx.add_window(
|
||||||
gpui2::WindowOptions {
|
gpui2::WindowOptions {
|
||||||
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1700., 980.))),
|
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1700., 980.))),
|
||||||
|
|
|
@ -131,6 +131,7 @@ pub struct Titlebar {
|
||||||
pub menu: TitlebarMenu,
|
pub menu: TitlebarMenu,
|
||||||
pub project_menu_button: Toggleable<Interactive<ContainedText>>,
|
pub project_menu_button: Toggleable<Interactive<ContainedText>>,
|
||||||
pub git_menu_button: Toggleable<Interactive<ContainedText>>,
|
pub git_menu_button: Toggleable<Interactive<ContainedText>>,
|
||||||
|
pub project_host: Interactive<ContainedText>,
|
||||||
pub item_spacing: f32,
|
pub item_spacing: f32,
|
||||||
pub face_pile_spacing: f32,
|
pub face_pile_spacing: f32,
|
||||||
pub avatar_ribbon: AvatarRibbon,
|
pub avatar_ribbon: AvatarRibbon,
|
||||||
|
|
|
@ -13,3 +13,4 @@ settings = { path = "../settings" }
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
strum = { version = "0.25.0", features = ["derive"] }
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
|
rand = "0.8"
|
||||||
|
|
|
@ -5,7 +5,7 @@ mod chat_panel;
|
||||||
mod collab_panel;
|
mod collab_panel;
|
||||||
mod command_palette;
|
mod command_palette;
|
||||||
mod context_menu;
|
mod context_menu;
|
||||||
mod editor;
|
mod editor_pane;
|
||||||
mod facepile;
|
mod facepile;
|
||||||
mod icon_button;
|
mod icon_button;
|
||||||
mod keybinding;
|
mod keybinding;
|
||||||
|
@ -31,7 +31,7 @@ pub use chat_panel::*;
|
||||||
pub use collab_panel::*;
|
pub use collab_panel::*;
|
||||||
pub use command_palette::*;
|
pub use command_palette::*;
|
||||||
pub use context_menu::*;
|
pub use context_menu::*;
|
||||||
pub use editor::*;
|
pub use editor_pane::*;
|
||||||
pub use facepile::*;
|
pub use facepile::*;
|
||||||
pub use icon_button::*;
|
pub use icon_button::*;
|
||||||
pub use keybinding::*;
|
pub use keybinding::*;
|
||||||
|
|
|
@ -1,17 +1,35 @@
|
||||||
use crate::prelude::*;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use gpui2::elements::div::Div;
|
||||||
|
|
||||||
use crate::{h_stack, theme};
|
use crate::{h_stack, theme};
|
||||||
|
use crate::{prelude::*, HighlightedText};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Symbol(pub Vec<HighlightedText>);
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct Breadcrumb {}
|
pub struct Breadcrumb {
|
||||||
|
path: PathBuf,
|
||||||
|
symbols: Vec<Symbol>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Breadcrumb {
|
impl Breadcrumb {
|
||||||
pub fn new() -> Self {
|
pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
|
||||||
Self {}
|
Self { path, symbols }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_separator<V: 'static>(&self, theme: &Theme) -> Div<V> {
|
||||||
|
div()
|
||||||
|
.child(" › ")
|
||||||
|
.text_color(HighlightColor::Default.hsla(theme))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
|
let symbols_len = self.symbols.len();
|
||||||
|
|
||||||
h_stack()
|
h_stack()
|
||||||
.px_1()
|
.px_1()
|
||||||
// TODO: Read font from theme (or settings?).
|
// TODO: Read font from theme (or settings?).
|
||||||
|
@ -21,11 +39,33 @@ impl Breadcrumb {
|
||||||
.rounded_md()
|
.rounded_md()
|
||||||
.hover()
|
.hover()
|
||||||
.fill(theme.highest.base.hovered.background)
|
.fill(theme.highest.base.hovered.background)
|
||||||
// TODO: Replace hardcoded breadcrumbs.
|
.child(self.path.clone().to_str().unwrap().to_string())
|
||||||
.child("crates/ui/src/components/toolbar.rs")
|
.child(if !self.symbols.is_empty() {
|
||||||
.child(" › ")
|
self.render_separator(&theme)
|
||||||
.child("impl Breadcrumb")
|
} else {
|
||||||
.child(" › ")
|
div()
|
||||||
.child("fn render")
|
})
|
||||||
|
.child(
|
||||||
|
div().flex().children(
|
||||||
|
self.symbols
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
// TODO: Could use something like `intersperse` here instead.
|
||||||
|
.flat_map(|(ix, symbol)| {
|
||||||
|
let mut items =
|
||||||
|
vec![div().flex().children(symbol.0.iter().map(|segment| {
|
||||||
|
div().child(segment.text.clone()).text_color(segment.color)
|
||||||
|
}))];
|
||||||
|
|
||||||
|
let is_last_segment = ix == symbols_len - 1;
|
||||||
|
if !is_last_segment {
|
||||||
|
items.push(self.render_separator(&theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use gpui2::{Hsla, WindowContext};
|
use gpui2::{Hsla, WindowContext};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
@ -33,6 +31,7 @@ pub struct BufferRow {
|
||||||
pub show_line_number: bool,
|
pub show_line_number: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BufferRows {
|
pub struct BufferRows {
|
||||||
pub show_line_numbers: bool,
|
pub show_line_numbers: bool,
|
||||||
pub rows: Vec<BufferRow>,
|
pub rows: Vec<BufferRow>,
|
||||||
|
@ -108,9 +107,8 @@ impl BufferRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element, Clone)]
|
||||||
pub struct Buffer<V: 'static> {
|
pub struct Buffer {
|
||||||
view_type: PhantomData<V>,
|
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
rows: Option<BufferRows>,
|
rows: Option<BufferRows>,
|
||||||
readonly: bool,
|
readonly: bool,
|
||||||
|
@ -119,10 +117,9 @@ pub struct Buffer<V: 'static> {
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> Buffer<V> {
|
impl Buffer {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_type: PhantomData,
|
|
||||||
scroll_state: ScrollState::default(),
|
scroll_state: ScrollState::default(),
|
||||||
rows: Some(BufferRows::default()),
|
rows: Some(BufferRows::default()),
|
||||||
readonly: false,
|
readonly: false,
|
||||||
|
@ -161,7 +158,7 @@ impl<V: 'static> Buffer<V> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_row(row: BufferRow, cx: &WindowContext) -> impl IntoElement<V> {
|
fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let system_color = SystemColor::new();
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
|
@ -172,28 +169,35 @@ impl<V: 'static> Buffer<V> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let line_number_color = if row.current {
|
let line_number_color = if row.current {
|
||||||
HighlightColor::Default.hsla(cx)
|
HighlightColor::Default.hsla(&theme)
|
||||||
} else {
|
} else {
|
||||||
HighlightColor::Comment.hsla(cx)
|
HighlightColor::Comment.hsla(&theme)
|
||||||
};
|
};
|
||||||
|
|
||||||
h_stack()
|
h_stack()
|
||||||
.fill(line_background)
|
.fill(line_background)
|
||||||
|
.w_full()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.px_2()
|
.px_1()
|
||||||
.child(h_stack().w_4().h_full().px_1().when(row.code_action, |c| {
|
.child(
|
||||||
|
h_stack()
|
||||||
|
.w_4()
|
||||||
|
.h_full()
|
||||||
|
.px_0p5()
|
||||||
|
.when(row.code_action, |c| {
|
||||||
div().child(IconElement::new(Icon::Bolt))
|
div().child(IconElement::new(Icon::Bolt))
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
.when(row.show_line_number, |this| {
|
.when(row.show_line_number, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
h_stack().justify_end().px_1().w_4().child(
|
h_stack().justify_end().px_0p5().w_3().child(
|
||||||
div()
|
div()
|
||||||
.text_color(line_number_color)
|
.text_color(line_number_color)
|
||||||
.child(row.line_number.to_string()),
|
.child(row.line_number.to_string()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(div().mx_1().w_1().h_full().fill(row.status.hsla(cx)))
|
.child(div().mx_0p5().w_1().h_full().fill(row.status.hsla(cx)))
|
||||||
.children(row.line.map(|line| {
|
.children(row.line.map(|line| {
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -205,7 +209,7 @@ impl<V: 'static> Buffer<V> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_rows(&self, cx: &WindowContext) -> Vec<impl IntoElement<V>> {
|
fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl IntoElement<V>> {
|
||||||
match &self.rows {
|
match &self.rows {
|
||||||
Some(rows) => rows
|
Some(rows) => rows
|
||||||
.rows
|
.rows
|
||||||
|
@ -216,7 +220,7 @@ impl<V: 'static> Buffer<V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let rows = self.render_rows(cx);
|
let rows = self.render_rows(cx);
|
||||||
v_stack()
|
v_stack()
|
||||||
|
|
|
@ -4,13 +4,12 @@ use chrono::NaiveDateTime;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::theme::theme;
|
use crate::theme::theme;
|
||||||
use crate::{Icon, IconButton, Input, Label, LabelColor, Panel, PanelSide};
|
use crate::{Icon, IconButton, Input, Label, LabelColor};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct ChatPanel<V: 'static> {
|
pub struct ChatPanel<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
view_type: PhantomData<V>,
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
current_side: PanelSide,
|
|
||||||
messages: Vec<ChatMessage>,
|
messages: Vec<ChatMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,16 +18,10 @@ impl<V: 'static> ChatPanel<V> {
|
||||||
Self {
|
Self {
|
||||||
view_type: PhantomData,
|
view_type: PhantomData,
|
||||||
scroll_state,
|
scroll_state,
|
||||||
current_side: PanelSide::default(),
|
|
||||||
messages: Vec::new(),
|
messages: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn side(mut self, side: PanelSide) -> Self {
|
|
||||||
self.current_side = side;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_messages(mut self, messages: Vec<ChatMessage>) -> Self {
|
pub fn with_messages(mut self, messages: Vec<ChatMessage>) -> Self {
|
||||||
self.messages = messages;
|
self.messages = messages;
|
||||||
self
|
self
|
||||||
|
@ -37,19 +30,10 @@ impl<V: 'static> ChatPanel<V> {
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
struct PanelPayload {
|
div()
|
||||||
pub scroll_state: ScrollState,
|
|
||||||
pub messages: Vec<ChatMessage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
Panel::new(
|
|
||||||
self.scroll_state.clone(),
|
|
||||||
|_, payload| {
|
|
||||||
let payload = payload.downcast_ref::<PanelPayload>().unwrap();
|
|
||||||
|
|
||||||
vec![div()
|
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
|
.justify_between()
|
||||||
.h_full()
|
.h_full()
|
||||||
.px_2()
|
.px_2()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
@ -58,7 +42,7 @@ impl<V: 'static> ChatPanel<V> {
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.gap_2()
|
.py_2()
|
||||||
.child(div().flex().child(Label::new("#design")))
|
.child(div().flex().child(Label::new("#design")))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
@ -69,6 +53,10 @@ impl<V: 'static> ChatPanel<V> {
|
||||||
.child(IconButton::new(Icon::AudioOn)),
|
.child(IconButton::new(Icon::AudioOn)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
// Chat Body
|
// Chat Body
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
@ -76,19 +64,12 @@ impl<V: 'static> ChatPanel<V> {
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.overflow_y_scroll(payload.scroll_state.clone())
|
.overflow_y_scroll(self.scroll_state.clone())
|
||||||
.children(payload.messages.clone()),
|
.children(self.messages.clone()),
|
||||||
)
|
)
|
||||||
// Composer
|
// Composer
|
||||||
.child(div().flex().gap_2().child(Input::new("Message #design")))
|
.child(div().flex().my_2().child(Input::new("Message #design"))),
|
||||||
.into_any()]
|
|
||||||
},
|
|
||||||
Box::new(PanelPayload {
|
|
||||||
scroll_state: self.scroll_state.clone(),
|
|
||||||
messages: self.messages.clone(),
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.side(self.current_side)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::{Buffer, Toolbar};
|
|
||||||
|
|
||||||
#[derive(Element)]
|
|
||||||
struct Editor<V: 'static> {
|
|
||||||
view_type: PhantomData<V>,
|
|
||||||
toolbar: Toolbar,
|
|
||||||
buffer: Buffer<V>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: 'static> Editor<V> {
|
|
||||||
pub fn new(toolbar: Toolbar, buffer: Buffer<V>) -> Self {
|
|
||||||
Self {
|
|
||||||
view_type: PhantomData,
|
|
||||||
toolbar,
|
|
||||||
buffer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
|
||||||
div().child(self.toolbar.clone())
|
|
||||||
}
|
|
||||||
}
|
|
60
crates/ui/src/components/editor_pane.rs
Normal file
60
crates/ui/src/components/editor_pane.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::{v_stack, Breadcrumb, Buffer, Icon, IconButton, Symbol, Tab, TabBar, Toolbar};
|
||||||
|
|
||||||
|
pub struct Editor {
|
||||||
|
pub tabs: Vec<Tab>,
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub symbols: Vec<Symbol>,
|
||||||
|
pub buffer: Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Element)]
|
||||||
|
pub struct EditorPane<V: 'static> {
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
editor: Editor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: 'static> EditorPane<V> {
|
||||||
|
pub fn new(editor: Editor) -> Self {
|
||||||
|
Self {
|
||||||
|
view_type: PhantomData,
|
||||||
|
editor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
struct LeftItemsPayload {
|
||||||
|
path: PathBuf,
|
||||||
|
symbols: Vec<Symbol>,
|
||||||
|
}
|
||||||
|
|
||||||
|
v_stack()
|
||||||
|
.w_full()
|
||||||
|
.h_full()
|
||||||
|
.flex_1()
|
||||||
|
.child(TabBar::new(self.editor.tabs.clone()))
|
||||||
|
.child(Toolbar::new(
|
||||||
|
|_, payload| {
|
||||||
|
let payload = payload.downcast_ref::<LeftItemsPayload>().unwrap();
|
||||||
|
|
||||||
|
vec![Breadcrumb::new(payload.path.clone(), payload.symbols.clone()).into_any()]
|
||||||
|
},
|
||||||
|
Box::new(LeftItemsPayload {
|
||||||
|
path: self.editor.path.clone(),
|
||||||
|
symbols: self.editor.symbols.clone(),
|
||||||
|
}),
|
||||||
|
|_, _| {
|
||||||
|
vec![
|
||||||
|
IconButton::new(Icon::InlayHint).into_any(),
|
||||||
|
IconButton::new(Icon::MagnifyingGlass).into_any(),
|
||||||
|
IconButton::new(Icon::MagicWand).into_any(),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Box::new(()),
|
||||||
|
))
|
||||||
|
.child(self.editor.buffer.clone())
|
||||||
|
}
|
||||||
|
}
|
|
@ -105,16 +105,12 @@ impl<V: 'static> Panel<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
let panel_base;
|
let panel_base;
|
||||||
let current_width = if let Some(width) = self.width {
|
let current_width = self.width.unwrap_or(self.initial_width);
|
||||||
width
|
|
||||||
} else {
|
|
||||||
self.initial_width
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.current_side {
|
match self.current_side {
|
||||||
PanelSide::Left => {
|
PanelSide::Left => {
|
||||||
panel_base = v_stack()
|
panel_base = v_stack()
|
||||||
.overflow_y_scroll(self.scroll_state.clone())
|
.flex_initial()
|
||||||
.h_full()
|
.h_full()
|
||||||
.w(current_width)
|
.w(current_width)
|
||||||
.fill(theme.middle.base.default.background)
|
.fill(theme.middle.base.default.background)
|
||||||
|
@ -123,20 +119,20 @@ impl<V: 'static> Panel<V> {
|
||||||
}
|
}
|
||||||
PanelSide::Right => {
|
PanelSide::Right => {
|
||||||
panel_base = v_stack()
|
panel_base = v_stack()
|
||||||
.overflow_y_scroll(self.scroll_state.clone())
|
.flex_initial()
|
||||||
.h_full()
|
.h_full()
|
||||||
.w(current_width)
|
.w(current_width)
|
||||||
.fill(theme.middle.base.default.background)
|
.fill(theme.middle.base.default.background)
|
||||||
.border_r()
|
.border_l()
|
||||||
.border_color(theme.middle.base.default.border);
|
.border_color(theme.middle.base.default.border);
|
||||||
}
|
}
|
||||||
PanelSide::Bottom => {
|
PanelSide::Bottom => {
|
||||||
panel_base = v_stack()
|
panel_base = v_stack()
|
||||||
.overflow_y_scroll(self.scroll_state.clone())
|
.flex_initial()
|
||||||
.w_full()
|
.w_full()
|
||||||
.h(current_width)
|
.h(current_width)
|
||||||
.fill(theme.middle.base.default.background)
|
.fill(theme.middle.base.default.background)
|
||||||
.border_r()
|
.border_t()
|
||||||
.border_color(theme.middle.base.default.border);
|
.border_color(theme.middle.base.default.border);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,8 @@ impl PlayerStack {
|
||||||
div().flex().justify_center().w_full().child(
|
div().flex().justify_center().w_full().child(
|
||||||
div()
|
div()
|
||||||
.w_4()
|
.w_4()
|
||||||
.h_1()
|
.h_0p5()
|
||||||
.rounded_bl_sm()
|
.rounded_sm()
|
||||||
.rounded_br_sm()
|
|
||||||
.fill(player.cursor_color(cx)),
|
.fill(player.cursor_color(cx)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -50,7 +49,7 @@ impl PlayerStack {
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.h_6()
|
.h_6()
|
||||||
.px_1()
|
.pl_1()
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
.fill(if followers.is_none() {
|
.fill(if followers.is_none() {
|
||||||
system_color.transparent
|
system_color.transparent
|
||||||
|
@ -59,7 +58,7 @@ impl PlayerStack {
|
||||||
})
|
})
|
||||||
.child(Avatar::new(player.avatar_src().to_string()))
|
.child(Avatar::new(player.avatar_src().to_string()))
|
||||||
.children(followers.map(|followers| {
|
.children(followers.map(|followers| {
|
||||||
div().neg_mr_1().child(Facepile::new(followers.into_iter()))
|
div().neg_ml_2().child(Facepile::new(followers.into_iter()))
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
static_project_panel_project_items, static_project_panel_single_items, theme, Input, List,
|
static_project_panel_project_items, static_project_panel_single_items, theme, Input, List,
|
||||||
ListHeader, Panel, PanelSide, Theme,
|
ListHeader,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct ProjectPanel<V: 'static> {
|
pub struct ProjectPanel<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
view_type: PhantomData<V>,
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
current_side: PanelSide,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> ProjectPanel<V> {
|
impl<V: 'static> ProjectPanel<V> {
|
||||||
|
@ -19,32 +17,16 @@ impl<V: 'static> ProjectPanel<V> {
|
||||||
Self {
|
Self {
|
||||||
view_type: PhantomData,
|
view_type: PhantomData,
|
||||||
scroll_state,
|
scroll_state,
|
||||||
current_side: PanelSide::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn side(mut self, side: PanelSide) -> Self {
|
|
||||||
self.current_side = side;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
struct PanelPayload {
|
let theme = theme(cx);
|
||||||
pub theme: Arc<Theme>,
|
|
||||||
pub scroll_state: ScrollState,
|
|
||||||
}
|
|
||||||
|
|
||||||
Panel::new(
|
div()
|
||||||
self.scroll_state.clone(),
|
|
||||||
|_, payload| {
|
|
||||||
let payload = payload.downcast_ref::<PanelPayload>().unwrap();
|
|
||||||
|
|
||||||
let theme = payload.theme.clone();
|
|
||||||
|
|
||||||
vec![div()
|
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.w_56()
|
.w_full()
|
||||||
.h_full()
|
.h_full()
|
||||||
.px_2()
|
.px_2()
|
||||||
.fill(theme.middle.base.default.background)
|
.fill(theme.middle.base.default.background)
|
||||||
|
@ -53,20 +35,16 @@ impl<V: 'static> ProjectPanel<V> {
|
||||||
.w_56()
|
.w_56()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.overflow_y_scroll(payload.scroll_state.clone())
|
.overflow_y_scroll(ScrollState::default())
|
||||||
.child(
|
.child(
|
||||||
List::new(static_project_panel_single_items())
|
List::new(static_project_panel_single_items())
|
||||||
.header(
|
.header(ListHeader::new("FILES").set_toggle(ToggleState::Toggled))
|
||||||
ListHeader::new("FILES").set_toggle(ToggleState::Toggled),
|
|
||||||
)
|
|
||||||
.empty_message("No files in directory")
|
.empty_message("No files in directory")
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
List::new(static_project_panel_project_items())
|
List::new(static_project_panel_project_items())
|
||||||
.header(
|
.header(ListHeader::new("PROJECT").set_toggle(ToggleState::Toggled))
|
||||||
ListHeader::new("PROJECT").set_toggle(ToggleState::Toggled),
|
|
||||||
)
|
|
||||||
.empty_message("No folders in directory")
|
.empty_message("No folders in directory")
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
),
|
),
|
||||||
|
@ -76,12 +54,5 @@ impl<V: 'static> ProjectPanel<V> {
|
||||||
.value("buffe".to_string())
|
.value("buffe".to_string())
|
||||||
.state(InteractionState::Focused),
|
.state(InteractionState::Focused),
|
||||||
)
|
)
|
||||||
.into_any()]
|
|
||||||
},
|
|
||||||
Box::new(PanelPayload {
|
|
||||||
theme: theme(cx),
|
|
||||||
scroll_state: self.scroll_state.clone(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{theme, Icon, IconColor, IconElement, Label, LabelColor};
|
use crate::{theme, Icon, IconColor, IconElement, Label, LabelColor};
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element, Clone)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
title: String,
|
title: String,
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
|
|
|
@ -7,20 +7,27 @@ use crate::{theme, Icon, IconButton, Tab};
|
||||||
pub struct TabBar<V: 'static> {
|
pub struct TabBar<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
view_type: PhantomData<V>,
|
||||||
scroll_state: ScrollState,
|
scroll_state: ScrollState,
|
||||||
|
tabs: Vec<Tab>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> TabBar<V> {
|
impl<V: 'static> TabBar<V> {
|
||||||
pub fn new(scroll_state: ScrollState) -> Self {
|
pub fn new(tabs: Vec<Tab>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_type: PhantomData,
|
view_type: PhantomData,
|
||||||
scroll_state,
|
scroll_state: ScrollState::default(),
|
||||||
|
tabs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bind_scroll_state(&mut self, scroll_state: ScrollState) {
|
||||||
|
self.scroll_state = scroll_state;
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let can_navigate_back = true;
|
let can_navigate_back = true;
|
||||||
let can_navigate_forward = false;
|
let can_navigate_forward = false;
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -54,51 +61,7 @@ impl<V: 'static> TabBar<V> {
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.overflow_x_scroll(self.scroll_state.clone())
|
.overflow_x_scroll(self.scroll_state.clone())
|
||||||
.child(
|
.children(self.tabs.clone()),
|
||||||
Tab::new()
|
|
||||||
.title("Cargo.toml".to_string())
|
|
||||||
.current(false)
|
|
||||||
.git_status(GitStatus::Modified),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Tab::new()
|
|
||||||
.title("Channels Panel".to_string())
|
|
||||||
.current(false),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Tab::new()
|
|
||||||
.title("channels_panel.rs".to_string())
|
|
||||||
.current(true)
|
|
||||||
.git_status(GitStatus::Modified),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Tab::new()
|
|
||||||
.title("workspace.rs".to_string())
|
|
||||||
.current(false)
|
|
||||||
.git_status(GitStatus::Modified),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Tab::new()
|
|
||||||
.title("icon_button.rs".to_string())
|
|
||||||
.current(false),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Tab::new()
|
|
||||||
.title("storybook.rs".to_string())
|
|
||||||
.current(false)
|
|
||||||
.git_status(GitStatus::Created),
|
|
||||||
)
|
|
||||||
.child(Tab::new().title("theme.rs".to_string()).current(false))
|
|
||||||
.child(
|
|
||||||
Tab::new()
|
|
||||||
.title("theme_registry.rs".to_string())
|
|
||||||
.current(false),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Tab::new()
|
|
||||||
.title("styleable_helpers.rs".to_string())
|
|
||||||
.current(false),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// Right Side
|
// Right Side
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use gpui2::geometry::{relative, rems, Size};
|
use gpui2::geometry::{relative, rems, Size};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
@ -20,6 +22,7 @@ impl Terminal {
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
|
.w_full()
|
||||||
.child(
|
.child(
|
||||||
// Terminal Tabs.
|
// Terminal Tabs.
|
||||||
div()
|
div()
|
||||||
|
@ -70,8 +73,12 @@ impl Terminal {
|
||||||
width: relative(1.).into(),
|
width: relative(1.).into(),
|
||||||
height: rems(36.).into(),
|
height: rems(36.).into(),
|
||||||
},
|
},
|
||||||
|_, _| vec![],
|
|_, payload| {
|
||||||
Box::new(()),
|
let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
|
||||||
|
|
||||||
|
vec![crate::static_data::terminal_buffer(&theme).into_any()]
|
||||||
|
},
|
||||||
|
Box::new(theme),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,24 @@ use std::marker::PhantomData;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, PlayerWithCallStatus};
|
||||||
use crate::{
|
use crate::{
|
||||||
static_players_with_call_status, theme, Avatar, Button, Icon, IconButton, IconColor,
|
theme, Avatar, Button, Icon, IconButton, IconColor, PlayerStack, ToolDivider, TrafficLights,
|
||||||
PlayerStack, ToolDivider, TrafficLights,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Livestream {
|
||||||
|
pub players: Vec<PlayerWithCallStatus>,
|
||||||
|
pub channel: Option<String>, // projects
|
||||||
|
// windows
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Element)]
|
#[derive(Element)]
|
||||||
pub struct TitleBar<V: 'static> {
|
pub struct TitleBar<V: 'static> {
|
||||||
view_type: PhantomData<V>,
|
view_type: PhantomData<V>,
|
||||||
|
/// If the window is active from the OS's perspective.
|
||||||
is_active: Arc<AtomicBool>,
|
is_active: Arc<AtomicBool>,
|
||||||
|
livestream: Option<Livestream>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: 'static> TitleBar<V> {
|
impl<V: 'static> TitleBar<V> {
|
||||||
|
@ -28,14 +36,24 @@ impl<V: 'static> TitleBar<V> {
|
||||||
Self {
|
Self {
|
||||||
view_type: PhantomData,
|
view_type: PhantomData,
|
||||||
is_active,
|
is_active,
|
||||||
|
livestream: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_livestream(mut self, livestream: Option<Livestream>) -> Self {
|
||||||
|
self.livestream = livestream;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
let has_focus = cx.window_is_active();
|
let has_focus = cx.window_is_active();
|
||||||
|
|
||||||
let player_list = static_players_with_call_status().into_iter();
|
let player_list = if let Some(livestream) = &self.livestream {
|
||||||
|
livestream.players.clone().into_iter()
|
||||||
|
} else {
|
||||||
|
vec![].into_iter()
|
||||||
|
};
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -61,7 +79,8 @@ impl<V: 'static> TitleBar<V> {
|
||||||
.child(Button::new("zed"))
|
.child(Button::new("zed"))
|
||||||
.child(Button::new("nate/gpui2-ui-components")),
|
.child(Button::new("nate/gpui2-ui-components")),
|
||||||
)
|
)
|
||||||
.children(player_list.map(|p| PlayerStack::new(p))),
|
.children(player_list.map(|p| PlayerStack::new(p)))
|
||||||
|
.child(IconButton::new(Icon::Plus)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
|
|
|
@ -1,33 +1,49 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{theme, Breadcrumb, Icon, IconButton};
|
use crate::theme;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ToolbarItem {}
|
pub struct ToolbarItem {}
|
||||||
|
|
||||||
#[derive(Element, Clone)]
|
#[derive(Element)]
|
||||||
pub struct Toolbar {
|
pub struct Toolbar<V: 'static> {
|
||||||
items: Vec<ToolbarItem>,
|
left_items: HackyChildren<V>,
|
||||||
|
left_items_payload: HackyChildrenPayload,
|
||||||
|
right_items: HackyChildren<V>,
|
||||||
|
right_items_payload: HackyChildrenPayload,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Toolbar {
|
impl<V: 'static> Toolbar<V> {
|
||||||
pub fn new() -> Self {
|
pub fn new(
|
||||||
Self { items: Vec::new() }
|
left_items: HackyChildren<V>,
|
||||||
|
left_items_payload: HackyChildrenPayload,
|
||||||
|
right_items: HackyChildren<V>,
|
||||||
|
right_items_payload: HackyChildrenPayload,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
left_items,
|
||||||
|
left_items_payload,
|
||||||
|
right_items,
|
||||||
|
right_items_payload,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
let theme = theme(cx);
|
let theme = theme(cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.fill(theme.highest.base.default.background)
|
||||||
.p_2()
|
.p_2()
|
||||||
.flex()
|
.flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(Breadcrumb::new())
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.child(IconButton::new(Icon::InlayHint))
|
.children_any((self.left_items)(cx, self.left_items_payload.as_ref())),
|
||||||
.child(IconButton::new(Icon::MagnifyingGlass))
|
)
|
||||||
.child(IconButton::new(Icon::MagicWand)),
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.children_any((self.right_items)(cx, self.right_items_payload.as_ref())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use gpui2::geometry::{relative, rems, Size};
|
use gpui2::geometry::{relative, rems, Size};
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
theme, v_stack, ChatMessage, ChatPanel, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide,
|
hello_world_rust_editor_with_status_example, prelude::*, random_players_with_call_status,
|
||||||
ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
|
Livestream,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
theme, v_stack, ChatMessage, ChatPanel, EditorPane, Pane, PaneGroup, Panel, PanelAllowedSides,
|
||||||
|
PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Element, Default)]
|
#[derive(Element, Default)]
|
||||||
|
@ -17,6 +22,8 @@ pub struct WorkspaceElement {
|
||||||
|
|
||||||
impl WorkspaceElement {
|
impl WorkspaceElement {
|
||||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||||
|
let theme = theme(cx).clone();
|
||||||
|
|
||||||
let temp_size = rems(36.).into();
|
let temp_size = rems(36.).into();
|
||||||
|
|
||||||
let root_group = PaneGroup::new_groups(
|
let root_group = PaneGroup::new_groups(
|
||||||
|
@ -29,8 +36,15 @@ impl WorkspaceElement {
|
||||||
width: relative(1.).into(),
|
width: relative(1.).into(),
|
||||||
height: temp_size,
|
height: temp_size,
|
||||||
},
|
},
|
||||||
|_, _| vec![Terminal::new().into_any()],
|
|_, payload| {
|
||||||
Box::new(()),
|
let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
|
||||||
|
|
||||||
|
vec![EditorPane::new(hello_world_rust_editor_with_status_example(
|
||||||
|
&theme,
|
||||||
|
))
|
||||||
|
.into_any()]
|
||||||
|
},
|
||||||
|
Box::new(theme.clone()),
|
||||||
),
|
),
|
||||||
Pane::new(
|
Pane::new(
|
||||||
ScrollState::default(),
|
ScrollState::default(),
|
||||||
|
@ -51,8 +65,15 @@ impl WorkspaceElement {
|
||||||
width: relative(1.).into(),
|
width: relative(1.).into(),
|
||||||
height: relative(1.).into(),
|
height: relative(1.).into(),
|
||||||
},
|
},
|
||||||
|_, _| vec![Terminal::new().into_any()],
|
|_, payload| {
|
||||||
Box::new(()),
|
let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
|
||||||
|
|
||||||
|
vec![EditorPane::new(hello_world_rust_editor_with_status_example(
|
||||||
|
&theme,
|
||||||
|
))
|
||||||
|
.into_any()]
|
||||||
|
},
|
||||||
|
Box::new(theme.clone()),
|
||||||
)],
|
)],
|
||||||
SplitDirection::Vertical,
|
SplitDirection::Vertical,
|
||||||
),
|
),
|
||||||
|
@ -60,8 +81,6 @@ impl WorkspaceElement {
|
||||||
SplitDirection::Horizontal,
|
SplitDirection::Horizontal,
|
||||||
);
|
);
|
||||||
|
|
||||||
let theme = theme(cx).clone();
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -72,7 +91,10 @@ impl WorkspaceElement {
|
||||||
.items_start()
|
.items_start()
|
||||||
.text_color(theme.lowest.base.default.foreground)
|
.text_color(theme.lowest.base.default.foreground)
|
||||||
.fill(theme.lowest.base.default.background)
|
.fill(theme.lowest.base.default.background)
|
||||||
.child(TitleBar::new(cx))
|
.child(TitleBar::new(cx).set_livestream(Some(Livestream {
|
||||||
|
players: random_players_with_call_status(7),
|
||||||
|
channel: Some("gpui2-ui".to_string()),
|
||||||
|
})))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
@ -84,7 +106,11 @@ impl WorkspaceElement {
|
||||||
.border_b()
|
.border_b()
|
||||||
.border_color(theme.lowest.base.default.border)
|
.border_color(theme.lowest.base.default.border)
|
||||||
.child(
|
.child(
|
||||||
ProjectPanel::new(self.left_panel_scroll_state.clone())
|
Panel::new(
|
||||||
|
self.left_panel_scroll_state.clone(),
|
||||||
|
|_, payload| vec![ProjectPanel::new(ScrollState::default()).into_any()],
|
||||||
|
Box::new(()),
|
||||||
|
)
|
||||||
.side(PanelSide::Left),
|
.side(PanelSide::Left),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
@ -110,7 +136,12 @@ impl WorkspaceElement {
|
||||||
.side(PanelSide::Bottom),
|
.side(PanelSide::Bottom),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(ChatPanel::new(ScrollState::default()).with_messages(vec![
|
.child(
|
||||||
|
Panel::new(
|
||||||
|
self.right_panel_scroll_state.clone(),
|
||||||
|
|_, payload| {
|
||||||
|
vec![ChatPanel::new(ScrollState::default())
|
||||||
|
.with_messages(vec![
|
||||||
ChatMessage::new(
|
ChatMessage::new(
|
||||||
"osiewicz".to_string(),
|
"osiewicz".to_string(),
|
||||||
"is this thing on?".to_string(),
|
"is this thing on?".to_string(),
|
||||||
|
@ -129,7 +160,13 @@ impl WorkspaceElement {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.naive_local(),
|
.naive_local(),
|
||||||
),
|
),
|
||||||
])),
|
])
|
||||||
|
.into_any()]
|
||||||
|
},
|
||||||
|
Box::new(()),
|
||||||
|
)
|
||||||
|
.side(PanelSide::Right),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(StatusBar::new())
|
.child(StatusBar::new())
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@ pub enum Icon {
|
||||||
Plus,
|
Plus,
|
||||||
Quote,
|
Quote,
|
||||||
Screen,
|
Screen,
|
||||||
|
SelectAll,
|
||||||
Split,
|
Split,
|
||||||
SplitMessage,
|
SplitMessage,
|
||||||
Terminal,
|
Terminal,
|
||||||
|
@ -131,6 +132,7 @@ impl Icon {
|
||||||
Icon::Plus => "icons/plus.svg",
|
Icon::Plus => "icons/plus.svg",
|
||||||
Icon::Quote => "icons/quote.svg",
|
Icon::Quote => "icons/quote.svg",
|
||||||
Icon::Screen => "icons/desktop.svg",
|
Icon::Screen => "icons/desktop.svg",
|
||||||
|
Icon::SelectAll => "icons/select-all.svg",
|
||||||
Icon::Split => "icons/split.svg",
|
Icon::Split => "icons/split.svg",
|
||||||
Icon::SplitMessage => "icons/split_message.svg",
|
Icon::SplitMessage => "icons/split_message.svg",
|
||||||
Icon::Terminal => "icons/terminal.svg",
|
Icon::Terminal => "icons/terminal.svg",
|
||||||
|
|
|
@ -81,6 +81,7 @@ impl Input {
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.h_7()
|
.h_7()
|
||||||
|
.w_full()
|
||||||
.px_2()
|
.px_2()
|
||||||
.border()
|
.border()
|
||||||
.border_color(border_color_default)
|
.border_color(border_color_default)
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl PlayerCallStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(PartialEq, Clone)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
index: usize,
|
index: usize,
|
||||||
avatar_src: String,
|
avatar_src: String,
|
||||||
|
@ -73,6 +73,7 @@ pub struct Player {
|
||||||
status: PlayerStatus,
|
status: PlayerStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct PlayerWithCallStatus {
|
pub struct PlayerWithCallStatus {
|
||||||
player: Player,
|
player: Player,
|
||||||
call_status: PlayerCallStatus,
|
call_status: PlayerCallStatus,
|
||||||
|
|
|
@ -2,7 +2,7 @@ pub use gpui2::elements::div::{div, ScrollState};
|
||||||
pub use gpui2::style::{StyleHelpers, Styleable};
|
pub use gpui2::style::{StyleHelpers, Styleable};
|
||||||
pub use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
pub use gpui2::{Element, IntoElement, ParentElement, ViewContext};
|
||||||
|
|
||||||
pub use crate::{theme, ButtonVariant, HackyChildren, HackyChildrenPayload, InputVariant};
|
pub use crate::{theme, ButtonVariant, HackyChildren, HackyChildrenPayload, InputVariant, Theme};
|
||||||
|
|
||||||
use gpui2::{hsla, rgb, Hsla, WindowContext};
|
use gpui2::{hsla, rgb, Hsla, WindowContext};
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
|
@ -40,8 +40,7 @@ pub enum HighlightColor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HighlightColor {
|
impl HighlightColor {
|
||||||
pub fn hsla(&self, cx: &WindowContext) -> Hsla {
|
pub fn hsla(&self, theme: &Theme) -> Hsla {
|
||||||
let theme = theme(cx);
|
|
||||||
let system_color = SystemColor::new();
|
let system_color = SystemColor::new();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
@ -74,7 +73,7 @@ impl HighlightColor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, EnumIter)]
|
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||||
pub enum FileSystemStatus {
|
pub enum FileSystemStatus {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
|
@ -92,7 +91,7 @@ impl FileSystemStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
|
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||||
pub enum GitStatus {
|
pub enum GitStatus {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
|
@ -130,7 +129,7 @@ impl GitStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||||
pub enum DiagnosticStatus {
|
pub enum DiagnosticStatus {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
|
@ -139,14 +138,14 @@ pub enum DiagnosticStatus {
|
||||||
Info,
|
Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||||
pub enum IconSide {
|
pub enum IconSide {
|
||||||
#[default]
|
#[default]
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||||
pub enum OrderMethod {
|
pub enum OrderMethod {
|
||||||
#[default]
|
#[default]
|
||||||
Ascending,
|
Ascending,
|
||||||
|
@ -154,14 +153,14 @@ pub enum OrderMethod {
|
||||||
MostRecent,
|
MostRecent,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Copy)]
|
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||||
pub enum Shape {
|
pub enum Shape {
|
||||||
#[default]
|
#[default]
|
||||||
Circle,
|
Circle,
|
||||||
RoundedRectangle,
|
RoundedRectangle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Copy)]
|
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||||
pub enum DisclosureControlVisibility {
|
pub enum DisclosureControlVisibility {
|
||||||
#[default]
|
#[default]
|
||||||
OnHover,
|
OnHover,
|
||||||
|
|
|
@ -1,12 +1,109 @@
|
||||||
use gpui2::WindowContext;
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Buffer, BufferRow, BufferRows, GitStatus, HighlightColor, HighlightedLine, HighlightedText,
|
Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor,
|
||||||
Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListItem, MicStatus,
|
HighlightedLine, HighlightedText, Icon, Keybinding, Label, LabelColor, ListEntry,
|
||||||
ModifierKeys, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus,
|
ListEntrySize, ListItem, Livestream, MicStatus, ModifierKeys, PaletteItem, Player,
|
||||||
ToggleState,
|
PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, Theme, ToggleState,
|
||||||
|
VideoStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn static_tabs_example() -> Vec<Tab> {
|
||||||
|
vec![
|
||||||
|
Tab::new()
|
||||||
|
.title("wip.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false)
|
||||||
|
.fs_status(FileSystemStatus::Deleted),
|
||||||
|
Tab::new()
|
||||||
|
.title("Cargo.toml".to_string())
|
||||||
|
.icon(Icon::FileToml)
|
||||||
|
.current(false)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
Tab::new()
|
||||||
|
.title("Channels Panel".to_string())
|
||||||
|
.icon(Icon::Hash)
|
||||||
|
.current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("channels_panel.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(true)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
Tab::new()
|
||||||
|
.title("workspace.rs".to_string())
|
||||||
|
.current(false)
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
Tab::new()
|
||||||
|
.title("icon_button.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("storybook.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false)
|
||||||
|
.git_status(GitStatus::Created),
|
||||||
|
Tab::new()
|
||||||
|
.title("theme.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("theme_registry.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("styleable_helpers.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn static_tabs_1() -> Vec<Tab> {
|
||||||
|
vec![
|
||||||
|
Tab::new()
|
||||||
|
.title("project_panel.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false)
|
||||||
|
.fs_status(FileSystemStatus::Deleted),
|
||||||
|
Tab::new()
|
||||||
|
.title("tab_bar.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
Tab::new()
|
||||||
|
.title("workspace.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false),
|
||||||
|
Tab::new()
|
||||||
|
.title("tab.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(true)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn static_tabs_2() -> Vec<Tab> {
|
||||||
|
vec![
|
||||||
|
Tab::new()
|
||||||
|
.title("tab_bar.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(false)
|
||||||
|
.fs_status(FileSystemStatus::Deleted),
|
||||||
|
Tab::new()
|
||||||
|
.title("static_data.rs".to_string())
|
||||||
|
.icon(Icon::FileRust)
|
||||||
|
.current(true)
|
||||||
|
.git_status(GitStatus::Modified),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn static_tabs_3() -> Vec<Tab> {
|
||||||
|
vec![Tab::new().git_status(GitStatus::Created).current(true)]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn static_players() -> Vec<Player> {
|
pub fn static_players() -> Vec<Player> {
|
||||||
vec![
|
vec![
|
||||||
Player::new(
|
Player::new(
|
||||||
|
@ -37,6 +134,154 @@ pub fn static_players() -> Vec<Player> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PlayerData {
|
||||||
|
pub url: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
pub fn static_player_data() -> Vec<PlayerData> {
|
||||||
|
vec![
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/1714999?v=4".into(),
|
||||||
|
name: "iamnbutler".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/326587?v=4".into(),
|
||||||
|
name: "maxbrunsfeld".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/482957?v=4".into(),
|
||||||
|
name: "as-cii".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/1789?v=4".into(),
|
||||||
|
name: "nathansobo".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(),
|
||||||
|
name: "ForLoveOfCats".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/2690773?v=4".into(),
|
||||||
|
name: "SomeoneToIgnore".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/19867440?v=4".into(),
|
||||||
|
name: "JosephTLyons".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/24362066?v=4".into(),
|
||||||
|
name: "osiewicz".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/22121886?v=4".into(),
|
||||||
|
name: "KCaverly".into(),
|
||||||
|
},
|
||||||
|
PlayerData {
|
||||||
|
url: "https://avatars.githubusercontent.com/u/1486634?v=4".into(),
|
||||||
|
name: "maxdeviant".into(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
pub fn create_static_players(player_data: Vec<PlayerData>) -> Vec<Player> {
|
||||||
|
let mut players = Vec::new();
|
||||||
|
for data in player_data {
|
||||||
|
players.push(Player::new(players.len(), data.url, data.name));
|
||||||
|
}
|
||||||
|
players
|
||||||
|
}
|
||||||
|
pub fn static_player_1(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(1, data[0].url.clone(), data[0].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_2(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(2, data[1].url.clone(), data[1].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_3(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(3, data[2].url.clone(), data[2].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_4(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(4, data[3].url.clone(), data[3].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_5(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(5, data[4].url.clone(), data[4].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_6(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(6, data[5].url.clone(), data[5].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_7(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(7, data[6].url.clone(), data[6].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_8(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(8, data[7].url.clone(), data[7].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_9(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(9, data[8].url.clone(), data[8].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_player_10(data: &Vec<PlayerData>) -> Player {
|
||||||
|
Player::new(10, data[9].url.clone(), data[9].name.clone())
|
||||||
|
}
|
||||||
|
pub fn static_livestream() -> Livestream {
|
||||||
|
Livestream {
|
||||||
|
players: random_players_with_call_status(7),
|
||||||
|
channel: Some("gpui2-ui".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn populate_player_call_status(
|
||||||
|
player: Player,
|
||||||
|
followers: Option<Vec<Player>>,
|
||||||
|
) -> PlayerCallStatus {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let in_current_project: bool = rng.gen();
|
||||||
|
let disconnected: bool = rng.gen();
|
||||||
|
let voice_activity: f32 = rng.gen();
|
||||||
|
let mic_status = if rng.gen_bool(0.5) {
|
||||||
|
MicStatus::Muted
|
||||||
|
} else {
|
||||||
|
MicStatus::Unmuted
|
||||||
|
};
|
||||||
|
let video_status = if rng.gen_bool(0.5) {
|
||||||
|
VideoStatus::On
|
||||||
|
} else {
|
||||||
|
VideoStatus::Off
|
||||||
|
};
|
||||||
|
let screen_share_status = if rng.gen_bool(0.5) {
|
||||||
|
ScreenShareStatus::Shared
|
||||||
|
} else {
|
||||||
|
ScreenShareStatus::NotShared
|
||||||
|
};
|
||||||
|
PlayerCallStatus {
|
||||||
|
mic_status,
|
||||||
|
voice_activity,
|
||||||
|
video_status,
|
||||||
|
screen_share_status,
|
||||||
|
in_current_project,
|
||||||
|
disconnected,
|
||||||
|
following: None,
|
||||||
|
followers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn random_players_with_call_status(number_of_players: usize) -> Vec<PlayerWithCallStatus> {
|
||||||
|
let players = create_static_players(static_player_data());
|
||||||
|
let mut player_status = vec![];
|
||||||
|
for i in 0..number_of_players {
|
||||||
|
let followers = if i == 0 {
|
||||||
|
Some(vec![
|
||||||
|
players[1].clone(),
|
||||||
|
players[3].clone(),
|
||||||
|
players[5].clone(),
|
||||||
|
players[6].clone(),
|
||||||
|
])
|
||||||
|
} else if i == 1 {
|
||||||
|
Some(vec![players[2].clone(), players[6].clone()])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let call_status = populate_player_call_status(players[i].clone(), followers);
|
||||||
|
player_status.push(PlayerWithCallStatus::new(players[i].clone(), call_status));
|
||||||
|
}
|
||||||
|
player_status
|
||||||
|
}
|
||||||
|
|
||||||
pub fn static_players_with_call_status() -> Vec<PlayerWithCallStatus> {
|
pub fn static_players_with_call_status() -> Vec<PlayerWithCallStatus> {
|
||||||
let players = static_players();
|
let players = static_players();
|
||||||
let mut player_0_status = PlayerCallStatus::new();
|
let mut player_0_status = PlayerCallStatus::new();
|
||||||
|
@ -123,7 +368,7 @@ pub fn static_project_panel_project_items() -> Vec<ListItem> {
|
||||||
.left_icon(Icon::FolderOpen.into())
|
.left_icon(Icon::FolderOpen.into())
|
||||||
.indent_level(3)
|
.indent_level(3)
|
||||||
.set_toggle(ToggleState::Toggled),
|
.set_toggle(ToggleState::Toggled),
|
||||||
ListEntry::new(Label::new("derrive_element.rs"))
|
ListEntry::new(Label::new("derive_element.rs"))
|
||||||
.left_icon(Icon::FileRust.into())
|
.left_icon(Icon::FileRust.into())
|
||||||
.indent_level(4),
|
.indent_level(4),
|
||||||
ListEntry::new(Label::new("storybook").color(LabelColor::Modified))
|
ListEntry::new(Label::new("storybook").color(LabelColor::Modified))
|
||||||
|
@ -337,33 +582,49 @@ pub fn example_editor_actions() -> Vec<PaletteItem> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn empty_buffer_example<V: 'static>() -> Buffer<V> {
|
pub fn empty_editor_example() -> Editor {
|
||||||
|
Editor {
|
||||||
|
tabs: static_tabs_example(),
|
||||||
|
path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
|
||||||
|
symbols: vec![],
|
||||||
|
buffer: empty_buffer_example(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty_buffer_example() -> Buffer {
|
||||||
Buffer::new().set_rows(Some(BufferRows::default()))
|
Buffer::new().set_rows(Some(BufferRows::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hello_world_rust_buffer_example<V: 'static>(cx: &WindowContext) -> Buffer<V> {
|
pub fn hello_world_rust_editor_example(theme: &Theme) -> Editor {
|
||||||
|
Editor {
|
||||||
|
tabs: static_tabs_example(),
|
||||||
|
path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
|
||||||
|
symbols: vec![Symbol(vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "fn ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "main".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
|
},
|
||||||
|
])],
|
||||||
|
buffer: hello_world_rust_buffer_example(theme),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hello_world_rust_buffer_example(theme: &Theme) -> Buffer {
|
||||||
Buffer::new()
|
Buffer::new()
|
||||||
.set_title("hello_world.rs".to_string())
|
.set_title("hello_world.rs".to_string())
|
||||||
.set_path("src/hello_world.rs".to_string())
|
.set_path("src/hello_world.rs".to_string())
|
||||||
.set_language("rust".to_string())
|
.set_language("rust".to_string())
|
||||||
.set_rows(Some(BufferRows {
|
.set_rows(Some(BufferRows {
|
||||||
show_line_numbers: true,
|
show_line_numbers: true,
|
||||||
rows: hello_world_rust_buffer_rows(cx),
|
rows: hello_world_rust_buffer_rows(theme),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hello_world_rust_buffer_with_status_example<V: 'static>(cx: &WindowContext) -> Buffer<V> {
|
pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
|
||||||
Buffer::new()
|
|
||||||
.set_title("hello_world.rs".to_string())
|
|
||||||
.set_path("src/hello_world.rs".to_string())
|
|
||||||
.set_language("rust".to_string())
|
|
||||||
.set_rows(Some(BufferRows {
|
|
||||||
show_line_numbers: true,
|
|
||||||
rows: hello_world_rust_with_status_buffer_rows(cx),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
|
||||||
let show_line_number = true;
|
let show_line_number = true;
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
|
@ -375,15 +636,15 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
||||||
highlighted_texts: vec![
|
highlighted_texts: vec![
|
||||||
HighlightedText {
|
HighlightedText {
|
||||||
text: "fn ".to_string(),
|
text: "fn ".to_string(),
|
||||||
color: HighlightColor::Keyword.hsla(cx),
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
},
|
},
|
||||||
HighlightedText {
|
HighlightedText {
|
||||||
text: "main".to_string(),
|
text: "main".to_string(),
|
||||||
color: HighlightColor::Function.hsla(cx),
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
},
|
},
|
||||||
HighlightedText {
|
HighlightedText {
|
||||||
text: "() {".to_string(),
|
text: "() {".to_string(),
|
||||||
color: HighlightColor::Default.hsla(cx),
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -399,7 +660,7 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
||||||
highlighted_texts: vec![HighlightedText {
|
highlighted_texts: vec![HighlightedText {
|
||||||
text: " // Statements here are executed when the compiled binary is called."
|
text: " // Statements here are executed when the compiled binary is called."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
color: HighlightColor::Comment.hsla(cx),
|
color: HighlightColor::Comment.hsla(&theme),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
cursors: None,
|
cursors: None,
|
||||||
|
@ -422,7 +683,7 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
||||||
line: Some(HighlightedLine {
|
line: Some(HighlightedLine {
|
||||||
highlighted_texts: vec![HighlightedText {
|
highlighted_texts: vec![HighlightedText {
|
||||||
text: " // Print text to the console.".to_string(),
|
text: " // Print text to the console.".to_string(),
|
||||||
color: HighlightColor::Comment.hsla(cx),
|
color: HighlightColor::Comment.hsla(&theme),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
cursors: None,
|
cursors: None,
|
||||||
|
@ -433,10 +694,34 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
||||||
line_number: 5,
|
line_number: 5,
|
||||||
code_action: false,
|
code_action: false,
|
||||||
current: false,
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: " println!(".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "\"Hello, world!\"".to_string(),
|
||||||
|
color: HighlightColor::String.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: ");".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 6,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
line: Some(HighlightedLine {
|
line: Some(HighlightedLine {
|
||||||
highlighted_texts: vec![HighlightedText {
|
highlighted_texts: vec![HighlightedText {
|
||||||
text: "}".to_string(),
|
text: "}".to_string(),
|
||||||
color: HighlightColor::Default.hsla(cx),
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
cursors: None,
|
cursors: None,
|
||||||
|
@ -446,7 +731,36 @@ pub fn hello_world_rust_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<BufferRow> {
|
pub fn hello_world_rust_editor_with_status_example(theme: &Theme) -> Editor {
|
||||||
|
Editor {
|
||||||
|
tabs: static_tabs_example(),
|
||||||
|
path: PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
|
||||||
|
symbols: vec![Symbol(vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "fn ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "main".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
|
},
|
||||||
|
])],
|
||||||
|
buffer: hello_world_rust_buffer_with_status_example(theme),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hello_world_rust_buffer_with_status_example(theme: &Theme) -> Buffer {
|
||||||
|
Buffer::new()
|
||||||
|
.set_title("hello_world.rs".to_string())
|
||||||
|
.set_path("src/hello_world.rs".to_string())
|
||||||
|
.set_language("rust".to_string())
|
||||||
|
.set_rows(Some(BufferRows {
|
||||||
|
show_line_numbers: true,
|
||||||
|
rows: hello_world_rust_with_status_buffer_rows(theme),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
|
||||||
let show_line_number = true;
|
let show_line_number = true;
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
|
@ -458,15 +772,15 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
|
||||||
highlighted_texts: vec![
|
highlighted_texts: vec![
|
||||||
HighlightedText {
|
HighlightedText {
|
||||||
text: "fn ".to_string(),
|
text: "fn ".to_string(),
|
||||||
color: HighlightColor::Keyword.hsla(cx),
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
},
|
},
|
||||||
HighlightedText {
|
HighlightedText {
|
||||||
text: "main".to_string(),
|
text: "main".to_string(),
|
||||||
color: HighlightColor::Function.hsla(cx),
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
},
|
},
|
||||||
HighlightedText {
|
HighlightedText {
|
||||||
text: "() {".to_string(),
|
text: "() {".to_string(),
|
||||||
color: HighlightColor::Default.hsla(cx),
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
@ -482,7 +796,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
|
||||||
highlighted_texts: vec![HighlightedText {
|
highlighted_texts: vec![HighlightedText {
|
||||||
text: "// Statements here are executed when the compiled binary is called."
|
text: "// Statements here are executed when the compiled binary is called."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
color: HighlightColor::Comment.hsla(cx),
|
color: HighlightColor::Comment.hsla(&theme),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
cursors: None,
|
cursors: None,
|
||||||
|
@ -505,7 +819,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
|
||||||
line: Some(HighlightedLine {
|
line: Some(HighlightedLine {
|
||||||
highlighted_texts: vec![HighlightedText {
|
highlighted_texts: vec![HighlightedText {
|
||||||
text: " // Print text to the console.".to_string(),
|
text: " // Print text to the console.".to_string(),
|
||||||
color: HighlightColor::Comment.hsla(cx),
|
color: HighlightColor::Comment.hsla(&theme),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
cursors: None,
|
cursors: None,
|
||||||
|
@ -517,10 +831,20 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
|
||||||
code_action: false,
|
code_action: false,
|
||||||
current: false,
|
current: false,
|
||||||
line: Some(HighlightedLine {
|
line: Some(HighlightedLine {
|
||||||
highlighted_texts: vec![HighlightedText {
|
highlighted_texts: vec![
|
||||||
text: "}".to_string(),
|
HighlightedText {
|
||||||
color: HighlightColor::Default.hsla(cx),
|
text: " println!(".to_string(),
|
||||||
}],
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "\"Hello, world!\"".to_string(),
|
||||||
|
color: HighlightColor::String.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: ");".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
cursors: None,
|
cursors: None,
|
||||||
status: GitStatus::None,
|
status: GitStatus::None,
|
||||||
|
@ -532,12 +856,12 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
|
||||||
current: false,
|
current: false,
|
||||||
line: Some(HighlightedLine {
|
line: Some(HighlightedLine {
|
||||||
highlighted_texts: vec![HighlightedText {
|
highlighted_texts: vec![HighlightedText {
|
||||||
text: "".to_string(),
|
text: "}".to_string(),
|
||||||
color: HighlightColor::Default.hsla(cx),
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
cursors: None,
|
cursors: None,
|
||||||
status: GitStatus::Created,
|
status: GitStatus::None,
|
||||||
show_line_number,
|
show_line_number,
|
||||||
},
|
},
|
||||||
BufferRow {
|
BufferRow {
|
||||||
|
@ -546,8 +870,22 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
|
||||||
current: false,
|
current: false,
|
||||||
line: Some(HighlightedLine {
|
line: Some(HighlightedLine {
|
||||||
highlighted_texts: vec![HighlightedText {
|
highlighted_texts: vec![HighlightedText {
|
||||||
text: "Marshall and Nate were here".to_string(),
|
text: "".to_string(),
|
||||||
color: HighlightColor::Default.hsla(cx),
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::Created,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 8,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: "// Marshall and Nate were here".to_string(),
|
||||||
|
color: HighlightColor::Comment.hsla(&theme),
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
cursors: None,
|
cursors: None,
|
||||||
|
@ -556,3 +894,73 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &WindowContext) -> Vec<Buffe
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn terminal_buffer(theme: &Theme) -> Buffer {
|
||||||
|
Buffer::new()
|
||||||
|
.set_title("zed — fish".to_string())
|
||||||
|
.set_rows(Some(BufferRows {
|
||||||
|
show_line_numbers: false,
|
||||||
|
rows: terminal_buffer_rows(theme),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn terminal_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
|
||||||
|
let show_line_number = false;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
BufferRow {
|
||||||
|
line_number: 1,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![
|
||||||
|
HighlightedText {
|
||||||
|
text: "maxdeviant ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "in ".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "profaned-capital ".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "in ".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "~/p/zed ".to_string(),
|
||||||
|
color: HighlightColor::Function.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: "on ".to_string(),
|
||||||
|
color: HighlightColor::Default.hsla(&theme),
|
||||||
|
},
|
||||||
|
HighlightedText {
|
||||||
|
text: " gpui2-ui ".to_string(),
|
||||||
|
color: HighlightColor::Keyword.hsla(&theme),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
BufferRow {
|
||||||
|
line_number: 2,
|
||||||
|
code_action: false,
|
||||||
|
current: false,
|
||||||
|
line: Some(HighlightedLine {
|
||||||
|
highlighted_texts: vec![HighlightedText {
|
||||||
|
text: "λ ".to_string(),
|
||||||
|
color: HighlightColor::String.hsla(&theme),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
cursors: None,
|
||||||
|
status: GitStatus::None,
|
||||||
|
show_line_number,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@ impl Member {
|
||||||
|_, _| {
|
|_, _| {
|
||||||
Label::new(
|
Label::new(
|
||||||
format!(
|
format!(
|
||||||
"Follow {} on their active project",
|
"Follow {} to their active project",
|
||||||
leader_user.github_login,
|
leader_user.github_login,
|
||||||
),
|
),
|
||||||
theme
|
theme
|
||||||
|
|
|
@ -2520,19 +2520,13 @@ impl Workspace {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_follow(
|
fn start_following(
|
||||||
&mut self,
|
&mut self,
|
||||||
leader_id: PeerId,
|
leader_id: PeerId,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<Task<Result<()>>> {
|
) -> Option<Task<Result<()>>> {
|
||||||
let pane = self.active_pane().clone();
|
let pane = self.active_pane().clone();
|
||||||
|
|
||||||
if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
|
|
||||||
if leader_id == prev_leader_id {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.last_leaders_by_pane
|
self.last_leaders_by_pane
|
||||||
.insert(pane.downgrade(), leader_id);
|
.insert(pane.downgrade(), leader_id);
|
||||||
self.follower_states_by_leader
|
self.follower_states_by_leader
|
||||||
|
@ -2603,9 +2597,64 @@ impl Workspace {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
next_leader_id
|
let pane = self.active_pane.clone();
|
||||||
.or_else(|| collaborators.keys().copied().next())
|
let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
|
||||||
.and_then(|leader_id| self.toggle_follow(leader_id, cx))
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if Some(leader_id) == self.unfollow(&pane, cx) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.follow(leader_id, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn follow(
|
||||||
|
&mut self,
|
||||||
|
leader_id: PeerId,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<Task<Result<()>>> {
|
||||||
|
let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
|
||||||
|
let project = self.project.read(cx);
|
||||||
|
|
||||||
|
let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let other_project_id = match remote_participant.location {
|
||||||
|
call::ParticipantLocation::External => None,
|
||||||
|
call::ParticipantLocation::UnsharedProject => None,
|
||||||
|
call::ParticipantLocation::SharedProject { project_id } => {
|
||||||
|
if Some(project_id) == project.remote_id() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(project_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// if they are active in another project, follow there.
|
||||||
|
if let Some(project_id) = other_project_id {
|
||||||
|
let app_state = self.app_state.clone();
|
||||||
|
return Some(crate::join_remote_project(
|
||||||
|
project_id,
|
||||||
|
remote_participant.user.id,
|
||||||
|
app_state,
|
||||||
|
cx,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// if you're already following, find the right pane and focus it.
|
||||||
|
for (existing_leader_id, states_by_pane) in &mut self.follower_states_by_leader {
|
||||||
|
if leader_id == *existing_leader_id {
|
||||||
|
for (pane, _) in states_by_pane {
|
||||||
|
cx.focus(pane);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, follow.
|
||||||
|
self.start_following(leader_id, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unfollow(
|
pub fn unfollow(
|
||||||
|
@ -4197,21 +4246,20 @@ pub fn join_remote_project(
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let existing_workspace = cx
|
let windows = cx.windows();
|
||||||
.windows()
|
let existing_workspace = windows.into_iter().find_map(|window| {
|
||||||
.into_iter()
|
|
||||||
.find_map(|window| {
|
|
||||||
window.downcast::<Workspace>().and_then(|window| {
|
window.downcast::<Workspace>().and_then(|window| {
|
||||||
window.read_root_with(&cx, |workspace, cx| {
|
window
|
||||||
|
.read_root_with(&cx, |workspace, cx| {
|
||||||
if workspace.project().read(cx).remote_id() == Some(project_id) {
|
if workspace.project().read(cx).remote_id() == Some(project_id) {
|
||||||
Some(cx.handle().downgrade())
|
Some(cx.handle().downgrade())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.unwrap_or(None)
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
.flatten();
|
|
||||||
|
|
||||||
let workspace = if let Some(existing_workspace) = existing_workspace {
|
let workspace = if let Some(existing_workspace) = existing_workspace {
|
||||||
existing_workspace
|
existing_workspace
|
||||||
|
@ -4276,13 +4324,11 @@ pub fn join_remote_project(
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(follow_peer_id) = follow_peer_id {
|
if let Some(follow_peer_id) = follow_peer_id {
|
||||||
if !workspace.is_being_followed(follow_peer_id) {
|
|
||||||
workspace
|
workspace
|
||||||
.toggle_follow(follow_peer_id, cx)
|
.follow(follow_peer_id, cx)
|
||||||
.map(|follow| follow.detach_and_log_err(cx));
|
.map(|follow| follow.detach_and_log_err(cx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
[(line_comment) (attribute_item)]* @context
|
[(line_comment) (attribute_item)]* @context
|
||||||
.
|
.
|
||||||
[
|
[
|
||||||
|
|
||||||
(struct_item
|
(struct_item
|
||||||
name: (_) @name)
|
name: (_) @name)
|
||||||
|
|
||||||
|
@ -26,3 +27,6 @@
|
||||||
name: (_) @name)
|
name: (_) @name)
|
||||||
] @item
|
] @item
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(attribute_item) @collapse
|
||||||
|
(use_declaration) @collapse
|
||||||
|
|
|
@ -177,7 +177,7 @@ fn main() {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
client.telemetry().start(installation_id);
|
client.telemetry().start(installation_id, cx);
|
||||||
|
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
languages,
|
languages,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { icon_button, toggleable_icon_button, toggleable_text_button } from "../component"
|
import { icon_button, text_button, toggleable_icon_button, toggleable_text_button } from "../component"
|
||||||
import { interactive, toggleable } from "../element"
|
import { interactive, toggleable } from "../element"
|
||||||
import { useTheme, with_opacity } from "../theme"
|
import { useTheme, with_opacity } from "../theme"
|
||||||
import { background, border, foreground, text } from "./components"
|
import { background, border, foreground, text } from "./components"
|
||||||
|
@ -191,6 +191,12 @@ export function titlebar(): any {
|
||||||
color: "variant",
|
color: "variant",
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
project_host: text_button({
|
||||||
|
text_properties: {
|
||||||
|
weight: "bold"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
// Collaborators
|
// Collaborators
|
||||||
leader_avatar: {
|
leader_avatar: {
|
||||||
width: avatar_width,
|
width: avatar_width,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue