Compare commits
2 Commits
acp-rewind
...
joao-gpui-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cd9c3f3cb | ||
|
|
bceb10a16b |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3192,6 +3192,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"linkme",
|
||||
"parking_lot",
|
||||
"theme",
|
||||
|
||||
@@ -497,6 +497,7 @@ impl Render for ContextPillPreview {
|
||||
// TODO: Component commented out due to new dependency on `Project`.
|
||||
/*
|
||||
impl Component for AddedContext {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Agent
|
||||
}
|
||||
|
||||
@@ -98,11 +98,16 @@ impl RenderOnce for UsageBanner {
|
||||
}
|
||||
|
||||
impl Component for UsageBanner {
|
||||
type InitialState = ();
|
||||
fn sort_name() -> &'static str {
|
||||
"AgentUsageBanner"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
let trial_limit = Plan::ZedProTrial.model_requests_limit();
|
||||
let trial_examples = vec![
|
||||
single_example(
|
||||
|
||||
@@ -24,6 +24,8 @@ use ui::{Disclosure, Tooltip, Window, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct EditFileTool;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct EditFileToolInput {
|
||||
/// A user-friendly markdown description of what's being replaced. This will be shown in the UI.
|
||||
@@ -75,8 +77,6 @@ struct PartialInput {
|
||||
new_string: String,
|
||||
}
|
||||
|
||||
pub struct EditFileTool;
|
||||
|
||||
const DEFAULT_UI_TEXT: &str = "Editing file";
|
||||
|
||||
impl Tool for EditFileTool {
|
||||
@@ -627,7 +627,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn still_streaming_ui_text_with_path() {
|
||||
let tool = EditFileTool;
|
||||
let input = json!({
|
||||
"path": "src/main.rs",
|
||||
"display_description": "",
|
||||
@@ -635,12 +634,11 @@ mod tests {
|
||||
"new_string": "new code"
|
||||
});
|
||||
|
||||
assert_eq!(tool.still_streaming_ui_text(&input), "src/main.rs");
|
||||
assert_eq!(EditFileTool.still_streaming_ui_text(&input), "src/main.rs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn still_streaming_ui_text_with_description() {
|
||||
let tool = EditFileTool;
|
||||
let input = json!({
|
||||
"path": "",
|
||||
"display_description": "Fix error handling",
|
||||
@@ -648,12 +646,14 @@ mod tests {
|
||||
"new_string": "new code"
|
||||
});
|
||||
|
||||
assert_eq!(tool.still_streaming_ui_text(&input), "Fix error handling");
|
||||
assert_eq!(
|
||||
EditFileTool.still_streaming_ui_text(&input),
|
||||
"Fix error handling",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn still_streaming_ui_text_with_path_and_description() {
|
||||
let tool = EditFileTool;
|
||||
let input = json!({
|
||||
"path": "src/main.rs",
|
||||
"display_description": "Fix error handling",
|
||||
@@ -661,12 +661,14 @@ mod tests {
|
||||
"new_string": "new code"
|
||||
});
|
||||
|
||||
assert_eq!(tool.still_streaming_ui_text(&input), "Fix error handling");
|
||||
assert_eq!(
|
||||
EditFileTool.still_streaming_ui_text(&input),
|
||||
"Fix error handling",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn still_streaming_ui_text_no_path_or_description() {
|
||||
let tool = EditFileTool;
|
||||
let input = json!({
|
||||
"path": "",
|
||||
"display_description": "",
|
||||
@@ -674,14 +676,19 @@ mod tests {
|
||||
"new_string": "new code"
|
||||
});
|
||||
|
||||
assert_eq!(tool.still_streaming_ui_text(&input), DEFAULT_UI_TEXT);
|
||||
assert_eq!(
|
||||
EditFileTool.still_streaming_ui_text(&input),
|
||||
DEFAULT_UI_TEXT,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn still_streaming_ui_text_with_null() {
|
||||
let tool = EditFileTool;
|
||||
let input = serde_json::Value::Null;
|
||||
|
||||
assert_eq!(tool.still_streaming_ui_text(&input), DEFAULT_UI_TEXT);
|
||||
assert_eq!(
|
||||
EditFileTool.still_streaming_ui_text(&input),
|
||||
DEFAULT_UI_TEXT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use futures::io::BufReader;
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt, FutureExt};
|
||||
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
|
||||
use assistant_tool::{ActionLog, Tool, ToolCard, ToolResult, ToolUseStatus};
|
||||
use component::Component;
|
||||
use futures::{
|
||||
AsyncBufReadExt, SinkExt, StreamExt,
|
||||
channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded},
|
||||
io::BufReader,
|
||||
stream::SelectAll,
|
||||
};
|
||||
use gpui::{AnyElement, AnyWindowHandle, App, AppContext, Entity, Task, WeakEntity, Window};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::future;
|
||||
use util::get_system_shell;
|
||||
use std::{path::Path, process::Stdio, sync::Arc, time::Duration};
|
||||
use ui::{ComponentScope, IconName, RegisterComponent, prelude::*};
|
||||
use util::{command::new_smol_command, get_system_shell, markdown::MarkdownString};
|
||||
use workspace::Workspace;
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use ui::IconName;
|
||||
use util::command::new_smol_command;
|
||||
use util::markdown::MarkdownString;
|
||||
const COMMAND_OUTPUT_LIMIT: usize = 16 * 1024;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct TerminalToolInput {
|
||||
@@ -25,6 +28,7 @@ pub struct TerminalToolInput {
|
||||
cd: String,
|
||||
}
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct TerminalTool;
|
||||
|
||||
impl Tool for TerminalTool {
|
||||
@@ -86,176 +90,186 @@ impl Tool for TerminalTool {
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))).into(),
|
||||
};
|
||||
|
||||
let project = project.read(cx);
|
||||
let input_path = Path::new(&input.cd);
|
||||
let working_dir = if input.cd == "." {
|
||||
// Accept "." as meaning "the one worktree" if we only have one worktree.
|
||||
let mut worktrees = project.worktrees(cx);
|
||||
|
||||
let only_worktree = match worktrees.next() {
|
||||
Some(worktree) => worktree,
|
||||
None => {
|
||||
return Task::ready(Err(anyhow!("No worktrees found in the project"))).into();
|
||||
}
|
||||
};
|
||||
|
||||
if worktrees.next().is_some() {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"'.' is ambiguous in multi-root workspaces. Please specify a root directory explicitly."
|
||||
))).into();
|
||||
}
|
||||
|
||||
only_worktree.read(cx).abs_path()
|
||||
} else if input_path.is_absolute() {
|
||||
// Absolute paths are allowed, but only if they're in one of the project's worktrees.
|
||||
if !project
|
||||
.worktrees(cx)
|
||||
.any(|worktree| input_path.starts_with(&worktree.read(cx).abs_path()))
|
||||
{
|
||||
return Task::ready(Err(anyhow!(
|
||||
"The absolute path must be within one of the project's worktrees"
|
||||
)))
|
||||
.into();
|
||||
}
|
||||
|
||||
input_path.into()
|
||||
} else {
|
||||
let Some(worktree) = project.worktree_for_root_name(&input.cd, cx) else {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"`cd` directory {} not found in the project",
|
||||
&input.cd
|
||||
)))
|
||||
.into();
|
||||
};
|
||||
|
||||
worktree.read(cx).abs_path()
|
||||
let working_dir = match working_dir(cx, &input, &project, input_path) {
|
||||
Ok(dir) => dir,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))).into(),
|
||||
};
|
||||
|
||||
cx.background_spawn(run_command_limited(working_dir, input.command))
|
||||
.into()
|
||||
let (line_sender, line_receiver) = unbounded();
|
||||
|
||||
let output = spawn_command_and_stream(working_dir, input.command, line_sender, cx);
|
||||
let output = match output {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Task::ready(Err(err)).into(),
|
||||
};
|
||||
|
||||
let card = cx.new(|cx| TerminalToolCard::new(line_receiver, cx));
|
||||
|
||||
ToolResult {
|
||||
output,
|
||||
card: Some(card.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const LIMIT: usize = 16 * 1024;
|
||||
|
||||
async fn run_command_limited(working_dir: Arc<Path>, command: String) -> Result<String> {
|
||||
/// Run a command until completion and return the output.
|
||||
///
|
||||
/// Also stream each line through a channel that can be accessed via the returned
|
||||
/// receiver, the channel will only receive updates if the future is awaited.
|
||||
fn spawn_command_and_stream(
|
||||
working_dir: Arc<Path>,
|
||||
command: String,
|
||||
mut line_sender: UnboundedSender<Result<String>>,
|
||||
cx: &mut App,
|
||||
) -> Result<Task<Result<String>>> {
|
||||
let shell = get_system_shell();
|
||||
|
||||
let mut cmd = new_smol_command(&shell)
|
||||
.arg("-c")
|
||||
.arg(&command)
|
||||
.args(["-c", &command])
|
||||
.current_dir(working_dir)
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.context("Failed to execute terminal command")?;
|
||||
|
||||
let mut combined_buffer = String::with_capacity(LIMIT + 1);
|
||||
|
||||
let mut out_reader = BufReader::new(cmd.stdout.take().context("Failed to get stdout")?);
|
||||
let mut out_tmp_buffer = String::with_capacity(512);
|
||||
let mut err_reader = BufReader::new(cmd.stderr.take().context("Failed to get stderr")?);
|
||||
let mut err_tmp_buffer = String::with_capacity(512);
|
||||
|
||||
let mut out_line = Box::pin(
|
||||
out_reader
|
||||
.read_line(&mut out_tmp_buffer)
|
||||
.left_future()
|
||||
.fuse(),
|
||||
let mut line_stream = SelectAll::new();
|
||||
line_stream.push(
|
||||
BufReader::new(cmd.stdout.take().context("Failed to get stdout")?)
|
||||
.lines()
|
||||
.boxed(),
|
||||
);
|
||||
let mut err_line = Box::pin(
|
||||
err_reader
|
||||
.read_line(&mut err_tmp_buffer)
|
||||
.left_future()
|
||||
.fuse(),
|
||||
line_stream.push(
|
||||
BufReader::new(cmd.stderr.take().context("Failed to get stderr")?)
|
||||
.lines()
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
let mut has_stdout = true;
|
||||
let mut has_stderr = true;
|
||||
while (has_stdout || has_stderr) && combined_buffer.len() < LIMIT + 1 {
|
||||
futures::select_biased! {
|
||||
read = out_line => {
|
||||
drop(out_line);
|
||||
combined_buffer.extend(out_tmp_buffer.drain(..));
|
||||
if read? == 0 {
|
||||
out_line = Box::pin(future::pending().right_future().fuse());
|
||||
has_stdout = false;
|
||||
} else {
|
||||
out_line = Box::pin(out_reader.read_line(&mut out_tmp_buffer).left_future().fuse());
|
||||
let fut = cx.background_spawn(async move {
|
||||
let mut combined_output = String::with_capacity(COMMAND_OUTPUT_LIMIT + 1);
|
||||
|
||||
while let Some(line) = line_stream.next().await {
|
||||
let line = match line {
|
||||
Ok(line) => line,
|
||||
Err(err) => {
|
||||
let err = format!("Failed to read line: {err}");
|
||||
// TODO: unwrap
|
||||
line_sender.send(Err(anyhow!(err.clone()))).await.unwrap();
|
||||
return Err(anyhow!(err));
|
||||
}
|
||||
};
|
||||
let truncated = combined_output.len() + line.len() > COMMAND_OUTPUT_LIMIT;
|
||||
|
||||
let line = if truncated {
|
||||
let remaining_capacity = COMMAND_OUTPUT_LIMIT.saturating_sub(combined_output.len());
|
||||
&line[..remaining_capacity]
|
||||
} else {
|
||||
&line
|
||||
};
|
||||
|
||||
combined_output.push_str(line);
|
||||
combined_output.push('\n');
|
||||
// TODO: unwrap
|
||||
line_sender
|
||||
.send(Ok(line.to_owned()))
|
||||
.await
|
||||
.context("Failed to send terminal output text")
|
||||
.unwrap();
|
||||
|
||||
if truncated {
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
read = err_line => {
|
||||
drop(err_line);
|
||||
combined_buffer.extend(err_tmp_buffer.drain(..));
|
||||
if read? == 0 {
|
||||
err_line = Box::pin(future::pending().right_future().fuse());
|
||||
has_stderr = false;
|
||||
} else {
|
||||
err_line = Box::pin(err_reader.read_line(&mut err_tmp_buffer).left_future().fuse());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
drop((out_line, err_line));
|
||||
|
||||
let truncated = combined_buffer.len() > LIMIT;
|
||||
combined_buffer.truncate(LIMIT);
|
||||
|
||||
consume_reader(out_reader, truncated).await?;
|
||||
consume_reader(err_reader, truncated).await?;
|
||||
|
||||
let status = cmd.status().await.context("Failed to get command status")?;
|
||||
|
||||
let output_string = if truncated {
|
||||
// Valid to find `\n` in UTF-8 since 0-127 ASCII characters are not used in
|
||||
// multi-byte characters.
|
||||
let last_line_ix = combined_buffer.bytes().rposition(|b| b == b'\n');
|
||||
let combined_buffer = &combined_buffer[..last_line_ix.unwrap_or(combined_buffer.len())];
|
||||
|
||||
format!(
|
||||
"Command output too long. The first {} bytes:\n\n{}",
|
||||
combined_buffer.len(),
|
||||
output_block(&combined_buffer),
|
||||
)
|
||||
} else {
|
||||
output_block(&combined_buffer)
|
||||
};
|
||||
|
||||
let output_with_status = if status.success() {
|
||||
if output_string.is_empty() {
|
||||
"Command executed successfully.".to_string()
|
||||
} else {
|
||||
output_string.to_string()
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"Command failed with exit code {} (shell: {}).\n\n{}",
|
||||
status.code().unwrap_or(-1),
|
||||
shell,
|
||||
output_string,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(output_with_status)
|
||||
Ok(output_block(&combined_output))
|
||||
});
|
||||
|
||||
Ok(fut)
|
||||
|
||||
// drop((out_line, err_line));
|
||||
|
||||
// let truncated = combined_buffer.len() > COMMAND_OUTPUT_LIMIT;
|
||||
// combined_buffer.truncate(COMMAND_OUTPUT_LIMIT);
|
||||
|
||||
// consume_reader(out_reader, truncated).await?;
|
||||
// consume_reader(err_reader, truncated).await?;
|
||||
|
||||
// let status = cmd.status().await.context("Failed to get command status")?;
|
||||
|
||||
// let output_string = if truncated {
|
||||
// // Valid to find `\n` in UTF-8 since 0-127 ASCII characters are not used in
|
||||
// // multi-byte characters.
|
||||
// let last_line_ix = combined_buffer.bytes().rposition(|b| b == b'\n');
|
||||
// let combined_buffer = &combined_buffer[..last_line_ix.unwrap_or(combined_buffer.len())];
|
||||
|
||||
// format!(
|
||||
// "Command output too long. The first {} bytes:\n\n{}",
|
||||
// combined_buffer.len(),
|
||||
// output_block(&combined_buffer),
|
||||
// )
|
||||
// } else {
|
||||
// output_block(&combined_buffer)
|
||||
// };
|
||||
|
||||
// let output_with_status = if status.success() {
|
||||
// if output_string.is_empty() {
|
||||
// "Command executed successfully.".to_string()
|
||||
// } else {
|
||||
// output_string.to_string()
|
||||
// }
|
||||
// } else {
|
||||
// format!(
|
||||
// "Command failed with exit code {} (shell: {}).\n\n{}",
|
||||
// status.code().unwrap_or(-1),
|
||||
// shell,
|
||||
// output_string,
|
||||
// )
|
||||
// };
|
||||
|
||||
// Ok(output_with_status)
|
||||
}
|
||||
|
||||
async fn consume_reader<T: AsyncReadExt + Unpin>(
|
||||
mut reader: BufReader<T>,
|
||||
truncated: bool,
|
||||
) -> Result<(), std::io::Error> {
|
||||
loop {
|
||||
let skipped_bytes = reader.fill_buf().await?;
|
||||
if skipped_bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
let skipped_bytes_len = skipped_bytes.len();
|
||||
reader.consume_unpin(skipped_bytes_len);
|
||||
fn working_dir(
|
||||
cx: &mut App,
|
||||
input: &TerminalToolInput,
|
||||
project: &Entity<Project>,
|
||||
input_path: &Path,
|
||||
) -> Result<Arc<Path>, &'static str> {
|
||||
let project = project.read(cx);
|
||||
if input.cd == "." {
|
||||
// Accept "." as meaning "the one worktree" if we only have one worktree.
|
||||
let mut worktrees = project.worktrees(cx);
|
||||
|
||||
// Should only skip if we went over the limit
|
||||
debug_assert!(truncated);
|
||||
let only_worktree = match worktrees.next() {
|
||||
Some(worktree) => worktree,
|
||||
None => return Err("No worktrees found in the project"),
|
||||
};
|
||||
|
||||
if worktrees.next().is_some() {
|
||||
return Err(
|
||||
"'.' is ambiguous in multi-root workspaces. Please specify a root directory explicitly.",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(only_worktree.read(cx).abs_path())
|
||||
} else if input_path.is_absolute() {
|
||||
// Absolute paths are allowed, but only if they're in one of the project's worktrees.
|
||||
if !project
|
||||
.worktrees(cx)
|
||||
.any(|worktree| input_path.starts_with(&worktree.read(cx).abs_path()))
|
||||
{
|
||||
return Err("The absolute path must be within one of the project's worktrees");
|
||||
}
|
||||
|
||||
Ok(input_path.into())
|
||||
} else {
|
||||
let Some(worktree) = project.worktree_for_root_name(&input.cd, cx) else {
|
||||
return Err("`cd` directory {} not found in the project");
|
||||
};
|
||||
|
||||
Ok(worktree.read(cx).abs_path())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_block(output: &str) -> String {
|
||||
@@ -266,107 +280,234 @@ fn output_block(output: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
struct TerminalToolCard {
|
||||
read_failed: bool,
|
||||
combined_contents: String,
|
||||
_task: Task<()>,
|
||||
}
|
||||
|
||||
impl TerminalToolCard {
|
||||
fn new(mut line_receiver: UnboundedReceiver<Result<String>>, cx: &mut Context<Self>) -> Self {
|
||||
let _task = cx.spawn(async move |this, cx| {
|
||||
while let Some(line) = line_receiver.next().await {
|
||||
let is_entity_released = this
|
||||
.update(cx, |card, cx| {
|
||||
let line = match line {
|
||||
Ok(line) => line,
|
||||
// TODO: don't we need to log these??
|
||||
Err(_) => {
|
||||
card.read_failed = true;
|
||||
return; // stop receiving
|
||||
}
|
||||
};
|
||||
|
||||
card.combined_contents += &line;
|
||||
cx.notify();
|
||||
})
|
||||
.is_err();
|
||||
|
||||
if is_entity_released {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
read_failed: false,
|
||||
combined_contents: String::new(),
|
||||
_task,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolCard for TerminalToolCard {
|
||||
fn render(
|
||||
&mut self,
|
||||
_status: &ToolUseStatus,
|
||||
_window: &mut Window,
|
||||
_workspace: WeakEntity<Workspace>,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
format!("text: {}", self.combined_contents)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for TerminalTool {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Agent
|
||||
}
|
||||
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
enum TerminalToolCardPreviewOperation {
|
||||
Sleep(u64),
|
||||
SendLine(&'static str),
|
||||
}
|
||||
|
||||
use TerminalToolCardPreviewOperation::*;
|
||||
|
||||
const OPERATIONS: &[TerminalToolCardPreviewOperation] = &[
|
||||
SendLine("$ ./imaginary-script.sh"),
|
||||
Sleep(100),
|
||||
SendLine(""),
|
||||
Sleep(200),
|
||||
SendLine(" This"),
|
||||
Sleep(16),
|
||||
SendLine(" takes"),
|
||||
Sleep(1000),
|
||||
SendLine(" LONG"),
|
||||
Sleep(100),
|
||||
SendLine(" to"),
|
||||
Sleep(300),
|
||||
SendLine(" finish."),
|
||||
];
|
||||
|
||||
let (mut tx, rx) = unbounded();
|
||||
let executor = cx.background_executor().clone();
|
||||
let ccccard = cx.new(|cx| TerminalToolCard::new(rx, cx));
|
||||
|
||||
cx.background_spawn(async move {
|
||||
for operation in OPERATIONS {
|
||||
match operation {
|
||||
&Sleep(millis) => executor.timer(Duration::from_millis(millis)).await,
|
||||
&SendLine(line) => {
|
||||
let _ = tx.send(Ok(line.to_owned())).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// TODO: add one where it receives a read failure.
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group(vec![single_example(
|
||||
"No failures (todo naming)",
|
||||
div()
|
||||
.size_full()
|
||||
.child(ccccard.update(cx, |tool, cx| {
|
||||
tool.render(
|
||||
&ToolUseStatus::Pending,
|
||||
window,
|
||||
WeakEntity::new_invalid(),
|
||||
cx,
|
||||
)
|
||||
.into_any_element()
|
||||
}))
|
||||
.into_any_element(),
|
||||
)])])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(not(windows))]
|
||||
mod tests {
|
||||
use gpui::TestAppContext;
|
||||
|
||||
use super::*;
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_run_command_simple(cx: &mut TestAppContext) {
|
||||
// cx.executor().allow_parking();
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_run_command_simple(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
// let result =
|
||||
// spawn_command_and_stream(Path::new(".").into(), "echo 'Hello, World!'".to_string())
|
||||
// .await;
|
||||
|
||||
let result =
|
||||
run_command_limited(Path::new(".").into(), "echo 'Hello, World!'".to_string()).await;
|
||||
// assert!(result.is_ok());
|
||||
// assert_eq!(result.unwrap(), "```\nHello, World!\n```");
|
||||
// }
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "```\nHello, World!\n```");
|
||||
}
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_interleaved_stdout_stderr(cx: &mut TestAppContext) {
|
||||
// cx.executor().allow_parking();
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_interleaved_stdout_stderr(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
// let command = "echo 'stdout 1' && sleep 0.01 && echo 'stderr 1' >&2 && sleep 0.01 && echo 'stdout 2' && sleep 0.01 && echo 'stderr 2' >&2";
|
||||
// let result = spawn_command_and_stream(Path::new(".").into(), command.to_string()).await;
|
||||
|
||||
let command = "echo 'stdout 1' && sleep 0.01 && echo 'stderr 1' >&2 && sleep 0.01 && echo 'stdout 2' && sleep 0.01 && echo 'stderr 2' >&2";
|
||||
let result = run_command_limited(Path::new(".").into(), command.to_string()).await;
|
||||
// assert!(result.is_ok());
|
||||
// assert_eq!(
|
||||
// result.unwrap(),
|
||||
// "```\nstdout 1\nstderr 1\nstdout 2\nstderr 2\n```"
|
||||
// );
|
||||
// }
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
result.unwrap(),
|
||||
"```\nstdout 1\nstderr 1\nstdout 2\nstderr 2\n```"
|
||||
);
|
||||
}
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_multiple_output_reads(cx: &mut TestAppContext) {
|
||||
// cx.executor().allow_parking();
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_multiple_output_reads(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
// // Command with multiple outputs that might require multiple reads
|
||||
// let result = spawn_command_and_stream(
|
||||
// Path::new(".").into(),
|
||||
// "echo '1'; sleep 0.01; echo '2'; sleep 0.01; echo '3'".to_string(),
|
||||
// )
|
||||
// .await;
|
||||
|
||||
// Command with multiple outputs that might require multiple reads
|
||||
let result = run_command_limited(
|
||||
Path::new(".").into(),
|
||||
"echo '1'; sleep 0.01; echo '2'; sleep 0.01; echo '3'".to_string(),
|
||||
)
|
||||
.await;
|
||||
// assert!(result.is_ok());
|
||||
// assert_eq!(result.unwrap(), "```\n1\n2\n3\n```");
|
||||
// }
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "```\n1\n2\n3\n```");
|
||||
}
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_output_truncation_single_line(cx: &mut TestAppContext) {
|
||||
// cx.executor().allow_parking();
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_output_truncation_single_line(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
// let cmd = format!(
|
||||
// "echo '{}'; sleep 0.01;",
|
||||
// "X".repeat(COMMAND_OUTPUT_LIMIT * 2)
|
||||
// );
|
||||
|
||||
let cmd = format!("echo '{}'; sleep 0.01;", "X".repeat(LIMIT * 2));
|
||||
// let result = spawn_command_and_stream(Path::new(".").into(), cmd).await;
|
||||
|
||||
let result = run_command_limited(Path::new(".").into(), cmd).await;
|
||||
// assert!(result.is_ok());
|
||||
// let output = result.unwrap();
|
||||
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
// let content_start = output.find("```\n").map(|i| i + 4).unwrap_or(0);
|
||||
// let content_end = output.rfind("\n```").unwrap_or(output.len());
|
||||
// let content_length = content_end - content_start;
|
||||
|
||||
let content_start = output.find("```\n").map(|i| i + 4).unwrap_or(0);
|
||||
let content_end = output.rfind("\n```").unwrap_or(output.len());
|
||||
let content_length = content_end - content_start;
|
||||
// // Output should be exactly the limit
|
||||
// assert_eq!(content_length, COMMAND_OUTPUT_LIMIT);
|
||||
// }
|
||||
|
||||
// Output should be exactly the limit
|
||||
assert_eq!(content_length, LIMIT);
|
||||
}
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_output_truncation_multiline(cx: &mut TestAppContext) {
|
||||
// cx.executor().allow_parking();
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_output_truncation_multiline(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
// let cmd = format!("echo '{}'; ", "X".repeat(120)).repeat(160);
|
||||
// let result = spawn_command_and_stream(Path::new(".").into(), cmd).await;
|
||||
|
||||
let cmd = format!("echo '{}'; ", "X".repeat(120)).repeat(160);
|
||||
let result = run_command_limited(Path::new(".").into(), cmd).await;
|
||||
// assert!(result.is_ok());
|
||||
// let output = result.unwrap();
|
||||
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
// assert!(output.starts_with("Command output too long. The first 16334 bytes:\n\n"));
|
||||
|
||||
assert!(output.starts_with("Command output too long. The first 16334 bytes:\n\n"));
|
||||
// let content_start = output.find("```\n").map(|i| i + 4).unwrap_or(0);
|
||||
// let content_end = output.rfind("\n```").unwrap_or(output.len());
|
||||
// let content_length = content_end - content_start;
|
||||
|
||||
let content_start = output.find("```\n").map(|i| i + 4).unwrap_or(0);
|
||||
let content_end = output.rfind("\n```").unwrap_or(output.len());
|
||||
let content_length = content_end - content_start;
|
||||
// assert!(content_length <= COMMAND_OUTPUT_LIMIT);
|
||||
// }
|
||||
|
||||
assert!(content_length <= LIMIT);
|
||||
}
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_command_failure(cx: &mut TestAppContext) {
|
||||
// cx.executor().allow_parking();
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_command_failure(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
// let result = spawn_command_and_stream(Path::new(".").into(), "exit 42".to_string()).await;
|
||||
|
||||
let result = run_command_limited(Path::new(".").into(), "exit 42".to_string()).await;
|
||||
// assert!(result.is_ok());
|
||||
// let output = result.unwrap();
|
||||
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
// // Extract the shell name from path for cleaner test output
|
||||
// let shell_path = std::env::var("SHELL").unwrap_or("bash".to_string());
|
||||
|
||||
// Extract the shell name from path for cleaner test output
|
||||
let shell_path = std::env::var("SHELL").unwrap_or("bash".to_string());
|
||||
|
||||
let expected_output = format!(
|
||||
"Command failed with exit code 42 (shell: {}).\n\n```\n\n```",
|
||||
shell_path
|
||||
);
|
||||
assert_eq!(output, expected_output);
|
||||
}
|
||||
// let expected_output = format!(
|
||||
// "Command failed with exit code 42 (shell: {}).\n\n```\n\n```",
|
||||
// shell_path
|
||||
// );
|
||||
// assert_eq!(output, expected_output);
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ pub struct WebSearchToolInput {
|
||||
query: String,
|
||||
}
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct WebSearchTool;
|
||||
|
||||
impl Tool for WebSearchTool {
|
||||
@@ -84,6 +83,7 @@ impl Tool for WebSearchTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
struct WebSearchToolCard {
|
||||
response: Option<Result<WebSearchResponse>>,
|
||||
_task: Task<()>,
|
||||
@@ -185,16 +185,17 @@ impl ToolCard for WebSearchToolCard {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for WebSearchTool {
|
||||
impl Component for WebSearchToolCard {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Agent
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
"ToolWebSearch"
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn preview(_state: &mut (), window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let in_progress_search = cx.new(|cx| WebSearchToolCard {
|
||||
response: None,
|
||||
_task: cx.spawn(async move |_this, cx| {
|
||||
|
||||
@@ -14,6 +14,7 @@ path = "src/component.rs"
|
||||
[dependencies]
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
linkme.workspace = true
|
||||
parking_lot.workspace = true
|
||||
theme.workspace = true
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::any::Any;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Display;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::LazyLock;
|
||||
@@ -7,11 +9,14 @@ use gpui::{
|
||||
AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, pattern_slash, prelude::*,
|
||||
px, rems,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use linkme::distributed_slice;
|
||||
use parking_lot::RwLock;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
pub trait Component {
|
||||
type InitialState;
|
||||
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
@@ -37,7 +42,14 @@ pub trait Component {
|
||||
fn description() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
/// State for `preview`, should be `()` for stateless components.
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState;
|
||||
/// Render the component.
|
||||
fn preview(
|
||||
_initial_state: &mut Self::InitialState,
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Option<AnyElement> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -46,28 +58,16 @@ pub trait Component {
|
||||
pub static __ALL_COMPONENTS: [fn()] = [..];
|
||||
|
||||
pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
|
||||
LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
|
||||
LazyLock::new(|| RwLock::new(BTreeMap::default()));
|
||||
|
||||
pub struct ComponentRegistry {
|
||||
components: Vec<(
|
||||
ComponentScope,
|
||||
// name
|
||||
&'static str,
|
||||
// sort name
|
||||
&'static str,
|
||||
// description
|
||||
Option<&'static str>,
|
||||
)>,
|
||||
previews: HashMap<&'static str, fn(&mut Window, &mut App) -> Option<AnyElement>>,
|
||||
}
|
||||
pub type ComponentRegistry = BTreeMap<&'static str, ComponentRegistryItem>;
|
||||
|
||||
impl ComponentRegistry {
|
||||
fn new() -> Self {
|
||||
ComponentRegistry {
|
||||
components: Vec::new(),
|
||||
previews: HashMap::default(),
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct ComponentRegistryItem {
|
||||
scope: ComponentScope,
|
||||
sort_name: &'static str,
|
||||
description: Option<&'static str>,
|
||||
preview_helper_creator: PreviewHelperCreator,
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
@@ -77,24 +77,69 @@ pub fn init() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_component<T: Component>() {
|
||||
let component_data = (T::scope(), T::name(), T::sort_name(), T::description());
|
||||
let mut data = COMPONENT_DATA.write();
|
||||
data.components.push(component_data);
|
||||
data.previews.insert(T::name(), T::preview);
|
||||
pub fn register_component<T: Component + 'static>() {
|
||||
let component_data = ComponentRegistryItem {
|
||||
scope: T::scope(),
|
||||
sort_name: T::sort_name(),
|
||||
description: T::description(),
|
||||
preview_helper_creator: PreviewHelperCreator::new::<T>(),
|
||||
};
|
||||
|
||||
COMPONENT_DATA.write().insert(T::name(), component_data);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ComponentId(pub &'static str);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PreviewHelperCreator {
|
||||
state_initializer: fn(&mut App) -> Box<dyn Any>,
|
||||
preview_fn: fn(Box<dyn Any>, &mut Window, &mut App) -> Option<AnyElement>,
|
||||
}
|
||||
|
||||
impl PreviewHelperCreator {
|
||||
fn new<T: Component + Any>() -> Self {
|
||||
PreviewHelperCreator {
|
||||
state_initializer: state_initializer_type_erased::<T>,
|
||||
preview_fn: access_state_and_preview::<T>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_initializer_type_erased<T: Component + 'static>(cx: &mut App) -> Box<dyn Any> {
|
||||
Box::new(T::initial_state(cx))
|
||||
}
|
||||
|
||||
fn access_state_and_preview<T: Component + 'static>(
|
||||
mut initial_state: Box<dyn Any>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<AnyElement> {
|
||||
let mut state = initial_state.downcast_mut::<T::InitialState>()?;
|
||||
T::preview(&mut state, window, cx)
|
||||
}
|
||||
|
||||
impl PreviewHelperCreator {
|
||||
fn create(&self, cx: &mut App) -> PreviewHelper {
|
||||
PreviewHelper {
|
||||
state: (self.state_initializer)(cx),
|
||||
preview_fn: self.preview_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PreviewHelper {
|
||||
state: Box<dyn Any>,
|
||||
preview_fn: fn(Box<dyn Any>, &mut Window, &mut App) -> Option<AnyElement>,
|
||||
}
|
||||
|
||||
pub struct ComponentMetadata {
|
||||
id: ComponentId,
|
||||
name: SharedString,
|
||||
sort_name: SharedString,
|
||||
scope: ComponentScope,
|
||||
description: Option<SharedString>,
|
||||
preview: Option<fn(&mut Window, &mut App) -> Option<AnyElement>>,
|
||||
preview_helper: PreviewHelper,
|
||||
}
|
||||
|
||||
impl ComponentMetadata {
|
||||
@@ -125,33 +170,43 @@ impl ComponentMetadata {
|
||||
pub fn description(&self) -> Option<SharedString> {
|
||||
self.description.clone()
|
||||
}
|
||||
pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> Option<AnyElement>> {
|
||||
self.preview
|
||||
}
|
||||
// pub fn preview_helper(&self, cx: &mut App) -> PreviewHelper {
|
||||
// self.preview_helper_creator.create(cx)
|
||||
// }
|
||||
}
|
||||
|
||||
pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
|
||||
pub struct AllComponents(HashMap<ComponentId, ComponentMetadata>);
|
||||
|
||||
impl AllComponents {
|
||||
pub fn new() -> Self {
|
||||
AllComponents(HashMap::default())
|
||||
pub fn new(cx: &mut App) -> Self {
|
||||
let data = COMPONENT_DATA.read();
|
||||
let mut map = HashMap::new();
|
||||
for (name, item) in data.iter() {
|
||||
let ComponentRegistryItem {
|
||||
scope,
|
||||
sort_name,
|
||||
description,
|
||||
preview_helper_creator,
|
||||
} = item.clone();
|
||||
|
||||
let id = ComponentId(name);
|
||||
|
||||
map.insert(
|
||||
id.clone(),
|
||||
ComponentMetadata {
|
||||
id,
|
||||
name: SharedString::new(name.to_owned()),
|
||||
sort_name: SharedString::new(sort_name.to_owned()),
|
||||
scope,
|
||||
description: description.map(Into::into),
|
||||
preview_helper: preview_helper_creator.create(cx),
|
||||
},
|
||||
);
|
||||
}
|
||||
Self(map)
|
||||
}
|
||||
pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().filter(|c| c.preview.is_some()).collect()
|
||||
}
|
||||
pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
|
||||
let mut previews: Vec<ComponentMetadata> =
|
||||
self.all_previews().into_iter().cloned().collect();
|
||||
previews.sort_by_key(|a| a.name());
|
||||
previews
|
||||
}
|
||||
pub fn all(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().collect()
|
||||
}
|
||||
pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
|
||||
let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
|
||||
components.sort_by_key(|a| a.name());
|
||||
components
|
||||
pub fn all_sorted(&self) -> Vec<&ComponentMetadata> {
|
||||
self.values().sorted_by_key(|a| a.name()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,29 +223,6 @@ impl DerefMut for AllComponents {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn components() -> AllComponents {
|
||||
let data = COMPONENT_DATA.read();
|
||||
let mut all_components = AllComponents::new();
|
||||
for (scope, name, sort_name, description) in &data.components {
|
||||
let preview = data.previews.get(name).cloned();
|
||||
let component_name = SharedString::new_static(name);
|
||||
let sort_name = SharedString::new_static(sort_name);
|
||||
let id = ComponentId(name);
|
||||
all_components.insert(
|
||||
id.clone(),
|
||||
ComponentMetadata {
|
||||
id,
|
||||
name: component_name,
|
||||
sort_name,
|
||||
scope: scope.clone(),
|
||||
description: description.map(Into::into),
|
||||
preview,
|
||||
},
|
||||
);
|
||||
}
|
||||
all_components
|
||||
}
|
||||
|
||||
// #[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
// pub enum ComponentStatus {
|
||||
// WorkInProgress,
|
||||
|
||||
@@ -191,8 +191,8 @@ impl ComponentPreview {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn get_component(&self, ix: usize) -> ComponentMetadata {
|
||||
self.components[ix].clone()
|
||||
fn get_component(&self, ix: usize) -> Option<ComponentMetadata> {
|
||||
self.components.get(ix)
|
||||
}
|
||||
|
||||
fn filtered_components(&self) -> Vec<ComponentMetadata> {
|
||||
|
||||
@@ -4494,11 +4494,16 @@ impl RenderOnce for PanelRepoFooter {
|
||||
}
|
||||
|
||||
impl Component for PanelRepoFooter {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::VersionControl
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let unknown_upstream = None;
|
||||
let no_remote_upstream = Some(UpstreamTracking::Gone);
|
||||
let ahead_of_upstream = Some(
|
||||
|
||||
@@ -493,11 +493,16 @@ impl RenderOnce for GitStatusIcon {
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl Component for GitStatusIcon {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::VersionControl
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn tracked_file_status(code: StatusCode) -> FileStatus {
|
||||
FileStatus::Tracked(git::status::TrackedStatus {
|
||||
index_status: code,
|
||||
|
||||
@@ -1142,11 +1142,16 @@ mod preview {
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl Component for ProjectDiffEmptyState {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::VersionControl
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let unknown_upstream: Option<UpstreamTracking> = None;
|
||||
let ahead_of_upstream: Option<UpstreamTracking> = Some(
|
||||
UpstreamTrackingStatus {
|
||||
|
||||
@@ -135,11 +135,16 @@ impl Focusable for StatusToast {
|
||||
impl EventEmitter<DismissEvent> for StatusToast {}
|
||||
|
||||
impl Component for StatusToast {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Notification
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let text_example = StatusToast::new("Operation completed", cx, |this, _| this);
|
||||
|
||||
let action_example = StatusToast::new("Update ready to install", cx, |this, _cx| {
|
||||
|
||||
@@ -514,7 +514,7 @@ impl Project {
|
||||
terminal_handle: &Entity<Terminal>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
terminal_handle.update(cx, |terminal, _| terminal.input_bytes(command.into_bytes()));
|
||||
terminal_handle.update(cx, |terminal, _| terminal.input(command));
|
||||
}
|
||||
|
||||
pub fn local_terminal_handles(&self) -> &Vec<WeakEntity<terminal::Terminal>> {
|
||||
|
||||
@@ -1216,15 +1216,11 @@ impl Terminal {
|
||||
}
|
||||
|
||||
///Write the Input payload to the tty.
|
||||
fn write_to_pty(&self, input: String) {
|
||||
self.pty_tx.notify(input.into_bytes());
|
||||
fn write_to_pty(&self, input: impl Into<Vec<u8>>) {
|
||||
self.pty_tx.notify(input.into());
|
||||
}
|
||||
|
||||
fn write_bytes_to_pty(&self, input: Vec<u8>) {
|
||||
self.pty_tx.notify(input);
|
||||
}
|
||||
|
||||
pub fn input(&mut self, input: String) {
|
||||
pub fn input(&mut self, input: impl Into<Vec<u8>>) {
|
||||
self.events
|
||||
.push_back(InternalEvent::Scroll(AlacScroll::Bottom));
|
||||
self.events.push_back(InternalEvent::SetSelection(None));
|
||||
@@ -1232,14 +1228,6 @@ impl Terminal {
|
||||
self.write_to_pty(input);
|
||||
}
|
||||
|
||||
pub fn input_bytes(&mut self, input: Vec<u8>) {
|
||||
self.events
|
||||
.push_back(InternalEvent::Scroll(AlacScroll::Bottom));
|
||||
self.events.push_back(InternalEvent::SetSelection(None));
|
||||
|
||||
self.write_bytes_to_pty(input);
|
||||
}
|
||||
|
||||
pub fn toggle_vi_mode(&mut self) {
|
||||
self.events.push_back(InternalEvent::ToggleViMode);
|
||||
}
|
||||
|
||||
@@ -1036,7 +1036,7 @@ impl InputHandler for TerminalInputHandler {
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.terminal.update(cx, |terminal, _| {
|
||||
terminal.input(text.into());
|
||||
terminal.input(text);
|
||||
});
|
||||
|
||||
self.workspace
|
||||
|
||||
@@ -1133,7 +1133,7 @@ async fn wait_for_terminals_tasks(
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
let _: Vec<_> = join_all(pending_tasks).await;
|
||||
join_all(pending_tasks).await;
|
||||
}
|
||||
|
||||
fn add_paths_to_terminal(
|
||||
|
||||
@@ -221,6 +221,7 @@ impl RenderOnce for AvatarAvailabilityIndicator {
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl Component for Avatar {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Collaboration
|
||||
}
|
||||
@@ -229,7 +230,11 @@ impl Component for Avatar {
|
||||
Some(Avatar::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let example_avatar = "https://avatars.githubusercontent.com/u/1714999?v=4";
|
||||
|
||||
Some(
|
||||
|
||||
@@ -137,11 +137,16 @@ impl RenderOnce for Banner {
|
||||
}
|
||||
|
||||
impl Component for Banner {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Notification
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let severity_examples = vec![
|
||||
single_example(
|
||||
"Default",
|
||||
|
||||
@@ -466,6 +466,7 @@ impl RenderOnce for Button {
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl Component for Button {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -478,7 +479,11 @@ impl Component for Button {
|
||||
Some("A button triggers an event or action.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -120,6 +120,7 @@ impl RenderOnce for ButtonIcon {
|
||||
}
|
||||
|
||||
impl Component for ButtonIcon {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -132,7 +133,11 @@ impl Component for ButtonIcon {
|
||||
Some("An icon component specifically designed for use within buttons.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -590,6 +590,7 @@ impl RenderOnce for ButtonLike {
|
||||
}
|
||||
|
||||
impl Component for ButtonLike {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -603,7 +604,11 @@ impl Component for ButtonLike {
|
||||
Some(ButtonLike::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -210,6 +210,7 @@ impl RenderOnce for IconButton {
|
||||
}
|
||||
|
||||
impl Component for IconButton {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -218,7 +219,11 @@ impl Component for IconButton {
|
||||
"ButtonB"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -155,6 +155,7 @@ impl RenderOnce for ToggleButton {
|
||||
}
|
||||
|
||||
impl Component for ToggleButton {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -163,7 +164,11 @@ impl Component for ToggleButton {
|
||||
"ButtonC"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -87,6 +87,7 @@ impl RenderOnce for ContentGroup {
|
||||
}
|
||||
|
||||
impl Component for ContentGroup {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
@@ -95,7 +96,11 @@ impl Component for ContentGroup {
|
||||
Some(ContentGroup::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
example_group(vec![
|
||||
single_example(
|
||||
|
||||
@@ -94,6 +94,7 @@ impl RenderOnce for Disclosure {
|
||||
}
|
||||
|
||||
impl Component for Disclosure {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Navigation
|
||||
}
|
||||
@@ -104,7 +105,11 @@ impl Component for Disclosure {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -160,6 +160,7 @@ impl Divider {
|
||||
}
|
||||
|
||||
impl Component for Divider {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
@@ -170,7 +171,11 @@ impl Component for Divider {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -73,6 +73,7 @@ impl RenderOnce for DropdownMenu {
|
||||
}
|
||||
|
||||
impl Component for DropdownMenu {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -87,7 +88,11 @@ impl Component for DropdownMenu {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let menu = ContextMenu::build(window, cx, |this, _, _| {
|
||||
this.entry("Option 1", None, |_, _| {})
|
||||
.entry("Option 2", None, |_, _| {})
|
||||
|
||||
@@ -88,6 +88,7 @@ pub const EXAMPLE_FACES: [&'static str; 6] = [
|
||||
];
|
||||
|
||||
impl Component for Facepile {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Collaboration
|
||||
}
|
||||
@@ -98,7 +99,11 @@ impl Component for Facepile {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -266,6 +266,7 @@ impl RenderOnce for IconWithIndicator {
|
||||
}
|
||||
|
||||
impl Component for Icon {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Images
|
||||
}
|
||||
@@ -276,7 +277,11 @@ impl Component for Icon {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -25,6 +25,7 @@ impl RenderOnce for DecoratedIcon {
|
||||
}
|
||||
|
||||
impl Component for DecoratedIcon {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Images
|
||||
}
|
||||
@@ -35,7 +36,11 @@ impl Component for DecoratedIcon {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let decoration_x = IconDecoration::new(
|
||||
IconDecorationKind::X,
|
||||
cx.theme().colors().surface_background,
|
||||
|
||||
@@ -84,6 +84,7 @@ impl RenderOnce for Vector {
|
||||
}
|
||||
|
||||
impl Component for Vector {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Images
|
||||
}
|
||||
@@ -96,7 +97,11 @@ impl Component for Vector {
|
||||
Some("A vector image component that can be displayed at specific sizes.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -85,6 +85,7 @@ impl RenderOnce for Indicator {
|
||||
}
|
||||
|
||||
impl Component for Indicator {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Status
|
||||
}
|
||||
@@ -95,7 +96,11 @@ impl Component for Indicator {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -445,6 +445,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode
|
||||
}
|
||||
|
||||
impl Component for KeyBinding {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Typography
|
||||
}
|
||||
@@ -459,7 +460,11 @@ impl Component for KeyBinding {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -206,6 +206,7 @@ impl RenderOnce for KeybindingHint {
|
||||
}
|
||||
|
||||
impl Component for KeybindingHint {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
@@ -214,7 +215,11 @@ impl Component for KeybindingHint {
|
||||
Some("Displays a keyboard shortcut hint with optional prefix and suffix text")
|
||||
}
|
||||
|
||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
|
||||
let enter = KeyBinding::for_action(&menu::Confirm, window, cx)
|
||||
.unwrap_or(KeyBinding::new(enter_fallback, cx));
|
||||
|
||||
@@ -136,6 +136,7 @@ impl RenderOnce for HighlightedLabel {
|
||||
}
|
||||
|
||||
impl Component for HighlightedLabel {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Typography
|
||||
}
|
||||
@@ -148,7 +149,11 @@ impl Component for HighlightedLabel {
|
||||
Some("A label with highlighted characters based on specified indices.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -204,6 +204,7 @@ impl RenderOnce for Label {
|
||||
}
|
||||
|
||||
impl Component for Label {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Typography
|
||||
}
|
||||
@@ -212,7 +213,11 @@ impl Component for Label {
|
||||
Some("A text label component that supports various styles, sizes, and formatting options.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -247,6 +247,7 @@ impl RenderOnce for LabelLike {
|
||||
}
|
||||
|
||||
impl Component for LabelLike {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Typography
|
||||
}
|
||||
@@ -261,7 +262,11 @@ impl Component for LabelLike {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -77,6 +77,7 @@ impl ParentElement for AlertModal {
|
||||
}
|
||||
|
||||
impl Component for AlertModal {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Notification
|
||||
}
|
||||
@@ -85,7 +86,11 @@ impl Component for AlertModal {
|
||||
Some("A modal dialog that presents an alert message with primary and dismiss actions.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -81,6 +81,7 @@ impl RenderOnce for ProgressBar {
|
||||
}
|
||||
|
||||
impl Component for ProgressBar {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Status
|
||||
}
|
||||
@@ -89,7 +90,11 @@ impl Component for ProgressBar {
|
||||
Some(Self::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let max_value = 180.0;
|
||||
|
||||
Some(
|
||||
|
||||
@@ -37,6 +37,7 @@ impl RenderOnce for SettingsContainer {
|
||||
}
|
||||
|
||||
impl Component for SettingsContainer {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
@@ -49,7 +50,11 @@ impl Component for SettingsContainer {
|
||||
Some("A container for organizing and displaying settings in a structured manner.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -38,6 +38,7 @@ impl RenderOnce for SettingsGroup {
|
||||
}
|
||||
|
||||
impl Component for SettingsGroup {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
@@ -50,7 +51,11 @@ impl Component for SettingsGroup {
|
||||
Some("A group of settings with a header, used to organize related settings.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -178,6 +178,7 @@ impl RenderOnce for Tab {
|
||||
}
|
||||
|
||||
impl Component for Tab {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
@@ -188,7 +189,11 @@ impl Component for Tab {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -153,6 +153,7 @@ impl RenderOnce for TabBar {
|
||||
}
|
||||
|
||||
impl Component for TabBar {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Navigation
|
||||
}
|
||||
@@ -165,7 +166,11 @@ impl Component for TabBar {
|
||||
Some("A horizontal bar containing tabs for navigation between different views or sections.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -152,6 +152,7 @@ where
|
||||
}
|
||||
|
||||
impl Component for Table {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
@@ -160,7 +161,11 @@ impl Component for Table {
|
||||
Some("A table component for displaying data in rows and columns with optional styling.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -599,6 +599,7 @@ impl RenderOnce for SwitchWithLabel {
|
||||
}
|
||||
|
||||
impl Component for Checkbox {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -607,7 +608,11 @@ impl Component for Checkbox {
|
||||
Some("A checkbox component that can be used for multiple choice selections")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
@@ -704,6 +709,7 @@ impl Component for Checkbox {
|
||||
}
|
||||
|
||||
impl Component for Switch {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -712,7 +718,11 @@ impl Component for Switch {
|
||||
Some("A switch component that represents binary states like on/off")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
@@ -823,6 +833,7 @@ impl Component for Switch {
|
||||
}
|
||||
|
||||
impl Component for CheckboxWithLabel {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
@@ -831,7 +842,11 @@ impl Component for CheckboxWithLabel {
|
||||
Some("A checkbox component with an attached label")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -227,6 +227,7 @@ impl Render for LinkPreview {
|
||||
}
|
||||
|
||||
impl Component for Tooltip {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
@@ -237,7 +238,11 @@ impl Component for Tooltip {
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
example_group(vec![single_example(
|
||||
"Text only",
|
||||
|
||||
@@ -98,6 +98,7 @@ impl<E: Styled> DefaultAnimations for E {}
|
||||
struct Animation {}
|
||||
|
||||
impl Component for Animation {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
@@ -106,7 +107,11 @@ impl Component for Animation {
|
||||
Some("Demonstrates various animation patterns and transitions available in the UI system.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
let container_size = 128.0;
|
||||
let element_size = 32.0;
|
||||
let left_offset = element_size - container_size / 2.0;
|
||||
|
||||
@@ -125,6 +125,7 @@ impl From<Hsla> for Color {
|
||||
}
|
||||
|
||||
impl Component for Color {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
@@ -133,7 +134,15 @@ impl Component for Color {
|
||||
Some(Color::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut gpui::Window, _cx: &mut App) -> Option<gpui::AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(
|
||||
_state: &mut (),
|
||||
_window: &mut gpui::Window,
|
||||
_cx: &mut App,
|
||||
) -> Option<gpui::AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
|
||||
@@ -234,6 +234,7 @@ impl Headline {
|
||||
}
|
||||
|
||||
impl Component for Headline {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Typography
|
||||
}
|
||||
@@ -242,7 +243,11 @@ impl Component for Headline {
|
||||
Some("A headline element used to emphasize text and create visual hierarchy in the UI.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
|
||||
@@ -168,11 +168,16 @@ impl Render for SingleLineInput {
|
||||
}
|
||||
|
||||
impl Component for SingleLineInput {
|
||||
type InitialState = ();
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
fn initial_state(_cx: &mut App) -> Self::InitialState {
|
||||
()
|
||||
}
|
||||
|
||||
fn preview(_state: &mut (), window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let input_1 =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "placeholder").label("Some Label"));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user