Compare commits
14 Commits
bash-timeo
...
tool-rende
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac63ea504a | ||
|
|
11aaea964f | ||
|
|
cd9a45a262 | ||
|
|
1965029528 | ||
|
|
46c738d724 | ||
|
|
b18bd2d67b | ||
|
|
7e0a97fabe | ||
|
|
8ba9429e60 | ||
|
|
cad3ee8def | ||
|
|
42cb40170e | ||
|
|
7dc3a39edf | ||
|
|
fc1b3889c2 | ||
|
|
34c6fee959 | ||
|
|
b25ec65b48 |
52
Cargo.lock
generated
52
Cargo.lock
generated
@@ -3329,15 +3329,6 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.8.0"
|
||||
@@ -4470,32 +4461,6 @@ dependencies = [
|
||||
"workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "documented"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc6db32f0995bc4553d2de888999075acd0dbeef75ba923503f6a724263dc6f3"
|
||||
dependencies = [
|
||||
"documented-macros",
|
||||
"phf",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "documented-macros"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a394bb35929b58f9a5fd418f7c6b17a4b616efcc1e53e6995ca123948f87e5fa"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"itertools 0.13.0",
|
||||
"optfield",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strum",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@@ -7910,7 +7875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9577,17 +9542,6 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "optfield"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa59f025cde9c698fcb4fcb3533db4621795374065bee908215263488f2d2a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@@ -14173,14 +14127,12 @@ name = "tasks_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"debugger_ui",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"file_icons",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"menu",
|
||||
"picker",
|
||||
@@ -15395,7 +15347,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"component",
|
||||
"documented",
|
||||
"gpui",
|
||||
"icons",
|
||||
"itertools 0.14.0",
|
||||
@@ -17661,7 +17612,6 @@ dependencies = [
|
||||
"indexmap",
|
||||
"inout",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.13.0",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libsqlite3-sys",
|
||||
|
||||
@@ -532,7 +532,6 @@
|
||||
"context": "Editor && showing_completions",
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"shift-enter": "editor::ConfirmCompletionReplace",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -681,7 +681,6 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"enter": "editor::ConfirmCompletion",
|
||||
"shift-enter": "editor::ConfirmCompletionReplace",
|
||||
"tab": "editor::ComposeCompletion"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,8 +16,6 @@ no need to create backup files (e.g. `.bak` files) because these files will just
|
||||
|
||||
When attempting to resolve issues around failing tests, never simply remove the failing tests. Unless the user explicitly asks you to remove tests, ALWAYS attempt to fix the code causing the tests to fail.
|
||||
|
||||
Ignore "TODO"-type comments unless they're relevant to the user's explicit request or the user specifically asks you to address them. It is, however, okay to include them in codebase summaries.
|
||||
|
||||
<style>
|
||||
Editing code:
|
||||
- Make sure to take previous edits into account.
|
||||
|
||||
@@ -1136,8 +1136,7 @@
|
||||
"code_actions_on_format": {},
|
||||
// Settings related to running tasks.
|
||||
"tasks": {
|
||||
"variables": {},
|
||||
"enabled": true
|
||||
"variables": {}
|
||||
},
|
||||
// An object whose keys are language names, and whose values
|
||||
// are arrays of filenames or extensions of files that should
|
||||
@@ -1457,8 +1456,6 @@
|
||||
"lsp": {
|
||||
// Specify the LSP name as a key here.
|
||||
// "rust-analyzer": {
|
||||
// // A special flag for rust-analyzer integration, to use server-provided tasks
|
||||
// enable_lsp_tasks": true,
|
||||
// // These initialization options are merged into Zed's defaults
|
||||
// "initialization_options": {
|
||||
// "check": {
|
||||
|
||||
@@ -21,7 +21,10 @@ use gpui::{
|
||||
linear_color_stop, linear_gradient, list, percentage, pulsating_between,
|
||||
};
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use language_model::{ConfiguredModel, LanguageModelRegistry, LanguageModelToolUseId, Role};
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModelRegistry, LanguageModelToolUseId, Role, StringToolOutput,
|
||||
ToolOutput,
|
||||
};
|
||||
use markdown::parser::CodeBlockKind;
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown, without_fences};
|
||||
use project::ProjectItem as _;
|
||||
@@ -36,6 +39,7 @@ use text::ToPoint;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
use util::markdown::MarkdownString;
|
||||
use workspace::{OpenOptions, Workspace};
|
||||
|
||||
use crate::context_store::ContextStore;
|
||||
@@ -74,7 +78,7 @@ struct RenderedMessage {
|
||||
struct RenderedToolUse {
|
||||
label: Entity<Markdown>,
|
||||
input: Entity<Markdown>,
|
||||
output: Entity<Markdown>,
|
||||
output: ToolOutput,
|
||||
}
|
||||
|
||||
impl RenderedMessage {
|
||||
@@ -729,21 +733,28 @@ impl ActiveThread {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_label: impl Into<SharedString>,
|
||||
tool_input: &serde_json::Value,
|
||||
tool_output: SharedString,
|
||||
tool_output: ToolOutput,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let rendered = RenderedToolUse {
|
||||
label: render_tool_use_markdown(tool_label.into(), self.language_registry.clone(), cx),
|
||||
input: render_tool_use_markdown(
|
||||
format!(
|
||||
"```json\n{}\n```",
|
||||
serde_json::to_string_pretty(tool_input).unwrap_or_default()
|
||||
MarkdownString::code_block(
|
||||
"json",
|
||||
&serde_json::to_string_pretty(tool_input).unwrap_or_default(),
|
||||
)
|
||||
.to_string()
|
||||
.into(),
|
||||
self.language_registry.clone(),
|
||||
cx,
|
||||
),
|
||||
output: render_tool_use_markdown(tool_output, self.language_registry.clone(), cx),
|
||||
output: render_tool_use_markdown(
|
||||
tool_output.clone(),
|
||||
self.language_registry.clone(),
|
||||
cx,
|
||||
),
|
||||
output: StringToolOutput::new(tool_output, language_registry: Arc<LanguageRegistry>),
|
||||
|
||||
};
|
||||
self.rendered_tool_uses
|
||||
.insert(tool_use_id.clone(), rendered);
|
||||
@@ -2094,24 +2105,50 @@ impl ActiveThread {
|
||||
results_content_container()
|
||||
.border_t_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
.child(
|
||||
Label::new("Result")
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
.buffer_font(cx),
|
||||
)
|
||||
.child(div().w_full().text_ui_sm(cx).children(
|
||||
rendered_tool_use.as_ref().map(|rendered| {
|
||||
MarkdownElement::new(
|
||||
rendered.output.clone(),
|
||||
tool_use_markdown_style(window, cx),
|
||||
)
|
||||
.on_url_click({
|
||||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(text, workspace.clone(), window, cx);
|
||||
let tool_name = tool_use.name.to_string();
|
||||
let tool_registry = assistant_tool::ToolRegistry::global(cx);
|
||||
|
||||
if let Some(_tool) = tool_registry.tool(&tool_name) {
|
||||
// Tool doesn't have a render method, but ToolOutput does
|
||||
match rendered.output.render(window, cx) {
|
||||
Some(rendered) => rendered,
|
||||
None => {
|
||||
// Default to rendering the output as markdown
|
||||
div()
|
||||
.child(
|
||||
Label::new("Result")
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
.buffer_font(cx),
|
||||
)
|
||||
.child(
|
||||
div().w_full().text_ui_sm(cx).child(
|
||||
MarkdownElement::new(
|
||||
rendered.output.clone(),
|
||||
tool_use_markdown_style(window, cx),
|
||||
)
|
||||
.on_url_click({
|
||||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(
|
||||
text,
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
log::error!("Tool not found: {tool_name}");
|
||||
gpui::Empty.into_any_element()
|
||||
}
|
||||
}),
|
||||
)),
|
||||
),
|
||||
@@ -2158,16 +2195,30 @@ impl ActiveThread {
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.children(rendered_tool_use.as_ref().map(|rendered| {
|
||||
MarkdownElement::new(
|
||||
rendered.output.clone(),
|
||||
tool_use_markdown_style(window, cx),
|
||||
)
|
||||
.on_url_click({
|
||||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(text, workspace.clone(), window, cx);
|
||||
}
|
||||
})
|
||||
let tool_name = tool_use.name.to_string();
|
||||
let tool_registry = assistant_tool::ToolRegistry::global(cx);
|
||||
|
||||
tool_registry
|
||||
.tool(&tool_name)
|
||||
.and_then(|_tool| None) // Tool doesn't have a render method, but ToolOutput does
|
||||
.unwrap_or_else(|| {
|
||||
MarkdownElement::new(
|
||||
rendered.output.clone(),
|
||||
tool_use_markdown_style(window, cx),
|
||||
)
|
||||
.on_url_click({
|
||||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(
|
||||
text,
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
})
|
||||
})),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -106,7 +106,7 @@ impl ContextPickerCompletionProvider {
|
||||
.iter()
|
||||
.map(|mode| {
|
||||
Completion {
|
||||
replace_range: source_range.clone(),
|
||||
old_range: source_range.clone(),
|
||||
new_text: format!("@{} ", mode.mention_prefix()),
|
||||
label: CodeLabel::plain(mode.label().to_string(), None),
|
||||
icon_path: Some(mode.icon().path().into()),
|
||||
@@ -160,7 +160,7 @@ impl ContextPickerCompletionProvider {
|
||||
let new_text = MentionLink::for_thread(&thread_entry);
|
||||
let new_text_len = new_text.len();
|
||||
Completion {
|
||||
replace_range: source_range.clone(),
|
||||
old_range: source_range.clone(),
|
||||
new_text,
|
||||
label: CodeLabel::plain(thread_entry.summary.to_string(), None),
|
||||
documentation: None,
|
||||
@@ -205,7 +205,7 @@ impl ContextPickerCompletionProvider {
|
||||
let new_text = MentionLink::for_fetch(&url_to_fetch);
|
||||
let new_text_len = new_text.len();
|
||||
Completion {
|
||||
replace_range: source_range.clone(),
|
||||
old_range: source_range.clone(),
|
||||
new_text,
|
||||
label: CodeLabel::plain(url_to_fetch.to_string(), None),
|
||||
documentation: None,
|
||||
@@ -287,7 +287,7 @@ impl ContextPickerCompletionProvider {
|
||||
let new_text = MentionLink::for_file(&file_name, &full_path);
|
||||
let new_text_len = new_text.len();
|
||||
Completion {
|
||||
replace_range: source_range.clone(),
|
||||
old_range: source_range.clone(),
|
||||
new_text,
|
||||
label,
|
||||
documentation: None,
|
||||
@@ -350,7 +350,7 @@ impl ContextPickerCompletionProvider {
|
||||
let new_text = MentionLink::for_symbol(&symbol.name, &full_path);
|
||||
let new_text_len = new_text.len();
|
||||
Some(Completion {
|
||||
replace_range: source_range.clone(),
|
||||
old_range: source_range.clone(),
|
||||
new_text,
|
||||
label,
|
||||
documentation: None,
|
||||
|
||||
@@ -1414,7 +1414,7 @@ impl Thread {
|
||||
|
||||
for tool_use in pending_tool_uses.iter() {
|
||||
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
|
||||
if tool.needs_confirmation(&tool_use.input, cx)
|
||||
if tool.needs_confirmation()
|
||||
&& !AssistantSettings::get_global(cx).always_allow_tool_actions
|
||||
{
|
||||
self.tool_use.confirm_tool_use(
|
||||
|
||||
@@ -6,6 +6,7 @@ use collections::HashMap;
|
||||
use futures::FutureExt as _;
|
||||
use futures::future::Shared;
|
||||
use gpui::{App, SharedString, Task};
|
||||
use language_model::ToolOutput;
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolResult,
|
||||
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
|
||||
@@ -32,7 +33,7 @@ pub enum ToolUseStatus {
|
||||
NeedsConfirmation,
|
||||
Pending,
|
||||
Running,
|
||||
Finished(SharedString),
|
||||
Finished(ToolOutput),
|
||||
Error(SharedString),
|
||||
}
|
||||
|
||||
@@ -131,6 +132,7 @@ impl ToolUseState {
|
||||
tool_name: tool_use.clone(),
|
||||
is_error: tool_result.is_error,
|
||||
content: tool_result.content.clone(),
|
||||
tool_output: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -153,6 +155,7 @@ impl ToolUseState {
|
||||
tool_name: tool_use.name.clone(),
|
||||
content: "Tool canceled by user".into(),
|
||||
is_error: true,
|
||||
tool_output: None,
|
||||
},
|
||||
);
|
||||
pending_tools.push(tool_use.clone());
|
||||
@@ -201,7 +204,7 @@ impl ToolUseState {
|
||||
|
||||
let (icon, needs_confirmation) = if let Some(tool) = self.tools.tool(&tool_use.name, cx)
|
||||
{
|
||||
(tool.icon(), tool.needs_confirmation(&tool_use.input, cx))
|
||||
(tool.icon(), tool.needs_confirmation())
|
||||
} else {
|
||||
(IconName::Cog, false)
|
||||
};
|
||||
@@ -331,7 +334,7 @@ impl ToolUseState {
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
output: Result<String>,
|
||||
output: Result<ToolOutput>,
|
||||
cx: &App,
|
||||
) -> Option<PendingToolUse> {
|
||||
match output {
|
||||
@@ -346,16 +349,19 @@ impl ToolUseState {
|
||||
.map(|model| model.model.max_token_count() * BYTES_PER_TOKEN_ESTIMATE)
|
||||
.unwrap_or(usize::MAX);
|
||||
|
||||
let tool_result = if tool_result.len() <= tool_output_limit {
|
||||
tool_result
|
||||
} else {
|
||||
let truncated = truncate_lines_to_byte_limit(&tool_result, tool_output_limit);
|
||||
// Get string representation of the tool result
|
||||
let response_text = tool_result.response_for_model();
|
||||
|
||||
format!(
|
||||
"Tool result too long. The first {} bytes:\n\n{}",
|
||||
truncated.len(),
|
||||
truncated
|
||||
)
|
||||
// Check length and truncate if needed
|
||||
let final_tool_result = if response_text.len() <= tool_output_limit {
|
||||
response_text.to_string()
|
||||
} else {
|
||||
let response_string = response_text.to_string();
|
||||
let truncated =
|
||||
truncate_lines_to_byte_limit(&response_string, tool_output_limit);
|
||||
let truncated_len = truncated.len();
|
||||
|
||||
format!("Tool result too long. The first {truncated_len} bytes:\n\n{truncated}")
|
||||
};
|
||||
|
||||
self.tool_results.insert(
|
||||
@@ -363,8 +369,9 @@ impl ToolUseState {
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name,
|
||||
content: tool_result.into(),
|
||||
content: Arc::from(final_tool_result),
|
||||
is_error: false,
|
||||
tool_output: Some(Arc::new(tool_result)),
|
||||
},
|
||||
);
|
||||
self.pending_tool_uses_by_id.remove(&tool_use_id)
|
||||
@@ -377,6 +384,7 @@ impl ToolUseState {
|
||||
tool_name,
|
||||
content: err.to_string().into(),
|
||||
is_error: true,
|
||||
tool_output: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -451,6 +459,7 @@ impl ToolUseState {
|
||||
} else {
|
||||
tool_result.content.clone()
|
||||
},
|
||||
tool_output: tool_result.tool_output.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ impl SlashCommandCompletionProvider {
|
||||
) as Arc<_>
|
||||
});
|
||||
Some(project::Completion {
|
||||
replace_range: name_range.clone(),
|
||||
old_range: name_range.clone(),
|
||||
documentation: Some(CompletionDocumentation::SingleLine(
|
||||
command.description().into(),
|
||||
)),
|
||||
@@ -219,7 +219,7 @@ impl SlashCommandCompletionProvider {
|
||||
}
|
||||
|
||||
project::Completion {
|
||||
replace_range: if new_argument.replace_previous_arguments {
|
||||
old_range: if new_argument.replace_previous_arguments {
|
||||
argument_range.clone()
|
||||
} else {
|
||||
last_argument_range.clone()
|
||||
|
||||
@@ -8,13 +8,15 @@ use std::fmt::Formatter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use gpui::{self, App, Entity, SharedString, Task};
|
||||
use icons::IconName;
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
use language_model::ToolOutput;
|
||||
use project::Project;
|
||||
|
||||
pub use crate::action_log::*;
|
||||
// StringToolOutput is now directly imported from language_model
|
||||
pub use crate::tool_registry::*;
|
||||
pub use crate::tool_working_set::*;
|
||||
|
||||
@@ -48,7 +50,7 @@ pub trait Tool: 'static + Send + Sync {
|
||||
|
||||
/// Returns true iff the tool needs the users's confirmation
|
||||
/// before having permission to run.
|
||||
fn needs_confirmation(&self, input: &serde_json::Value, cx: &App) -> bool;
|
||||
fn needs_confirmation(&self) -> bool;
|
||||
|
||||
/// Returns the JSON schema that describes the tool's input.
|
||||
fn input_schema(&self, _: LanguageModelToolSchemaFormat) -> serde_json::Value {
|
||||
@@ -66,7 +68,7 @@ pub trait Tool: 'static + Send + Sync {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>>;
|
||||
) -> Task<Result<ToolOutput>>;
|
||||
}
|
||||
|
||||
impl Debug for dyn Tool {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::io::BufReader;
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt, FutureExt};
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -29,7 +30,7 @@ impl Tool for BashTool {
|
||||
"bash".to_string()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@ impl Tool for BashTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input: BashToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -123,7 +124,7 @@ impl Tool for BashTool {
|
||||
worktree.read(cx).abs_path()
|
||||
};
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
cx.spawn(async move |_| -> Result<ToolOutput> {
|
||||
// Add 2>&1 to merge stderr into stdout for proper interleaving.
|
||||
let command = format!("({}) 2>&1", input.command);
|
||||
|
||||
@@ -158,33 +159,14 @@ impl Tool for BashTool {
|
||||
let mut buffer = vec![0; LIMIT + 1];
|
||||
let bytes_read = reader.read(&mut buffer).await?;
|
||||
|
||||
let mut timer = cx
|
||||
.background_executor()
|
||||
.timer(std::time::Duration::from_secs(10))
|
||||
.fuse();
|
||||
|
||||
// Repeatedly fill the output reader's buffer without copying it.
|
||||
loop {
|
||||
let mut skipped_bytes = reader.fill_buf().fuse();
|
||||
|
||||
futures::select! {
|
||||
skipped_bytes = skipped_bytes => {
|
||||
let skipped_bytes = skipped_bytes?;
|
||||
if skipped_bytes.is_empty() {
|
||||
break;
|
||||
}
|
||||
let skipped_bytes_len = skipped_bytes.len();
|
||||
reader.consume_unpin(skipped_bytes_len);
|
||||
|
||||
timer = cx
|
||||
.background_executor()
|
||||
.timer(std::time::Duration::from_secs(10))
|
||||
.fuse();
|
||||
}
|
||||
_ = timer => {
|
||||
return Err(anyhow!("Command timed out. Output so far:\n{}", String::from_utf8_lossy(&buffer[..bytes_read])));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
let output_bytes = &buffer[..bytes_read];
|
||||
@@ -211,25 +193,21 @@ impl Tool for BashTool {
|
||||
String::from_utf8_lossy(&output_bytes).into()
|
||||
};
|
||||
|
||||
let output_with_status = if status.success() {
|
||||
if status.success() {
|
||||
if output_string.is_empty() {
|
||||
"Command executed successfully.".to_string()
|
||||
Ok(StringToolOutput::new("Command executed successfully."))
|
||||
} else {
|
||||
output_string.to_string()
|
||||
Ok(StringToolOutput::new(output_string))
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
Ok(StringToolOutput::new(format!(
|
||||
"{}{}{}{}",
|
||||
ERR_MESSAGE_1,
|
||||
status.code().unwrap_or(-1),
|
||||
ERR_MESSAGE_2,
|
||||
output_string,
|
||||
)
|
||||
};
|
||||
|
||||
debug_assert!(output_with_status.len() <= STDOUT_LIMIT);
|
||||
|
||||
Ok(output_with_status)
|
||||
)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolWorkingSet};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::future::join_all;
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -151,17 +152,8 @@ impl Tool for BatchTool {
|
||||
"batch_tool".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, input: &serde_json::Value, cx: &App) -> bool {
|
||||
serde_json::from_value::<BatchToolInput>(input.clone())
|
||||
.map(|input| {
|
||||
let working_set = ToolWorkingSet::default();
|
||||
input.invocations.iter().any(|invocation| {
|
||||
working_set
|
||||
.tool(&invocation.name, cx)
|
||||
.map_or(false, |tool| tool.needs_confirmation(&invocation.input, cx))
|
||||
})
|
||||
})
|
||||
.unwrap_or(false)
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
@@ -219,7 +211,7 @@ impl Tool for BatchTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<BatchToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -289,7 +281,7 @@ impl Tool for BatchTool {
|
||||
match result {
|
||||
Ok(output) => {
|
||||
formatted_results
|
||||
.push_str(&format!("Tool '{}' result:\n{}\n\n", tool_name, output));
|
||||
.push_str(&format!("Tool '{}' result:\n{}\n\n", tool_name, output.response_for_model()));
|
||||
}
|
||||
Err(err) => {
|
||||
error_occurred = true;
|
||||
@@ -304,7 +296,7 @@ impl Tool for BatchTool {
|
||||
.push_str("Note: Some tool invocations failed. See individual results above.");
|
||||
}
|
||||
|
||||
Ok(formatted_results.trim().to_string())
|
||||
Ok(StringToolOutput::new(formatted_results.trim().to_string()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::sync::Arc;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use collections::IndexMap;
|
||||
use gpui::{App, AsyncApp, Entity, Task};
|
||||
use language::{OutlineItem, ParseStatus, Point};
|
||||
@@ -79,7 +80,7 @@ impl Tool for CodeSymbolsTool {
|
||||
"code_symbols".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -129,7 +130,7 @@ impl Tool for CodeSymbolsTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<CodeSymbolsInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -147,8 +148,8 @@ impl Tool for CodeSymbolsTool {
|
||||
};
|
||||
|
||||
cx.spawn(async move |cx| match input.path {
|
||||
Some(path) => file_outline(project, path, action_log, regex, input.offset, cx).await,
|
||||
None => project_symbols(project, regex, input.offset, cx).await,
|
||||
Some(path) => file_outline(project, path, action_log, regex, input.offset, cx).await.map(StringToolOutput::new),
|
||||
None => project_symbols(project, regex, input.offset, cx).await.map(StringToolOutput::new),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -43,7 +44,7 @@ impl Tool for CopyPathTool {
|
||||
"copy_path".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ impl Tool for CopyPathTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<CopyPathToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -105,10 +106,10 @@ impl Tool for CopyPathTool {
|
||||
|
||||
cx.background_spawn(async move {
|
||||
match copy_task.await {
|
||||
Ok(_) => Ok(format!(
|
||||
Ok(_) => Ok(StringToolOutput::new(format!(
|
||||
"Copied {} to {}",
|
||||
input.source_path, input.destination_path
|
||||
)),
|
||||
))),
|
||||
Err(err) => Err(anyhow!(
|
||||
"Failed to copy {} to {}: {}",
|
||||
input.source_path,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -33,7 +34,7 @@ impl Tool for CreateDirectoryTool {
|
||||
"create_directory".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ impl Tool for CreateDirectoryTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<CreateDirectoryToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -87,7 +88,7 @@ impl Tool for CreateDirectoryTool {
|
||||
.await
|
||||
.map_err(|err| anyhow!("Unable to create directory {destination_path}: {err}"))?;
|
||||
|
||||
Ok(format!("Created directory {destination_path}"))
|
||||
Ok(StringToolOutput::new(format!("Created directory {destination_path}")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -40,7 +41,7 @@ impl Tool for CreateFileTool {
|
||||
"create_file".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ impl Tool for CreateFileTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<CreateFileToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -104,7 +105,7 @@ impl Tool for CreateFileTool {
|
||||
.await
|
||||
.map_err(|err| anyhow!("Unable to save buffer for {destination_path}: {err}"))?;
|
||||
|
||||
Ok(format!("Created file {destination_path}"))
|
||||
Ok(StringToolOutput::new(format!("Created file {destination_path}")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::{SinkExt, StreamExt, channel::mpsc};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -33,7 +34,7 @@ impl Tool for DeletePathTool {
|
||||
"delete_path".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ impl Tool for DeletePathTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let path_str = match serde_json::from_value::<DeletePathToolInput>(input) {
|
||||
Ok(input) => input.path,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -124,7 +125,7 @@ impl Tool for DeletePathTool {
|
||||
|
||||
match delete {
|
||||
Some(deletion_task) => match deletion_task.await {
|
||||
Ok(()) => Ok(format!("Deleted {path_str}")),
|
||||
Ok(()) => Ok(StringToolOutput::new(format!("Deleted {path_str}"))),
|
||||
Err(err) => Err(anyhow!("Failed to delete {path_str}: {err}")),
|
||||
},
|
||||
None => Err(anyhow!(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::{DiagnosticSeverity, OffsetRangeExt};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -46,7 +47,7 @@ impl Tool for DiagnosticsTool {
|
||||
"diagnostics".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ impl Tool for DiagnosticsTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
match serde_json::from_value::<DiagnosticsToolInput>(input)
|
||||
.ok()
|
||||
.and_then(|input| input.path)
|
||||
@@ -120,9 +121,9 @@ impl Tool for DiagnosticsTool {
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
Ok("File doesn't have errors or warnings!".to_string())
|
||||
Ok(StringToolOutput::new("File doesn't have errors or warnings!"))
|
||||
} else {
|
||||
Ok(output)
|
||||
Ok(StringToolOutput::new(output))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -155,9 +156,9 @@ impl Tool for DiagnosticsTool {
|
||||
});
|
||||
|
||||
if has_diagnostics {
|
||||
Task::ready(Ok(output))
|
||||
Task::ready(Ok(StringToolOutput::new(output)))
|
||||
} else {
|
||||
Task::ready(Ok("No errors or warnings found in the project.".to_string()))
|
||||
Task::ready(Ok(StringToolOutput::new("No errors or warnings found in the project.")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::sync::Arc;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{App, AppContext as _, Entity, Task};
|
||||
use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown};
|
||||
@@ -116,7 +117,7 @@ impl Tool for FetchTool {
|
||||
"fetch".to_string()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -146,7 +147,7 @@ impl Tool for FetchTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<FetchToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -164,7 +165,7 @@ impl Tool for FetchTool {
|
||||
bail!("no textual content found");
|
||||
}
|
||||
|
||||
Ok(text)
|
||||
Ok(StringToolOutput::new(text))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{replace::replace_with_flexible_indent, schema::json_schema_for};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, AsyncApp, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -129,7 +130,7 @@ impl Tool for FindReplaceFileTool {
|
||||
"find_replace_file".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ impl Tool for FindReplaceFileTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<FindReplaceFileToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -251,7 +252,7 @@ impl Tool for FindReplaceFileTool {
|
||||
}).await;
|
||||
|
||||
|
||||
Ok(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str))
|
||||
Ok(StringToolOutput::new(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str)))
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -44,7 +45,7 @@ impl Tool for ListDirectoryTool {
|
||||
"list_directory".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ impl Tool for ListDirectoryTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<ListDirectoryToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -101,7 +102,7 @@ impl Tool for ListDirectoryTool {
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
return Task::ready(Ok(output));
|
||||
return Task::ready(Ok(StringToolOutput::new(output)));
|
||||
}
|
||||
|
||||
let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else {
|
||||
@@ -133,8 +134,8 @@ impl Tool for ListDirectoryTool {
|
||||
.unwrap();
|
||||
}
|
||||
if output.is_empty() {
|
||||
return Task::ready(Ok(format!("{} is empty.", input.path)));
|
||||
return Task::ready(Ok(StringToolOutput::new(format!("{} is empty.", input.path))));
|
||||
}
|
||||
Task::ready(Ok(output))
|
||||
Task::ready(Ok(StringToolOutput::new(output)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -42,7 +43,7 @@ impl Tool for MovePathTool {
|
||||
"move_path".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ impl Tool for MovePathTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<MovePathToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -116,10 +117,10 @@ impl Tool for MovePathTool {
|
||||
|
||||
cx.background_spawn(async move {
|
||||
match rename_task.await {
|
||||
Ok(_) => Ok(format!(
|
||||
Ok(_) => Ok(StringToolOutput::new(format!(
|
||||
"Moved {} to {}",
|
||||
input.source_path, input.destination_path
|
||||
)),
|
||||
))),
|
||||
Err(err) => Err(anyhow!(
|
||||
"Failed to move {} to {}: {}",
|
||||
input.source_path,
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use chrono::{Local, Utc};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -33,7 +34,7 @@ impl Tool for NowTool {
|
||||
"now".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ impl Tool for NowTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input: NowToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -72,6 +73,6 @@ impl Tool for NowTool {
|
||||
};
|
||||
let text = format!("The current datetime is {now}.");
|
||||
|
||||
Task::ready(Ok(text))
|
||||
Task::ready(Ok(StringToolOutput::new(text)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -23,7 +24,7 @@ impl Tool for OpenTool {
|
||||
"open".to_string()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ impl Tool for OpenTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input: OpenToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -62,7 +63,7 @@ impl Tool for OpenTool {
|
||||
cx.background_spawn(async move {
|
||||
open::that(&input.path_or_url).context("Failed to open URL or file path")?;
|
||||
|
||||
Ok(format!("Successfully opened {}", input.path_or_url))
|
||||
Ok(StringToolOutput::new(format!("Successfully opened {}", input.path_or_url)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -41,7 +42,7 @@ impl Tool for PathSearchTool {
|
||||
"path_search".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -71,7 +72,7 @@ impl Tool for PathSearchTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let (offset, glob) = match serde_json::from_value::<PathSearchToolInput>(input) {
|
||||
Ok(input) => (input.offset, input.glob),
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -110,7 +111,7 @@ impl Tool for PathSearchTool {
|
||||
}
|
||||
|
||||
if matches.is_empty() {
|
||||
Ok(format!("No paths in the project matched the glob {glob:?}"))
|
||||
Ok(StringToolOutput::new(format!("No paths in the project matched the glob {glob:?}")))
|
||||
} else {
|
||||
// Sort to group entries in the same directory together.
|
||||
matches.sort();
|
||||
@@ -134,7 +135,7 @@ impl Tool for PathSearchTool {
|
||||
matches.join("\n")
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
Ok(StringToolOutput::new(response))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use itertools::Itertools;
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -51,7 +52,7 @@ impl Tool for ReadFileTool {
|
||||
"read_file".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -88,7 +89,7 @@ impl Tool for ReadFileTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<ReadFileToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -114,9 +115,9 @@ impl Tool for ReadFileTool {
|
||||
let lines = text.split('\n').skip(start - 1);
|
||||
if let Some(end) = input.end_line {
|
||||
let count = end.saturating_sub(start).max(1); // Ensure at least 1 line
|
||||
Itertools::intersperse(lines.take(count), "\n").collect()
|
||||
Itertools::intersperse(lines.take(count), "\n").collect::<String>()
|
||||
} else {
|
||||
Itertools::intersperse(lines, "\n").collect()
|
||||
Itertools::intersperse(lines, "\n").collect::<String>()
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -124,7 +125,7 @@ impl Tool for ReadFileTool {
|
||||
log.buffer_read(buffer, cx);
|
||||
})?;
|
||||
|
||||
Ok(result)
|
||||
Ok(StringToolOutput::new(result))
|
||||
} else {
|
||||
// No line ranges specified, so check file size to see if it's too big.
|
||||
let file_size = buffer.read_with(cx, |buffer, _cx| buffer.text().len())?;
|
||||
@@ -137,13 +138,13 @@ impl Tool for ReadFileTool {
|
||||
log.buffer_read(buffer, cx);
|
||||
})?;
|
||||
|
||||
Ok(result)
|
||||
Ok(StringToolOutput::new(result))
|
||||
} else {
|
||||
// File is too big, so return an error with the outline
|
||||
// and a suggestion to read again with line numbers.
|
||||
let outline = file_outline(project, file_path, action_log, None, 0, cx).await?;
|
||||
|
||||
Ok(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start_line and end_line fields to see the implementations of symbols in the outline."))
|
||||
Ok(StringToolOutput::new(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start_line and end_line fields to see the implementations of symbols in the outline.")))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::OffsetRangeExt;
|
||||
@@ -44,7 +45,7 @@ impl Tool for RegexSearchTool {
|
||||
"regex_search".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ impl Tool for RegexSearchTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
const CONTEXT_LINES: u32 = 2;
|
||||
|
||||
let (offset, regex) = match serde_json::from_value::<RegexSearchToolInput>(input) {
|
||||
@@ -179,16 +180,16 @@ impl Tool for RegexSearchTool {
|
||||
}
|
||||
|
||||
if matches_found == 0 {
|
||||
Ok("No matches found".to_string())
|
||||
Ok(StringToolOutput::new("No matches found".to_string()))
|
||||
} else if has_more_matches {
|
||||
Ok(format!(
|
||||
Ok(StringToolOutput::new(format!(
|
||||
"Showing matches {}-{} (there were more matches found; use offset: {} to see next page):\n{output}",
|
||||
offset + 1,
|
||||
offset + matches_found,
|
||||
offset + RESULTS_PER_PAGE,
|
||||
))
|
||||
)))
|
||||
} else {
|
||||
Ok(format!("Found {matches_found} matches:\n{output}"))
|
||||
Ok(StringToolOutput::new(format!("Found {matches_found} matches:\n{output}")))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AsyncApp, Entity, Task};
|
||||
use language::{self, Anchor, Buffer, BufferSnapshot, Location, Point, ToPoint, ToPointUtf16};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -72,7 +73,7 @@ impl Tool for SymbolInfoTool {
|
||||
"symbol_info".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -122,7 +123,7 @@ impl Tool for SymbolInfoTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<SymbolInfoToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -203,7 +204,7 @@ impl Tool for SymbolInfoTool {
|
||||
if output.is_empty() {
|
||||
Err(anyhow!("None found."))
|
||||
} else {
|
||||
Ok(output)
|
||||
Ok(StringToolOutput::new(output))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -24,7 +25,7 @@ impl Tool for ThinkingTool {
|
||||
"thinking".to_string()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -51,10 +52,10 @@ impl Tool for ThinkingTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
// This tool just "thinks out loud" and doesn't perform any actions.
|
||||
Task::ready(match serde_json::from_value::<ThinkingToolInput>(input) {
|
||||
Ok(_input) => Ok("Finished thinking.".to_string()),
|
||||
Ok(_input) => Ok(StringToolOutput::new("Finished thinking.")),
|
||||
Err(err) => Err(anyhow!(err)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -318,7 +318,6 @@ impl Server {
|
||||
.add_request_handler(forward_read_only_project_request::<proto::OpenUncommittedDiff>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::LspExtExpandMacro>)
|
||||
.add_request_handler(forward_read_only_project_request::<proto::LspExtOpenDocs>)
|
||||
.add_request_handler(forward_mutating_project_request::<proto::LspExtRunnables>)
|
||||
.add_request_handler(
|
||||
forward_read_only_project_request::<proto::LspExtSwitchSourceHeader>,
|
||||
)
|
||||
|
||||
@@ -309,7 +309,7 @@ impl MessageEditor {
|
||||
.map(|mat| {
|
||||
let (new_text, label) = completion_fn(&mat);
|
||||
Completion {
|
||||
replace_range: range.clone(),
|
||||
old_range: range.clone(),
|
||||
new_text,
|
||||
label,
|
||||
icon_path: None,
|
||||
|
||||
@@ -3,62 +3,37 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{
|
||||
AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, pattern_slash, prelude::*,
|
||||
px, rems,
|
||||
};
|
||||
use gpui::{AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, prelude::*, px};
|
||||
use linkme::distributed_slice;
|
||||
use parking_lot::RwLock;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
pub trait Component {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
fn scope() -> Option<ComponentScope>;
|
||||
fn name() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
/// Returns a name that the component should be sorted by.
|
||||
///
|
||||
/// Implement this if the component should be sorted in an alternate order than its name.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// For example, to group related components together when sorted:
|
||||
///
|
||||
/// - Button -> ButtonA
|
||||
/// - IconButton -> ButtonBIcon
|
||||
/// - ToggleButton -> ButtonCToggle
|
||||
///
|
||||
/// This naming scheme keeps these components together and allows them to /// be sorted in a logical order.
|
||||
fn sort_name() -> &'static str {
|
||||
Self::name()
|
||||
}
|
||||
fn description() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComponentPreview: Component {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement;
|
||||
}
|
||||
|
||||
#[distributed_slice]
|
||||
pub static __ALL_COMPONENTS: [fn()] = [..];
|
||||
|
||||
#[distributed_slice]
|
||||
pub static __ALL_PREVIEWS: [fn()] = [..];
|
||||
|
||||
pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
|
||||
LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
|
||||
|
||||
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>>,
|
||||
components: Vec<(Option<ComponentScope>, &'static str, Option<&'static str>)>,
|
||||
previews: HashMap<&'static str, fn(&mut Window, &mut App) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl ComponentRegistry {
|
||||
@@ -72,16 +47,30 @@ impl ComponentRegistry {
|
||||
|
||||
pub fn init() {
|
||||
let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
|
||||
let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
|
||||
|
||||
for f in component_fns {
|
||||
f();
|
||||
}
|
||||
for f in preview_fns {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
let component_data = (T::scope(), T::name(), T::description());
|
||||
COMPONENT_DATA.write().components.push(component_data);
|
||||
}
|
||||
|
||||
pub fn register_preview<T: ComponentPreview>() {
|
||||
let preview_data = (
|
||||
T::name(),
|
||||
T::preview as fn(&mut Window, &mut App) -> AnyElement,
|
||||
);
|
||||
COMPONENT_DATA
|
||||
.write()
|
||||
.previews
|
||||
.insert(preview_data.0, preview_data.1);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -91,41 +80,29 @@ pub struct ComponentId(pub &'static str);
|
||||
pub struct ComponentMetadata {
|
||||
id: ComponentId,
|
||||
name: SharedString,
|
||||
sort_name: SharedString,
|
||||
scope: ComponentScope,
|
||||
scope: Option<ComponentScope>,
|
||||
description: Option<SharedString>,
|
||||
preview: Option<fn(&mut Window, &mut App) -> Option<AnyElement>>,
|
||||
preview: Option<fn(&mut Window, &mut App) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl ComponentMetadata {
|
||||
pub fn id(&self) -> ComponentId {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> SharedString {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn sort_name(&self) -> SharedString {
|
||||
self.sort_name.clone()
|
||||
}
|
||||
|
||||
pub fn scopeless_name(&self) -> SharedString {
|
||||
self.name
|
||||
.clone()
|
||||
.split("::")
|
||||
.last()
|
||||
.unwrap_or(&self.name)
|
||||
.to_string()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> ComponentScope {
|
||||
pub fn scope(&self) -> Option<ComponentScope> {
|
||||
self.scope.clone()
|
||||
}
|
||||
|
||||
pub fn description(&self) -> Option<SharedString> {
|
||||
self.description.clone()
|
||||
}
|
||||
pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> Option<AnyElement>> {
|
||||
|
||||
pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> AnyElement> {
|
||||
self.preview
|
||||
}
|
||||
}
|
||||
@@ -136,18 +113,26 @@ impl AllComponents {
|
||||
pub fn new() -> Self {
|
||||
AllComponents(HashMap::default())
|
||||
}
|
||||
|
||||
/// Returns all components with previews
|
||||
pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().filter(|c| c.preview.is_some()).collect()
|
||||
}
|
||||
|
||||
/// Returns all components with previews sorted by name
|
||||
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
|
||||
}
|
||||
|
||||
/// Returns all components
|
||||
pub fn all(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().collect()
|
||||
}
|
||||
|
||||
/// Returns all components sorted by name
|
||||
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());
|
||||
@@ -157,6 +142,7 @@ impl AllComponents {
|
||||
|
||||
impl Deref for AllComponents {
|
||||
type Target = HashMap<ComponentId, ComponentMetadata>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
@@ -171,127 +157,139 @@ 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 {
|
||||
|
||||
for (scope, 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 ComponentScope {
|
||||
Collaboration,
|
||||
DataDisplay,
|
||||
Editor,
|
||||
Images,
|
||||
Input,
|
||||
Layout,
|
||||
Loading,
|
||||
Navigation,
|
||||
None,
|
||||
Input,
|
||||
Notification,
|
||||
Overlays,
|
||||
Status,
|
||||
Typography,
|
||||
Editor,
|
||||
Collaboration,
|
||||
VersionControl,
|
||||
Unknown(SharedString),
|
||||
}
|
||||
|
||||
impl Display for ComponentScope {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ComponentScope::Collaboration => write!(f, "Collaboration"),
|
||||
ComponentScope::DataDisplay => write!(f, "Data Display"),
|
||||
ComponentScope::Editor => write!(f, "Editor"),
|
||||
ComponentScope::Images => write!(f, "Images & Icons"),
|
||||
ComponentScope::Input => write!(f, "Forms & Input"),
|
||||
ComponentScope::Layout => write!(f, "Layout & Structure"),
|
||||
ComponentScope::Loading => write!(f, "Loading & Progress"),
|
||||
ComponentScope::Navigation => write!(f, "Navigation"),
|
||||
ComponentScope::None => write!(f, "Unsorted"),
|
||||
ComponentScope::Layout => write!(f, "Layout"),
|
||||
ComponentScope::Input => write!(f, "Input"),
|
||||
ComponentScope::Notification => write!(f, "Notification"),
|
||||
ComponentScope::Overlays => write!(f, "Overlays & Layering"),
|
||||
ComponentScope::Status => write!(f, "Status"),
|
||||
ComponentScope::Typography => write!(f, "Typography"),
|
||||
ComponentScope::Editor => write!(f, "Editor"),
|
||||
ComponentScope::Collaboration => write!(f, "Collaboration"),
|
||||
ComponentScope::VersionControl => write!(f, "Version Control"),
|
||||
ComponentScope::Unknown(name) => write!(f, "Unknown: {}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ComponentScope {
|
||||
fn from(value: &str) -> Self {
|
||||
match value {
|
||||
"Layout" => ComponentScope::Layout,
|
||||
"Input" => ComponentScope::Input,
|
||||
"Notification" => ComponentScope::Notification,
|
||||
"Editor" => ComponentScope::Editor,
|
||||
"Collaboration" => ComponentScope::Collaboration,
|
||||
"Version Control" | "VersionControl" => ComponentScope::VersionControl,
|
||||
_ => ComponentScope::Unknown(SharedString::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ComponentScope {
|
||||
fn from(value: String) -> Self {
|
||||
match value.as_str() {
|
||||
"Layout" => ComponentScope::Layout,
|
||||
"Input" => ComponentScope::Input,
|
||||
"Notification" => ComponentScope::Notification,
|
||||
"Editor" => ComponentScope::Editor,
|
||||
"Collaboration" => ComponentScope::Collaboration,
|
||||
"Version Control" | "VersionControl" => ComponentScope::VersionControl,
|
||||
_ => ComponentScope::Unknown(SharedString::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which side of the preview to show labels on
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExampleLabelSide {
|
||||
/// Left side
|
||||
Left,
|
||||
/// Right side
|
||||
Right,
|
||||
/// Top side
|
||||
#[default]
|
||||
Top,
|
||||
/// Bottom side
|
||||
Bottom,
|
||||
}
|
||||
|
||||
/// A single example of a component.
|
||||
#[derive(IntoElement)]
|
||||
pub struct ComponentExample {
|
||||
pub variant_name: SharedString,
|
||||
pub description: Option<SharedString>,
|
||||
pub element: AnyElement,
|
||||
variant_name: SharedString,
|
||||
element: AnyElement,
|
||||
label_side: ExampleLabelSide,
|
||||
grow: bool,
|
||||
}
|
||||
|
||||
impl RenderOnce for ComponentExample {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(
|
||||
div()
|
||||
.child(self.variant_name.clone())
|
||||
.text_size(rems(1.25))
|
||||
.text_color(cx.theme().colors().text),
|
||||
)
|
||||
.when_some(self.description, |this, description| {
|
||||
this.child(
|
||||
div()
|
||||
.text_size(rems(0.9375))
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(description.clone()),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.w_full()
|
||||
.rounded_xl()
|
||||
.min_h(px(100.))
|
||||
.justify_center()
|
||||
.p_8()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(pattern_slash(
|
||||
cx.theme().colors().surface_background.opacity(0.5),
|
||||
24.0,
|
||||
24.0,
|
||||
))
|
||||
.shadow_sm()
|
||||
.child(self.element),
|
||||
)
|
||||
let base = div().flex();
|
||||
|
||||
let base = match self.label_side {
|
||||
ExampleLabelSide::Right => base.flex_row(),
|
||||
ExampleLabelSide::Left => base.flex_row_reverse(),
|
||||
ExampleLabelSide::Bottom => base.flex_col(),
|
||||
ExampleLabelSide::Top => base.flex_col_reverse(),
|
||||
};
|
||||
|
||||
base.gap_2()
|
||||
.p_2()
|
||||
.text_size(px(10.))
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.when(self.grow, |this| this.flex_1())
|
||||
.when(!self.grow, |this| this.flex_none())
|
||||
.child(self.element)
|
||||
.child(self.variant_name)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentExample {
|
||||
/// Create a new example with the given variant name and example value.
|
||||
pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
|
||||
Self {
|
||||
variant_name: variant_name.into(),
|
||||
element,
|
||||
description: None,
|
||||
label_side: ExampleLabelSide::default(),
|
||||
grow: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
|
||||
self.description = Some(description.into());
|
||||
/// Set the example to grow to fill the available horizontal space.
|
||||
pub fn grow(mut self) -> Self {
|
||||
self.grow = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -311,7 +309,7 @@ impl RenderOnce for ComponentExampleGroup {
|
||||
.flex_col()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.w_full()
|
||||
.when(self.grow, |this| this.w_full().flex_1())
|
||||
.when_some(self.title, |this, title| {
|
||||
this.gap_4().child(
|
||||
div()
|
||||
@@ -338,7 +336,7 @@ impl RenderOnce for ComponentExampleGroup {
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.when(self.vertical, |this| this.flex_col())
|
||||
.items_start()
|
||||
.w_full()
|
||||
.gap_6()
|
||||
@@ -350,6 +348,7 @@ impl RenderOnce for ComponentExampleGroup {
|
||||
}
|
||||
|
||||
impl ComponentExampleGroup {
|
||||
/// Create a new group of examples with the given title.
|
||||
pub fn new(examples: Vec<ComponentExample>) -> Self {
|
||||
Self {
|
||||
title: None,
|
||||
@@ -358,6 +357,8 @@ impl ComponentExampleGroup {
|
||||
vertical: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new group of examples with the given title.
|
||||
pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
|
||||
Self {
|
||||
title: Some(title.into()),
|
||||
@@ -366,16 +367,21 @@ impl ComponentExampleGroup {
|
||||
vertical: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the group to grow to fill the available horizontal space.
|
||||
pub fn grow(mut self) -> Self {
|
||||
self.grow = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Lay the group out vertically.
|
||||
pub fn vertical(mut self) -> Self {
|
||||
self.vertical = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a single example
|
||||
pub fn single_example(
|
||||
variant_name: impl Into<SharedString>,
|
||||
example: AnyElement,
|
||||
@@ -383,10 +389,12 @@ pub fn single_example(
|
||||
ComponentExample::new(variant_name, example)
|
||||
}
|
||||
|
||||
/// Create a group of examples without a title
|
||||
pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
|
||||
ComponentExampleGroup::new(examples)
|
||||
}
|
||||
|
||||
/// Create a group of examples with a title
|
||||
pub fn example_group_with_title(
|
||||
title: impl Into<SharedString>,
|
||||
examples: Vec<ComponentExample>,
|
||||
|
||||
@@ -43,7 +43,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) {
|
||||
language_registry,
|
||||
user_store,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -107,12 +106,10 @@ impl ComponentPreview {
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
user_store: Entity<UserStore>,
|
||||
selected_index: impl Into<Option<usize>>,
|
||||
active_page: Option<PreviewPage>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let sorted_components = components().all_sorted();
|
||||
let selected_index = selected_index.into().unwrap_or(0);
|
||||
let active_page = active_page.unwrap_or(PreviewPage::AllComponents);
|
||||
|
||||
let component_list = ListState::new(
|
||||
sorted_components.len(),
|
||||
@@ -138,7 +135,7 @@ impl ComponentPreview {
|
||||
language_registry,
|
||||
user_store,
|
||||
workspace,
|
||||
active_page,
|
||||
active_page: PreviewPage::AllComponents,
|
||||
component_map: components().0,
|
||||
components: sorted_components,
|
||||
component_list,
|
||||
@@ -172,7 +169,8 @@ impl ComponentPreview {
|
||||
fn scope_ordered_entries(&self) -> Vec<PreviewEntry> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut scope_groups: HashMap<ComponentScope, Vec<ComponentMetadata>> = HashMap::default();
|
||||
let mut scope_groups: HashMap<Option<ComponentScope>, Vec<ComponentMetadata>> =
|
||||
HashMap::default();
|
||||
|
||||
for component in &self.components {
|
||||
scope_groups
|
||||
@@ -194,7 +192,6 @@ impl ComponentPreview {
|
||||
ComponentScope::Notification,
|
||||
ComponentScope::Collaboration,
|
||||
ComponentScope::VersionControl,
|
||||
ComponentScope::None,
|
||||
];
|
||||
|
||||
// Always show all components first
|
||||
@@ -202,27 +199,38 @@ impl ComponentPreview {
|
||||
entries.push(PreviewEntry::Separator);
|
||||
|
||||
for scope in known_scopes.iter() {
|
||||
if let Some(components) = scope_groups.remove(scope) {
|
||||
let scope_key = Some(scope.clone());
|
||||
if let Some(components) = scope_groups.remove(&scope_key) {
|
||||
if !components.is_empty() {
|
||||
entries.push(PreviewEntry::SectionHeader(scope.to_string().into()));
|
||||
let mut sorted_components = components;
|
||||
sorted_components.sort_by_key(|component| component.sort_name());
|
||||
|
||||
for component in sorted_components {
|
||||
for component in components {
|
||||
entries.push(PreviewEntry::Component(component));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(components) = scope_groups.get(&ComponentScope::None) {
|
||||
for (scope, components) in &scope_groups {
|
||||
if let Some(ComponentScope::Unknown(_)) = scope {
|
||||
if !components.is_empty() {
|
||||
if let Some(scope_value) = scope {
|
||||
entries.push(PreviewEntry::SectionHeader(scope_value.to_string().into()));
|
||||
}
|
||||
|
||||
for component in components {
|
||||
entries.push(PreviewEntry::Component(component.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(components) = scope_groups.get(&None) {
|
||||
if !components.is_empty() {
|
||||
entries.push(PreviewEntry::Separator);
|
||||
entries.push(PreviewEntry::SectionHeader("Uncategorized".into()));
|
||||
let mut sorted_components = components.clone();
|
||||
sorted_components.sort_by_key(|c| c.sort_name());
|
||||
|
||||
for component in sorted_components {
|
||||
for component in components {
|
||||
entries.push(PreviewEntry::Component(component.clone()));
|
||||
}
|
||||
}
|
||||
@@ -242,10 +250,7 @@ impl ComponentPreview {
|
||||
let id = component_metadata.id();
|
||||
let selected = self.active_page == PreviewPage::Component(id.clone());
|
||||
ListItem::new(ix)
|
||||
.child(
|
||||
Label::new(component_metadata.scopeless_name().clone())
|
||||
.color(Color::Default),
|
||||
)
|
||||
.child(Label::new(component_metadata.name().clone()).color(Color::Default))
|
||||
.selectable(true)
|
||||
.toggle_state(selected)
|
||||
.inset(true)
|
||||
@@ -328,7 +333,7 @@ impl ComponentPreview {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
let name = component.scopeless_name();
|
||||
let name = component.name();
|
||||
let scope = component.scope();
|
||||
|
||||
let description = component.description();
|
||||
@@ -349,12 +354,13 @@ impl ComponentPreview {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex().gap_1().text_xl().child(div().child(name)).when(
|
||||
!matches!(scope, ComponentScope::None),
|
||||
|this| {
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_xl()
|
||||
.child(div().child(name))
|
||||
.when_some(scope, |this, scope| {
|
||||
this.child(div().opacity(0.5).child(format!("({})", scope)))
|
||||
},
|
||||
),
|
||||
}),
|
||||
)
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
@@ -367,7 +373,7 @@ impl ComponentPreview {
|
||||
}),
|
||||
)
|
||||
.when_some(component.preview(), |this, preview| {
|
||||
this.children(preview(window, cx))
|
||||
this.child(preview(window, cx))
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
@@ -389,16 +395,17 @@ impl ComponentPreview {
|
||||
fn render_component_page(
|
||||
&mut self,
|
||||
component_id: &ComponentId,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let component = self.component_map.get(&component_id);
|
||||
|
||||
if let Some(component) = component {
|
||||
v_flex()
|
||||
.id("render-component-page")
|
||||
.size_full()
|
||||
.child(ComponentPreviewPage::new(component.clone()))
|
||||
.w_full()
|
||||
.flex_initial()
|
||||
.min_h_full()
|
||||
.child(self.render_preview(component, window, cx))
|
||||
.into_any_element()
|
||||
} else {
|
||||
v_flex()
|
||||
@@ -438,11 +445,10 @@ impl Render for ComponentPreview {
|
||||
.overflow_hidden()
|
||||
.size_full()
|
||||
.track_focus(&self.focus_handle)
|
||||
.px_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
v_flex()
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.h_full()
|
||||
.child(
|
||||
uniform_list(
|
||||
@@ -459,7 +465,6 @@ impl Render for ComponentPreview {
|
||||
)
|
||||
.track_scroll(self.nav_scroll_handle.clone())
|
||||
.pt_4()
|
||||
.px_4()
|
||||
.w(px(240.))
|
||||
.h_full()
|
||||
.flex_1(),
|
||||
@@ -522,7 +527,6 @@ impl Item for ComponentPreview {
|
||||
let user_store = self.user_store.clone();
|
||||
let weak_workspace = self.workspace.clone();
|
||||
let selected_index = self.cursor_index;
|
||||
let active_page = self.active_page.clone();
|
||||
|
||||
Some(cx.new(|cx| {
|
||||
Self::new(
|
||||
@@ -530,7 +534,6 @@ impl Item for ComponentPreview {
|
||||
language_registry,
|
||||
user_store,
|
||||
selected_index,
|
||||
Some(active_page),
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
@@ -563,14 +566,7 @@ impl SerializableItem for ComponentPreview {
|
||||
let weak_workspace = workspace.clone();
|
||||
cx.update(|_, cx| {
|
||||
Ok(cx.new(|cx| {
|
||||
ComponentPreview::new(
|
||||
weak_workspace,
|
||||
language_registry,
|
||||
user_store,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
ComponentPreview::new(weak_workspace, language_registry, user_store, None, cx)
|
||||
}))
|
||||
})?
|
||||
})
|
||||
@@ -604,76 +600,3 @@ impl SerializableItem for ComponentPreview {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ComponentPreviewPage {
|
||||
// languages: Arc<LanguageRegistry>,
|
||||
component: ComponentMetadata,
|
||||
}
|
||||
|
||||
impl ComponentPreviewPage {
|
||||
pub fn new(
|
||||
component: ComponentMetadata,
|
||||
// languages: Arc<LanguageRegistry>
|
||||
) -> Self {
|
||||
Self {
|
||||
// languages,
|
||||
component,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_header(&self, _: &Window, cx: &App) -> impl IntoElement {
|
||||
v_flex()
|
||||
.px_12()
|
||||
.pt_16()
|
||||
.pb_12()
|
||||
.gap_6()
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new(self.component.scope().to_string())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Headline::new(self.component.scopeless_name()).size(HeadlineSize::XLarge),
|
||||
),
|
||||
)
|
||||
.when_some(self.component.description(), |this, description| {
|
||||
this.child(div().text_sm().child(description))
|
||||
})
|
||||
}
|
||||
|
||||
fn render_preview(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.px_12()
|
||||
.py_6()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(if let Some(preview) = self.component.preview() {
|
||||
preview(window, cx).unwrap_or_else(|| {
|
||||
div()
|
||||
.child("Failed to load preview. This path should be unreachable")
|
||||
.into_any_element()
|
||||
})
|
||||
} else {
|
||||
div().child("No preview available").into_any_element()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ComponentPreviewPage {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
v_flex()
|
||||
.id("component-preview-page")
|
||||
.overflow_y_scroll()
|
||||
.overflow_x_hidden()
|
||||
.w_full()
|
||||
.child(self.render_header(window, cx))
|
||||
.child(self.render_preview(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use assistant_tool::{ActionLog, Tool, ToolSource};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use icons::IconName;
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -49,7 +50,7 @@ impl Tool for ContextServerTool {
|
||||
}
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
fn needs_confirmation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@ impl Tool for ContextServerTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
|
||||
let tool_name = self.tool.name.clone();
|
||||
let server_clone = server.clone();
|
||||
@@ -114,7 +115,7 @@ impl Tool for ContextServerTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
Ok(StringToolOutput::new(result))
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("Context server not found")))
|
||||
|
||||
@@ -356,7 +356,7 @@ impl ConsoleQueryBarCompletionProvider {
|
||||
let variable_value = variables.get(&string_match.string)?;
|
||||
|
||||
Some(project::Completion {
|
||||
replace_range: buffer_position..buffer_position,
|
||||
old_range: buffer_position..buffer_position,
|
||||
new_text: string_match.string.clone(),
|
||||
label: CodeLabel {
|
||||
filter_range: 0..string_match.string.len(),
|
||||
@@ -428,10 +428,10 @@ impl ConsoleQueryBarCompletionProvider {
|
||||
let buffer_offset = buffer_position.to_offset(&snapshot);
|
||||
let start = buffer_offset - word_bytes_length;
|
||||
let start = snapshot.anchor_before(start);
|
||||
let replace_range = start..buffer_position;
|
||||
let old_range = start..buffer_position;
|
||||
|
||||
project::Completion {
|
||||
replace_range,
|
||||
old_range,
|
||||
new_text,
|
||||
label: CodeLabel {
|
||||
filter_range: 0..completion.label.len(),
|
||||
|
||||
@@ -3,7 +3,6 @@ use super::*;
|
||||
use gpui::{action_as, action_with_deprecated_aliases, actions};
|
||||
use schemars::JsonSchema;
|
||||
use util::serde::default_true;
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SelectNext {
|
||||
@@ -263,8 +262,6 @@ actions!(
|
||||
Cancel,
|
||||
CancelLanguageServerWork,
|
||||
ConfirmRename,
|
||||
ConfirmCompletionInsert,
|
||||
ConfirmCompletionReplace,
|
||||
ContextMenuFirst,
|
||||
ContextMenuLast,
|
||||
ContextMenuNext,
|
||||
|
||||
@@ -230,7 +230,7 @@ impl CompletionsMenu {
|
||||
let completions = choices
|
||||
.iter()
|
||||
.map(|choice| Completion {
|
||||
replace_range: selection.start.text_anchor..selection.end.text_anchor,
|
||||
old_range: selection.start.text_anchor..selection.end.text_anchor,
|
||||
new_text: choice.to_string(),
|
||||
label: CodeLabel {
|
||||
text: choice.to_string(),
|
||||
|
||||
@@ -109,8 +109,8 @@ use language::{
|
||||
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
|
||||
TransactionId, TreeSitterOptions, WordsQuery,
|
||||
language_settings::{
|
||||
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
|
||||
all_language_settings, language_settings,
|
||||
self, InlayHintSettings, RewrapBehavior, WordsCompletionMode, all_language_settings,
|
||||
language_settings,
|
||||
},
|
||||
point_from_lsp, text_diff_with_options,
|
||||
};
|
||||
@@ -131,7 +131,7 @@ pub use proposed_changes_editor::{
|
||||
};
|
||||
use smallvec::smallvec;
|
||||
use std::{cell::OnceCell, iter::Peekable};
|
||||
use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
pub use lsp::CompletionContext;
|
||||
use lsp::{
|
||||
@@ -140,7 +140,6 @@ use lsp::{
|
||||
};
|
||||
|
||||
use language::BufferSnapshot;
|
||||
pub use lsp_ext::lsp_tasks;
|
||||
use movement::TextLayoutDetails;
|
||||
pub use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
|
||||
@@ -4462,7 +4461,7 @@ impl Editor {
|
||||
words.remove(&lsp_completion.new_text);
|
||||
}
|
||||
completions.extend(words.into_iter().map(|(word, word_range)| Completion {
|
||||
replace_range: old_range.clone(),
|
||||
old_range: old_range.clone(),
|
||||
new_text: word.clone(),
|
||||
label: CodeLabel::plain(word, None),
|
||||
icon_path: None,
|
||||
@@ -4569,26 +4568,6 @@ impl Editor {
|
||||
self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
|
||||
}
|
||||
|
||||
pub fn confirm_completion_insert(
|
||||
&mut self,
|
||||
_: &ConfirmCompletionInsert,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||
self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
|
||||
}
|
||||
|
||||
pub fn confirm_completion_replace(
|
||||
&mut self,
|
||||
_: &ConfirmCompletionReplace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||
self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
|
||||
}
|
||||
|
||||
pub fn compose_completion(
|
||||
&mut self,
|
||||
action: &ComposeCompletion,
|
||||
@@ -4608,10 +4587,12 @@ impl Editor {
|
||||
) -> Option<Task<Result<()>>> {
|
||||
use language::ToOffset as _;
|
||||
|
||||
let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let completions_menu =
|
||||
if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
|
||||
menu
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let candidate_id = {
|
||||
let entries = completions_menu.entries.borrow();
|
||||
@@ -4640,12 +4621,9 @@ impl Editor {
|
||||
new_text = completion.new_text.clone();
|
||||
};
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
|
||||
let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
|
||||
let buffer = buffer_handle.read(cx);
|
||||
let old_text = buffer
|
||||
.text_for_range(replace_range.clone())
|
||||
.collect::<String>();
|
||||
let old_range = completion.old_range.to_offset(buffer);
|
||||
let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
|
||||
|
||||
let newest_selection = self.selections.newest_anchor();
|
||||
if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
|
||||
@@ -4656,8 +4634,8 @@ impl Editor {
|
||||
.start
|
||||
.text_anchor
|
||||
.to_offset(buffer)
|
||||
.saturating_sub(replace_range.start);
|
||||
let lookahead = replace_range
|
||||
.saturating_sub(old_range.start);
|
||||
let lookahead = old_range
|
||||
.end
|
||||
.saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
|
||||
let mut common_prefix_len = 0;
|
||||
@@ -4686,8 +4664,8 @@ impl Editor {
|
||||
ranges.clear();
|
||||
ranges.extend(selections.iter().map(|s| {
|
||||
if s.id == newest_selection.id {
|
||||
range_to_replace = Some(replace_range.clone());
|
||||
replace_range.clone()
|
||||
range_to_replace = Some(old_range.clone());
|
||||
old_range.clone()
|
||||
} else {
|
||||
s.start..s.end
|
||||
}
|
||||
@@ -12471,13 +12449,12 @@ impl Editor {
|
||||
return Task::ready(());
|
||||
}
|
||||
let project = self.project.as_ref().map(Entity::downgrade);
|
||||
let task_sources = self.lsp_task_sources(cx);
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
|
||||
let Some(project) = project.and_then(|p| p.upgrade()) else {
|
||||
return;
|
||||
};
|
||||
let Ok(display_snapshot) = editor.update(cx, |this, cx| {
|
||||
let Ok(display_snapshot) = this.update(cx, |this, cx| {
|
||||
this.display_map.update(cx, |map, cx| map.snapshot(cx))
|
||||
}) else {
|
||||
return;
|
||||
@@ -12500,77 +12477,15 @@ impl Editor {
|
||||
}
|
||||
})
|
||||
.await;
|
||||
let Ok(lsp_tasks) =
|
||||
cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let lsp_tasks = lsp_tasks.await;
|
||||
|
||||
let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
|
||||
lsp_tasks
|
||||
.into_iter()
|
||||
.flat_map(|(kind, tasks)| {
|
||||
tasks.into_iter().filter_map(move |(location, task)| {
|
||||
Some((kind.clone(), location?, task))
|
||||
})
|
||||
})
|
||||
.fold(HashMap::default(), |mut acc, (kind, location, task)| {
|
||||
let buffer = location.target.buffer;
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
|
||||
|(excerpt_id, snapshot, _)| {
|
||||
if snapshot.remote_id() == buffer_snapshot.remote_id() {
|
||||
display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_in_excerpt(excerpt_id, location.target.range.start)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
);
|
||||
if let Some(offset) = offset {
|
||||
let task_buffer_range =
|
||||
location.target.range.to_point(&buffer_snapshot);
|
||||
let context_buffer_range =
|
||||
task_buffer_range.to_offset(&buffer_snapshot);
|
||||
let context_range = BufferOffset(context_buffer_range.start)
|
||||
..BufferOffset(context_buffer_range.end);
|
||||
|
||||
acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
|
||||
.or_insert_with(|| RunnableTasks {
|
||||
templates: Vec::new(),
|
||||
offset,
|
||||
column: task_buffer_range.start.column,
|
||||
extra_variables: HashMap::default(),
|
||||
context_range,
|
||||
})
|
||||
.templates
|
||||
.push((kind, task.original_task().clone()));
|
||||
}
|
||||
|
||||
acc
|
||||
})
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
|
||||
editor
|
||||
.update(cx, |editor, _| {
|
||||
editor.clear_tasks();
|
||||
for (key, mut value) in rows {
|
||||
if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
|
||||
value.templates.extend(lsp_tasks.templates);
|
||||
}
|
||||
|
||||
editor.insert_tasks(key, value);
|
||||
}
|
||||
for (key, value) in lsp_tasks_by_rows {
|
||||
editor.insert_tasks(key, value);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
this.update(cx, |this, _| {
|
||||
this.clear_tasks();
|
||||
for (key, value) in rows {
|
||||
this.insert_tasks(key, value);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
fn fetch_runnable_ranges(
|
||||
@@ -12585,7 +12500,7 @@ impl Editor {
|
||||
snapshot: DisplaySnapshot,
|
||||
runnable_ranges: Vec<RunnableRange>,
|
||||
mut cx: AsyncWindowContext,
|
||||
) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
|
||||
) -> Vec<((BufferId, u32), RunnableTasks)> {
|
||||
runnable_ranges
|
||||
.into_iter()
|
||||
.filter_map(|mut runnable| {
|
||||
@@ -12642,9 +12557,11 @@ impl Editor {
|
||||
)
|
||||
});
|
||||
|
||||
let mut templates_with_tags = mem::take(&mut runnable.tags)
|
||||
let tags = mem::take(&mut runnable.tags);
|
||||
let mut tags: Vec<_> = tags
|
||||
.into_iter()
|
||||
.flat_map(|RunnableTag(tag)| {
|
||||
.flat_map(|tag| {
|
||||
let tag = tag.0.clone();
|
||||
inventory
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
@@ -12661,20 +12578,20 @@ impl Editor {
|
||||
})
|
||||
})
|
||||
.sorted_by_key(|(kind, _)| kind.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
if let Some((leading_tag_source, _)) = templates_with_tags.first() {
|
||||
.collect();
|
||||
if let Some((leading_tag_source, _)) = tags.first() {
|
||||
// Strongest source wins; if we have worktree tag binding, prefer that to
|
||||
// global and language bindings;
|
||||
// if we have a global binding, prefer that to language binding.
|
||||
let first_mismatch = templates_with_tags
|
||||
let first_mismatch = tags
|
||||
.iter()
|
||||
.position(|(tag_source, _)| tag_source != leading_tag_source);
|
||||
if let Some(index) = first_mismatch {
|
||||
templates_with_tags.truncate(index);
|
||||
tags.truncate(index);
|
||||
}
|
||||
}
|
||||
|
||||
templates_with_tags
|
||||
tags
|
||||
}
|
||||
|
||||
pub fn move_to_enclosing_bracket(
|
||||
@@ -18001,81 +17918,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
// Consider user intent and default settings
|
||||
fn choose_completion_range(
|
||||
completion: &Completion,
|
||||
intent: CompletionIntent,
|
||||
buffer: &Entity<Buffer>,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Range<usize> {
|
||||
fn should_replace(
|
||||
completion: &Completion,
|
||||
insert_range: &Range<text::Anchor>,
|
||||
intent: CompletionIntent,
|
||||
completion_mode_setting: LspInsertMode,
|
||||
buffer: &Buffer,
|
||||
) -> bool {
|
||||
// specific actions take precedence over settings
|
||||
match intent {
|
||||
CompletionIntent::CompleteWithInsert => return false,
|
||||
CompletionIntent::CompleteWithReplace => return true,
|
||||
CompletionIntent::Complete | CompletionIntent::Compose => {}
|
||||
}
|
||||
|
||||
match completion_mode_setting {
|
||||
LspInsertMode::Insert => false,
|
||||
LspInsertMode::Replace => true,
|
||||
LspInsertMode::ReplaceSubsequence => {
|
||||
let mut text_to_replace = buffer.chars_for_range(
|
||||
buffer.anchor_before(completion.replace_range.start)
|
||||
..buffer.anchor_after(completion.replace_range.end),
|
||||
);
|
||||
let mut completion_text = completion.new_text.chars();
|
||||
|
||||
// is `text_to_replace` a subsequence of `completion_text`
|
||||
text_to_replace
|
||||
.all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
|
||||
}
|
||||
LspInsertMode::ReplaceSuffix => {
|
||||
let range_after_cursor = insert_range.end..completion.replace_range.end;
|
||||
|
||||
let text_after_cursor = buffer
|
||||
.text_for_range(
|
||||
buffer.anchor_before(range_after_cursor.start)
|
||||
..buffer.anchor_after(range_after_cursor.end),
|
||||
)
|
||||
.collect::<String>();
|
||||
completion.new_text.ends_with(&text_after_cursor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
|
||||
if let CompletionSource::Lsp {
|
||||
insert_range: Some(insert_range),
|
||||
..
|
||||
} = &completion.source
|
||||
{
|
||||
let completion_mode_setting =
|
||||
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
|
||||
.completions
|
||||
.lsp_insert_mode;
|
||||
|
||||
if !should_replace(
|
||||
completion,
|
||||
&insert_range,
|
||||
intent,
|
||||
completion_mode_setting,
|
||||
buffer,
|
||||
) {
|
||||
return insert_range.to_offset(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
completion.replace_range.to_offset(buffer)
|
||||
}
|
||||
|
||||
fn insert_extra_newline_brackets(
|
||||
buffer: &MultiBufferSnapshot,
|
||||
range: Range<usize>,
|
||||
@@ -18797,10 +18639,9 @@ fn snippet_completions(
|
||||
end: lsp_end,
|
||||
};
|
||||
Some(Completion {
|
||||
replace_range: range,
|
||||
old_range: range,
|
||||
new_text: snippet.body.clone(),
|
||||
source: CompletionSource::Lsp {
|
||||
insert_range: None,
|
||||
server_id: LanguageServerId(usize::MAX),
|
||||
resolved: true,
|
||||
lsp_completion: Box::new(lsp::CompletionItem {
|
||||
|
||||
@@ -9218,7 +9218,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: String,
|
||||
buffer_marked_text: String,
|
||||
completion_text: &'static str,
|
||||
expected_with_insert_mode: String,
|
||||
expected_with_insertion_mode: String,
|
||||
expected_with_replace_mode: String,
|
||||
expected_with_replace_subsequence_mode: String,
|
||||
expected_with_replace_suffix_mode: String,
|
||||
@@ -9230,7 +9230,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "before ediˇ after".into(),
|
||||
buffer_marked_text: "before <edi|> after".into(),
|
||||
completion_text: "editor",
|
||||
expected_with_insert_mode: "before editorˇ after".into(),
|
||||
expected_with_insertion_mode: "before editorˇ after".into(),
|
||||
expected_with_replace_mode: "before editorˇ after".into(),
|
||||
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
||||
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
||||
@@ -9240,7 +9240,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "before ediˇtor after".into(),
|
||||
buffer_marked_text: "before <edi|tor> after".into(),
|
||||
completion_text: "editor",
|
||||
expected_with_insert_mode: "before editorˇtor after".into(),
|
||||
expected_with_insertion_mode: "before editorˇtor after".into(),
|
||||
expected_with_replace_mode: "before ediˇtor after".into(),
|
||||
expected_with_replace_subsequence_mode: "before ediˇtor after".into(),
|
||||
expected_with_replace_suffix_mode: "before ediˇtor after".into(),
|
||||
@@ -9250,7 +9250,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "before torˇ after".into(),
|
||||
buffer_marked_text: "before <tor|> after".into(),
|
||||
completion_text: "editor",
|
||||
expected_with_insert_mode: "before editorˇ after".into(),
|
||||
expected_with_insertion_mode: "before editorˇ after".into(),
|
||||
expected_with_replace_mode: "before editorˇ after".into(),
|
||||
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
||||
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
||||
@@ -9260,7 +9260,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "before ˇtor after".into(),
|
||||
buffer_marked_text: "before <|tor> after".into(),
|
||||
completion_text: "editor",
|
||||
expected_with_insert_mode: "before editorˇtor after".into(),
|
||||
expected_with_insertion_mode: "before editorˇtor after".into(),
|
||||
expected_with_replace_mode: "before editorˇ after".into(),
|
||||
expected_with_replace_subsequence_mode: "before editorˇ after".into(),
|
||||
expected_with_replace_suffix_mode: "before editorˇ after".into(),
|
||||
@@ -9270,7 +9270,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "pˇfield: bool".into(),
|
||||
buffer_marked_text: "<p|field>: bool".into(),
|
||||
completion_text: "pub ",
|
||||
expected_with_insert_mode: "pub ˇfield: bool".into(),
|
||||
expected_with_insertion_mode: "pub ˇfield: bool".into(),
|
||||
expected_with_replace_mode: "pub ˇ: bool".into(),
|
||||
expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
|
||||
expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
|
||||
@@ -9280,7 +9280,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "[element_ˇelement_2]".into(),
|
||||
buffer_marked_text: "[<element_|element_2>]".into(),
|
||||
completion_text: "element_1",
|
||||
expected_with_insert_mode: "[element_1ˇelement_2]".into(),
|
||||
expected_with_insertion_mode: "[element_1ˇelement_2]".into(),
|
||||
expected_with_replace_mode: "[element_1ˇ]".into(),
|
||||
expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
|
||||
expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
|
||||
@@ -9290,7 +9290,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "[elˇelement]".into(),
|
||||
buffer_marked_text: "[<el|element>]".into(),
|
||||
completion_text: "element",
|
||||
expected_with_insert_mode: "[elementˇelement]".into(),
|
||||
expected_with_insertion_mode: "[elementˇelement]".into(),
|
||||
expected_with_replace_mode: "[elˇement]".into(),
|
||||
expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
|
||||
expected_with_replace_suffix_mode: "[elˇement]".into(),
|
||||
@@ -9300,7 +9300,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "SubˇError".into(),
|
||||
buffer_marked_text: "<Sub|Error>".into(),
|
||||
completion_text: "SubscriptionError",
|
||||
expected_with_insert_mode: "SubscriptionErrorˇError".into(),
|
||||
expected_with_insertion_mode: "SubscriptionErrorˇError".into(),
|
||||
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
||||
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
||||
expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
|
||||
@@ -9310,7 +9310,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "SubˇErr".into(),
|
||||
buffer_marked_text: "<Sub|Err>".into(),
|
||||
completion_text: "SubscriptionError",
|
||||
expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
|
||||
expected_with_insertion_mode: "SubscriptionErrorˇErr".into(),
|
||||
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
||||
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
||||
expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
|
||||
@@ -9320,7 +9320,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "Suˇscrirr".into(),
|
||||
buffer_marked_text: "<Su|scrirr>".into(),
|
||||
completion_text: "SubscriptionError",
|
||||
expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
|
||||
expected_with_insertion_mode: "SubscriptionErrorˇscrirr".into(),
|
||||
expected_with_replace_mode: "SubscriptionErrorˇ".into(),
|
||||
expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
|
||||
expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
|
||||
@@ -9330,7 +9330,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
initial_state: "foo(indˇix)".into(),
|
||||
buffer_marked_text: "foo(<ind|ix>)".into(),
|
||||
completion_text: "node_index",
|
||||
expected_with_insert_mode: "foo(node_indexˇix)".into(),
|
||||
expected_with_insertion_mode: "foo(node_indexˇix)".into(),
|
||||
expected_with_replace_mode: "foo(node_indexˇ)".into(),
|
||||
expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
|
||||
expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
|
||||
@@ -9339,7 +9339,7 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
|
||||
for run in runs {
|
||||
let run_variations = [
|
||||
(LspInsertMode::Insert, run.expected_with_insert_mode),
|
||||
(LspInsertMode::Insert, run.expected_with_insertion_mode),
|
||||
(LspInsertMode::Replace, run.expected_with_replace_mode),
|
||||
(
|
||||
LspInsertMode::ReplaceSubsequence,
|
||||
@@ -9395,98 +9395,6 @@ async fn test_completion_mode(cx: &mut TestAppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
resolve_provider: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
let initial_state = "SubˇError";
|
||||
let buffer_marked_text = "<Sub|Error>";
|
||||
let completion_text = "SubscriptionError";
|
||||
let expected_with_insert_mode = "SubscriptionErrorˇError";
|
||||
let expected_with_replace_mode = "SubscriptionErrorˇ";
|
||||
|
||||
update_test_language_settings(&mut cx, |settings| {
|
||||
settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
// set the opposite here to ensure that the action is overriding the default behavior
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
});
|
||||
});
|
||||
|
||||
cx.set_state(initial_state);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
handle_completion_request_with_insert_and_replace(
|
||||
&mut cx,
|
||||
&buffer_marked_text,
|
||||
vec![completion_text],
|
||||
counter.clone(),
|
||||
)
|
||||
.await;
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
|
||||
|
||||
let apply_additional_edits = cx.update_editor(|editor, window, cx| {
|
||||
editor
|
||||
.confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
|
||||
.unwrap()
|
||||
});
|
||||
cx.assert_editor_state(&expected_with_replace_mode);
|
||||
handle_resolve_completion_request(&mut cx, None).await;
|
||||
apply_additional_edits.await.unwrap();
|
||||
|
||||
update_test_language_settings(&mut cx, |settings| {
|
||||
settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
// set the opposite here to ensure that the action is overriding the default behavior
|
||||
lsp_insert_mode: LspInsertMode::Replace,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
});
|
||||
});
|
||||
|
||||
cx.set_state(initial_state);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
|
||||
});
|
||||
handle_completion_request_with_insert_and_replace(
|
||||
&mut cx,
|
||||
&buffer_marked_text,
|
||||
vec![completion_text],
|
||||
counter.clone(),
|
||||
)
|
||||
.await;
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
|
||||
|
||||
let apply_additional_edits = cx.update_editor(|editor, window, cx| {
|
||||
editor
|
||||
.confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
|
||||
.unwrap()
|
||||
});
|
||||
cx.assert_editor_state(&expected_with_insert_mode);
|
||||
handle_resolve_completion_request(&mut cx, None).await;
|
||||
apply_additional_edits.await.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_completion(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -12631,7 +12539,6 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
|
||||
initialization_options: Some(json!({
|
||||
"some other init value": false
|
||||
})),
|
||||
enable_lsp_tasks: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -12651,7 +12558,6 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
|
||||
initialization_options: Some(json!({
|
||||
"anotherInitValue": false
|
||||
})),
|
||||
enable_lsp_tasks: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -12671,7 +12577,6 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
|
||||
initialization_options: Some(json!({
|
||||
"anotherInitValue": false
|
||||
})),
|
||||
enable_lsp_tasks: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -12689,7 +12594,6 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
|
||||
binary: None,
|
||||
settings: None,
|
||||
initialization_options: None,
|
||||
enable_lsp_tasks: false,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -461,20 +461,6 @@ impl EditorElement {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_completion_replace(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.confirm_completion_insert(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
});
|
||||
register_action(editor, window, |editor, action, window, cx| {
|
||||
if let Some(task) = editor.compose_completion(action, window, cx) {
|
||||
task.detach_and_notify_err(window, cx);
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Editor;
|
||||
use collections::HashMap;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use gpui::{App, AppContext as _, Entity, Task};
|
||||
use itertools::Itertools;
|
||||
use language::Buffer;
|
||||
use language::Language;
|
||||
use lsp::LanguageServerId;
|
||||
use lsp::LanguageServerName;
|
||||
use multi_buffer::Anchor;
|
||||
use project::LanguageServerToQuery;
|
||||
use project::LocationLink;
|
||||
use project::Project;
|
||||
use project::TaskSourceKind;
|
||||
use project::lsp_store::lsp_ext_command::GetLspRunnables;
|
||||
use smol::stream::StreamExt;
|
||||
use task::ResolvedTask;
|
||||
use task::TaskContext;
|
||||
use text::BufferId;
|
||||
use util::ResultExt as _;
|
||||
|
||||
pub(crate) fn find_specific_language_server_in_selection<F>(
|
||||
editor: &Editor,
|
||||
@@ -73,83 +60,3 @@ where
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lsp_tasks(
|
||||
project: Entity<Project>,
|
||||
task_sources: &HashMap<LanguageServerName, Vec<BufferId>>,
|
||||
for_position: Option<text::Anchor>,
|
||||
cx: &mut App,
|
||||
) -> Task<Vec<(TaskSourceKind, Vec<(Option<LocationLink>, ResolvedTask)>)>> {
|
||||
let mut lsp_task_sources = task_sources
|
||||
.iter()
|
||||
.map(|(name, buffer_ids)| {
|
||||
let buffers = buffer_ids
|
||||
.iter()
|
||||
.filter_map(|&buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
|
||||
.collect::<Vec<_>>();
|
||||
language_server_for_buffers(project.clone(), name.clone(), buffers, cx)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut lsp_tasks = Vec::new();
|
||||
let lsp_task_context = TaskContext::default();
|
||||
while let Some(server_to_query) = lsp_task_sources.next().await {
|
||||
if let Some((server_id, buffers)) = server_to_query {
|
||||
let source_kind = TaskSourceKind::Lsp(server_id);
|
||||
let id_base = source_kind.to_id_base();
|
||||
let mut new_lsp_tasks = Vec::new();
|
||||
for buffer in buffers {
|
||||
if let Ok(runnables_task) = project.update(cx, |project, cx| {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
project.request_lsp(
|
||||
buffer,
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
GetLspRunnables {
|
||||
buffer_id,
|
||||
position: for_position,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}) {
|
||||
if let Some(new_runnables) = runnables_task.await.log_err() {
|
||||
new_lsp_tasks.extend(new_runnables.runnables.into_iter().filter_map(
|
||||
|(location, runnable)| {
|
||||
let resolved_task =
|
||||
runnable.resolve_task(&id_base, &lsp_task_context)?;
|
||||
Some((location, resolved_task))
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
lsp_tasks.push((source_kind, new_lsp_tasks));
|
||||
}
|
||||
}
|
||||
lsp_tasks
|
||||
})
|
||||
}
|
||||
|
||||
fn language_server_for_buffers(
|
||||
project: Entity<Project>,
|
||||
name: LanguageServerName,
|
||||
candidates: Vec<Entity<Buffer>>,
|
||||
cx: &mut App,
|
||||
) -> Task<Option<(LanguageServerId, Vec<Entity<Buffer>>)>> {
|
||||
cx.spawn(async move |cx| {
|
||||
for buffer in &candidates {
|
||||
let server_id = buffer
|
||||
.update(cx, |buffer, cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
project.language_server_id_for_name(buffer, &name.0, cx)
|
||||
})
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
if let Some(server_id) = server_id {
|
||||
return Some((server_id, candidates));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use crate::Editor;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{App, Task, Window};
|
||||
use lsp::LanguageServerName;
|
||||
use project::{Location, project_settings::ProjectSettings};
|
||||
use settings::Settings as _;
|
||||
use project::Location;
|
||||
use task::{TaskContext, TaskVariables, VariableName};
|
||||
use text::{BufferId, ToOffset, ToPoint};
|
||||
use text::{ToOffset, ToPoint};
|
||||
|
||||
impl Editor {
|
||||
pub fn task_context(&self, window: &mut Window, cx: &mut App) -> Task<Option<TaskContext>> {
|
||||
@@ -73,38 +70,4 @@ impl Editor {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lsp_task_sources(&self, cx: &App) -> HashMap<LanguageServerName, Vec<BufferId>> {
|
||||
let lsp_settings = &ProjectSettings::get_global(cx).lsp;
|
||||
|
||||
self.buffer()
|
||||
.read(cx)
|
||||
.all_buffers()
|
||||
.into_iter()
|
||||
.filter_map(|buffer| {
|
||||
let lsp_tasks_source = buffer
|
||||
.read(cx)
|
||||
.language()?
|
||||
.context_provider()?
|
||||
.lsp_task_source()?;
|
||||
if lsp_settings
|
||||
.get(&lsp_tasks_source)
|
||||
.map_or(true, |s| s.enable_lsp_tasks)
|
||||
{
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
Some((lsp_tasks_source, buffer_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.fold(
|
||||
HashMap::default(),
|
||||
|mut acc, (lsp_task_source, buffer_id)| {
|
||||
acc.entry(lsp_task_source)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(buffer_id);
|
||||
acc
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3953,7 +3953,8 @@ impl Render for GitPanelMessageTooltip {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Version Control")]
|
||||
pub struct PanelRepoFooter {
|
||||
active_repository: SharedString,
|
||||
branch: Option<Branch>,
|
||||
@@ -4133,12 +4134,8 @@ impl RenderOnce for PanelRepoFooter {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for PanelRepoFooter {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::VersionControl
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
impl ComponentPreview for PanelRepoFooter {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
let unknown_upstream = None;
|
||||
let no_remote_upstream = Some(UpstreamTracking::Gone);
|
||||
let ahead_of_upstream = Some(
|
||||
@@ -4210,180 +4207,192 @@ impl Component for PanelRepoFooter {
|
||||
}
|
||||
|
||||
let example_width = px(340.);
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.w_full()
|
||||
.flex_none()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Action Button States",
|
||||
vec![
|
||||
single_example(
|
||||
"No Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(1).clone(),
|
||||
None,
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Remote status unknown",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(2).clone(),
|
||||
Some(branch(unknown_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"No Remote Upstream",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(3).clone(),
|
||||
Some(branch(no_remote_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Not Ahead or Behind",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(4).clone(),
|
||||
Some(branch(not_ahead_or_behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Behind remote",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(5).clone(),
|
||||
Some(branch(behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Ahead of remote",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(6).clone(),
|
||||
Some(branch(ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Ahead and behind remote",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(7).clone(),
|
||||
Some(branch(ahead_and_behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
)
|
||||
.grow()
|
||||
.vertical(),
|
||||
])
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Labels",
|
||||
vec![
|
||||
single_example(
|
||||
"Short Branch & Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed"),
|
||||
Some(custom("main", behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Long Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed"),
|
||||
Some(custom(
|
||||
"redesign-and-update-git-ui-list-entry-style",
|
||||
behind_upstream,
|
||||
)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Long Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed-industries-community-examples"),
|
||||
Some(custom("gpui", ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Long Repo & Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed-industries-community-examples"),
|
||||
Some(custom(
|
||||
"redesign-and-update-git-ui-list-entry-style",
|
||||
behind_upstream,
|
||||
)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Uppercase Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("LICENSES"),
|
||||
Some(custom("main", ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Uppercase Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed"),
|
||||
Some(custom("update-README", behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
)
|
||||
.grow()
|
||||
.vertical(),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.w_full()
|
||||
.flex_none()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Action Button States",
|
||||
vec![
|
||||
single_example(
|
||||
"No Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(1).clone(),
|
||||
None,
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Remote status unknown",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(2).clone(),
|
||||
Some(branch(unknown_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"No Remote Upstream",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(3).clone(),
|
||||
Some(branch(no_remote_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Not Ahead or Behind",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(4).clone(),
|
||||
Some(branch(not_ahead_or_behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Behind remote",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(5).clone(),
|
||||
Some(branch(behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Ahead of remote",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(6).clone(),
|
||||
Some(branch(ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Ahead and behind remote",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
active_repository(7).clone(),
|
||||
Some(branch(ahead_and_behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
],
|
||||
)
|
||||
.grow()
|
||||
.vertical(),
|
||||
])
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Labels",
|
||||
vec![
|
||||
single_example(
|
||||
"Short Branch & Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed"),
|
||||
Some(custom("main", behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Long Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed"),
|
||||
Some(custom(
|
||||
"redesign-and-update-git-ui-list-entry-style",
|
||||
behind_upstream,
|
||||
)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Long Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed-industries-community-examples"),
|
||||
Some(custom("gpui", ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Long Repo & Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed-industries-community-examples"),
|
||||
Some(custom(
|
||||
"redesign-and-update-git-ui-list-entry-style",
|
||||
behind_upstream,
|
||||
)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Uppercase Repo",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("LICENSES"),
|
||||
Some(custom("main", ahead_of_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Uppercase Branch",
|
||||
div()
|
||||
.w(example_width)
|
||||
.overflow_hidden()
|
||||
.child(PanelRepoFooter::new_preview(
|
||||
SharedString::from("zed"),
|
||||
Some(custom("update-README", behind_upstream)),
|
||||
))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
],
|
||||
)
|
||||
.grow()
|
||||
.vertical(),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -441,8 +441,8 @@ mod remote_button {
|
||||
}
|
||||
}
|
||||
|
||||
/// A visual representation of a file's Git status.
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Version Control")]
|
||||
pub struct GitStatusIcon {
|
||||
status: FileStatus,
|
||||
}
|
||||
@@ -484,12 +484,8 @@ impl RenderOnce for GitStatusIcon {
|
||||
}
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl Component for GitStatusIcon {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::VersionControl
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
impl ComponentPreview for GitStatusIcon {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
fn tracked_file_status(code: StatusCode) -> FileStatus {
|
||||
FileStatus::Tracked(git::status::TrackedStatus {
|
||||
index_status: code,
|
||||
@@ -506,19 +502,17 @@ impl Component for GitStatusIcon {
|
||||
}
|
||||
.into();
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group(vec![
|
||||
single_example("Modified", GitStatusIcon::new(modified).into_any_element()),
|
||||
single_example("Added", GitStatusIcon::new(added).into_any_element()),
|
||||
single_example("Deleted", GitStatusIcon::new(deleted).into_any_element()),
|
||||
single_example(
|
||||
"Conflicted",
|
||||
GitStatusIcon::new(conflict).into_any_element(),
|
||||
),
|
||||
])])
|
||||
.into_any_element(),
|
||||
)
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group(vec![
|
||||
single_example("Modified", GitStatusIcon::new(modified).into_any_element()),
|
||||
single_example("Added", GitStatusIcon::new(added).into_any_element()),
|
||||
single_example("Deleted", GitStatusIcon::new(deleted).into_any_element()),
|
||||
single_example(
|
||||
"Conflicted",
|
||||
GitStatusIcon::new(conflict).into_any_element(),
|
||||
),
|
||||
])])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,7 +1005,8 @@ impl Render for ProjectDiffToolbar {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Version Control")]
|
||||
pub struct ProjectDiffEmptyState {
|
||||
pub no_repo: bool,
|
||||
pub can_push_and_pull: bool,
|
||||
@@ -1177,12 +1178,8 @@ mod preview {
|
||||
use super::ProjectDiffEmptyState;
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl Component for ProjectDiffEmptyState {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::VersionControl
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
impl ComponentPreview for ProjectDiffEmptyState {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
let unknown_upstream: Option<UpstreamTracking> = None;
|
||||
let ahead_of_upstream: Option<UpstreamTracking> = Some(
|
||||
UpstreamTrackingStatus {
|
||||
@@ -1247,48 +1244,46 @@ mod preview {
|
||||
|
||||
let (width, height) = (px(480.), px(320.));
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group(vec![
|
||||
single_example(
|
||||
"No Repo",
|
||||
div()
|
||||
.w(width)
|
||||
.h(height)
|
||||
.child(no_repo_state)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"No Changes",
|
||||
div()
|
||||
.w(width)
|
||||
.h(height)
|
||||
.child(no_changes_state)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Unknown Upstream",
|
||||
div()
|
||||
.w(width)
|
||||
.h(height)
|
||||
.child(unknown_upstream_state)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Ahead of Remote",
|
||||
div()
|
||||
.w(width)
|
||||
.h(height)
|
||||
.child(ahead_of_upstream_state)
|
||||
.into_any_element(),
|
||||
),
|
||||
])
|
||||
.vertical(),
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group(vec![
|
||||
single_example(
|
||||
"No Repo",
|
||||
div()
|
||||
.w(width)
|
||||
.h(height)
|
||||
.child(no_repo_state)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"No Changes",
|
||||
div()
|
||||
.w(width)
|
||||
.h(height)
|
||||
.child(no_changes_state)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Unknown Upstream",
|
||||
div()
|
||||
.w(width)
|
||||
.h(height)
|
||||
.child(unknown_upstream_state)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Ahead of Remote",
|
||||
div()
|
||||
.w(width)
|
||||
.h(height)
|
||||
.child(ahead_of_upstream_state)
|
||||
.into_any_element(),
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
.vertical(),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,11 +572,7 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
}
|
||||
|
||||
/// Support custom initialize params.
|
||||
fn prepare_initialize_params(
|
||||
&self,
|
||||
original: InitializeParams,
|
||||
_: &App,
|
||||
) -> Result<InitializeParams> {
|
||||
fn prepare_initialize_params(&self, original: InitializeParams) -> Result<InitializeParams> {
|
||||
Ok(original)
|
||||
}
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ fn default_words_completion_mode() -> WordsCompletionMode {
|
||||
}
|
||||
|
||||
fn default_lsp_insert_mode() -> LspInsertMode {
|
||||
LspInsertMode::ReplaceSuffix
|
||||
LspInsertMode::Insert
|
||||
}
|
||||
|
||||
fn default_lsp_fetch_timeout_ms() -> u64 {
|
||||
@@ -1029,10 +1029,7 @@ fn scroll_debounce_ms() -> u64 {
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct LanguageTaskConfig {
|
||||
/// Extra task variables to set for a particular language.
|
||||
#[serde(default)]
|
||||
pub variables: HashMap<String, String>,
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl InlayHintSettings {
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::{LanguageToolchainStore, Location, Runnable};
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use gpui::{App, Task};
|
||||
use lsp::LanguageServerName;
|
||||
use task::{TaskTemplates, TaskVariables};
|
||||
use text::BufferId;
|
||||
|
||||
@@ -16,7 +15,6 @@ pub struct RunnableRange {
|
||||
pub runnable: Runnable,
|
||||
pub extra_captures: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Language Contexts are used by Zed tasks to extract information about the source file where the tasks are supposed to be scheduled from.
|
||||
/// Multiple context providers may be used together: by default, Zed provides a base [`BasicContextProvider`] context that fills all non-custom [`VariableName`] variants.
|
||||
///
|
||||
@@ -42,9 +40,4 @@ pub trait ContextProvider: Send + Sync {
|
||||
) -> Option<TaskTemplates> {
|
||||
None
|
||||
}
|
||||
|
||||
/// A language server name, that can return tasks using LSP (ext) for this language.
|
||||
fn lsp_task_source(&self) -> Option<LanguageServerName> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ mod registry;
|
||||
mod request;
|
||||
mod role;
|
||||
mod telemetry;
|
||||
mod tool_output;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod fake_provider;
|
||||
@@ -26,6 +27,7 @@ use util::serde::is_default;
|
||||
|
||||
pub use crate::model::*;
|
||||
pub use crate::rate_limiter::*;
|
||||
pub use crate::tool_output::{ToolOutput, StringToolOutput};
|
||||
pub use crate::registry::*;
|
||||
pub use crate::request::*;
|
||||
pub use crate::role::*;
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::io::{Cursor, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::role::Role;
|
||||
use crate::tool_output::ToolOutput;
|
||||
use crate::{LanguageModelToolUse, LanguageModelToolUseId};
|
||||
use base64::write::EncoderWriter;
|
||||
use gpui::{
|
||||
@@ -164,12 +165,13 @@ impl LanguageModelImage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct LanguageModelToolResult {
|
||||
pub tool_use_id: LanguageModelToolUseId,
|
||||
pub tool_name: Arc<str>,
|
||||
pub is_error: bool,
|
||||
pub content: Arc<str>,
|
||||
pub tool_output: Option<Arc<ToolOutput>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
|
||||
33
crates/language_model/src/tool_output.rs
Normal file
33
crates/language_model/src/tool_output.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use gpui::{AnyElement, App, SharedString, Window};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An enum that represents different types of tool outputs that can be provided to the language model
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ToolOutput {
|
||||
/// A simple string output
|
||||
String {
|
||||
string: SharedString,
|
||||
rendered: Entity<Markdown>,
|
||||
},
|
||||
// Add other tool output types here as variants
|
||||
}
|
||||
|
||||
impl ToolOutput {
|
||||
/// Returns a string that will be given to the model
|
||||
/// as the tool output.
|
||||
pub fn response_for_model(&self) -> SharedString {
|
||||
match self {
|
||||
ToolOutput::String(output) => output.0.clone(),
|
||||
// Handle other variants here
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a custom UI element to render the tool's output.
|
||||
/// Returns None by default to indicate that rendering has not yet been
|
||||
/// implemented for this tool, and the caller should do some default rendering.
|
||||
pub fn render(&self, _window: &mut Window, _cx: &App) -> Option<AnyElement> {
|
||||
match self {
|
||||
ToolOutput::String { string, rendered } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, AsyncApp};
|
||||
use gpui::AsyncApp;
|
||||
use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
|
||||
pub use language::*;
|
||||
use lsp::{DiagnosticTag, InitializeParams, LanguageServerBinary, LanguageServerName};
|
||||
@@ -273,7 +273,6 @@ impl super::LspAdapter for CLspAdapter {
|
||||
fn prepare_initialize_params(
|
||||
&self,
|
||||
mut original: InitializeParams,
|
||||
_: &App,
|
||||
) -> Result<InitializeParams> {
|
||||
let experimental = json!({
|
||||
"textDocument": {
|
||||
|
||||
@@ -7,11 +7,8 @@ use gpui::{App, AsyncApp, SharedString, Task};
|
||||
use http_client::github::AssetKind;
|
||||
use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
|
||||
pub use language::*;
|
||||
use lsp::{InitializeParams, LanguageServerBinary};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use lsp::LanguageServerBinary;
|
||||
use regex::Regex;
|
||||
use serde_json::json;
|
||||
use settings::Settings as _;
|
||||
use smol::fs::{self};
|
||||
use std::fmt::Display;
|
||||
use std::{
|
||||
@@ -21,7 +18,6 @@ use std::{
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use task::{TaskTemplate, TaskTemplates, TaskType, TaskVariables, VariableName};
|
||||
use util::merge_json_value_into;
|
||||
use util::{ResultExt, fs::remove_matching, maybe};
|
||||
|
||||
use crate::language_settings::language_settings;
|
||||
@@ -52,9 +48,9 @@ impl RustLspAdapter {
|
||||
const ARCH_SERVER_NAME: &str = "pc-windows-msvc";
|
||||
}
|
||||
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer");
|
||||
|
||||
impl RustLspAdapter {
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("rust-analyzer");
|
||||
|
||||
fn build_asset_name() -> String {
|
||||
let extension = match Self::GITHUB_ASSET_KIND {
|
||||
AssetKind::TarGz => "tar.gz",
|
||||
@@ -64,7 +60,7 @@ impl RustLspAdapter {
|
||||
|
||||
format!(
|
||||
"{}-{}-{}.{}",
|
||||
SERVER_NAME,
|
||||
Self::SERVER_NAME,
|
||||
std::env::consts::ARCH,
|
||||
Self::ARCH_SERVER_NAME,
|
||||
extension
|
||||
@@ -102,7 +98,7 @@ impl ManifestProvider for CargoManifestProvider {
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for RustLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
SERVER_NAME.clone()
|
||||
Self::SERVER_NAME.clone()
|
||||
}
|
||||
|
||||
fn manifest_name(&self) -> Option<ManifestName> {
|
||||
@@ -477,30 +473,6 @@ impl LspAdapter for RustLspAdapter {
|
||||
filter_range,
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_initialize_params(
|
||||
&self,
|
||||
mut original: InitializeParams,
|
||||
cx: &App,
|
||||
) -> Result<InitializeParams> {
|
||||
let enable_lsp_tasks = ProjectSettings::get_global(cx)
|
||||
.lsp
|
||||
.get(&SERVER_NAME)
|
||||
.map_or(false, |s| s.enable_lsp_tasks);
|
||||
if enable_lsp_tasks {
|
||||
let experimental = json!({
|
||||
"runnables": {
|
||||
"kinds": [ "cargo", "shell" ],
|
||||
},
|
||||
});
|
||||
if let Some(ref mut original_experimental) = original.capabilities.experimental {
|
||||
merge_json_value_into(experimental, original_experimental);
|
||||
} else {
|
||||
original.capabilities.experimental = Some(experimental);
|
||||
}
|
||||
}
|
||||
Ok(original)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RustContextProvider;
|
||||
@@ -804,10 +776,6 @@ impl ContextProvider for RustContextProvider {
|
||||
|
||||
Some(TaskTemplates(task_templates))
|
||||
}
|
||||
|
||||
fn lsp_task_source(&self) -> Option<LanguageServerName> {
|
||||
Some(SERVER_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
/// Part of the data structure of Cargo metadata
|
||||
|
||||
@@ -1718,25 +1718,21 @@ impl MultiBuffer {
|
||||
(None, None) => break,
|
||||
(None, Some(_)) => {
|
||||
let existing_id = existing_iter.next().unwrap();
|
||||
let locator = snapshot.excerpt_locator_for_id(existing_id);
|
||||
let existing_excerpt = excerpts_cursor.item().unwrap();
|
||||
excerpts_cursor.seek_forward(&Some(locator), Bias::Left, &());
|
||||
let existing_end = existing_excerpt
|
||||
.range
|
||||
.context
|
||||
.end
|
||||
.to_point(&buffer_snapshot);
|
||||
if let Some((new_id, last)) = to_insert.last() {
|
||||
let locator = snapshot.excerpt_locator_for_id(existing_id);
|
||||
excerpts_cursor.seek_forward(&Some(locator), Bias::Left, &());
|
||||
if let Some(existing_excerpt) = excerpts_cursor
|
||||
.item()
|
||||
.filter(|e| e.buffer_id == buffer_snapshot.remote_id())
|
||||
{
|
||||
let existing_end = existing_excerpt
|
||||
.range
|
||||
.context
|
||||
.end
|
||||
.to_point(&buffer_snapshot);
|
||||
if existing_end <= last.context.end {
|
||||
self.snapshot
|
||||
.borrow_mut()
|
||||
.replaced_excerpts
|
||||
.insert(existing_id, *new_id);
|
||||
}
|
||||
};
|
||||
if existing_end <= last.context.end {
|
||||
self.snapshot
|
||||
.borrow_mut()
|
||||
.replaced_excerpts
|
||||
.insert(existing_id, *new_id);
|
||||
}
|
||||
}
|
||||
to_remove.push(existing_id);
|
||||
continue;
|
||||
@@ -1749,14 +1745,16 @@ impl MultiBuffer {
|
||||
};
|
||||
let locator = snapshot.excerpt_locator_for_id(*existing);
|
||||
excerpts_cursor.seek_forward(&Some(locator), Bias::Left, &());
|
||||
let Some(existing_excerpt) = excerpts_cursor
|
||||
.item()
|
||||
.filter(|e| e.buffer_id == buffer_snapshot.remote_id())
|
||||
else {
|
||||
let Some(existing_excerpt) = excerpts_cursor.item() else {
|
||||
to_remove.push(existing_iter.next().unwrap());
|
||||
to_insert.push((next_excerpt_id(), new_iter.next().unwrap()));
|
||||
continue;
|
||||
};
|
||||
if existing_excerpt.buffer_id != buffer_snapshot.remote_id() {
|
||||
to_remove.push(existing_iter.next().unwrap());
|
||||
to_insert.push((next_excerpt_id(), new_iter.next().unwrap()));
|
||||
continue;
|
||||
}
|
||||
|
||||
let existing_start = existing_excerpt
|
||||
.range
|
||||
|
||||
@@ -1798,88 +1798,6 @@ fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
|
||||
let buf1 = cx.new(|cx| {
|
||||
Buffer::local(
|
||||
indoc! {
|
||||
"zero
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
",
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let path: PathKey = PathKey::namespaced(0, Path::new("/").into());
|
||||
let buf2 = cx.new(|cx| {
|
||||
Buffer::local(
|
||||
indoc! {
|
||||
"000
|
||||
111
|
||||
222
|
||||
333
|
||||
"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path.clone(),
|
||||
buf1.clone(),
|
||||
vec![Point::row_range(1..1), Point::row_range(4..5)],
|
||||
1,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
assert_excerpts_match(
|
||||
&multibuffer,
|
||||
cx,
|
||||
indoc! {
|
||||
"-----
|
||||
zero
|
||||
one
|
||||
two
|
||||
-----
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
"
|
||||
},
|
||||
);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path.clone(),
|
||||
buf2.clone(),
|
||||
vec![Point::row_range(0..1)],
|
||||
2,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
assert_excerpts_match(
|
||||
&multibuffer,
|
||||
cx,
|
||||
indoc! {"-----
|
||||
000
|
||||
111
|
||||
222
|
||||
333
|
||||
"},
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
|
||||
let base_text_1 = indoc!(
|
||||
|
||||
@@ -33,7 +33,8 @@ impl From<IconName> for ToastIcon {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RegisterComponent)]
|
||||
#[derive(IntoComponent)]
|
||||
#[component(scope = "Notification")]
|
||||
pub struct StatusToast {
|
||||
icon: Option<ToastIcon>,
|
||||
text: SharedString,
|
||||
@@ -134,12 +135,8 @@ impl Focusable for StatusToast {
|
||||
|
||||
impl EventEmitter<DismissEvent> for StatusToast {}
|
||||
|
||||
impl Component for StatusToast {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Notification
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
impl ComponentPreview for StatusToast {
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
let text_example = StatusToast::new("Operation completed", cx, |this, _| this);
|
||||
|
||||
let action_example = StatusToast::new("Update ready to install", cx, |this, _cx| {
|
||||
@@ -178,40 +175,29 @@ impl Component for StatusToast {
|
||||
})
|
||||
});
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.p_4()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic Toast",
|
||||
vec![
|
||||
single_example("Text", div().child(text_example).into_any_element()),
|
||||
single_example(
|
||||
"Action",
|
||||
div().child(action_example).into_any_element(),
|
||||
),
|
||||
single_example("Icon", div().child(icon_example).into_any_element()),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Examples",
|
||||
vec![
|
||||
single_example(
|
||||
"Success",
|
||||
div().child(success_example).into_any_element(),
|
||||
),
|
||||
single_example("Error", div().child(error_example).into_any_element()),
|
||||
single_example(
|
||||
"Warning",
|
||||
div().child(warning_example).into_any_element(),
|
||||
),
|
||||
single_example("Create PR", div().child(pr_example).into_any_element()),
|
||||
],
|
||||
)
|
||||
.vertical(),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.p_4()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic Toast",
|
||||
vec![
|
||||
single_example("Text", div().child(text_example).into_any_element()),
|
||||
single_example("Action", div().child(action_example).into_any_element()),
|
||||
single_example("Icon", div().child(icon_example).into_any_element()),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Examples",
|
||||
vec![
|
||||
single_example("Success", div().child(success_example).into_any_element()),
|
||||
single_example("Error", div().child(error_example).into_any_element()),
|
||||
single_example("Warning", div().child(warning_example).into_any_element()),
|
||||
single_example("Create PR", div().child(pr_example).into_any_element()),
|
||||
],
|
||||
)
|
||||
.vertical(),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ use gpui::{App, AsyncApp, Entity};
|
||||
use language::{
|
||||
Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, OffsetRangeExt, PointUtf16,
|
||||
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
language_settings::{InlayHintKind, LanguageSettings, language_settings},
|
||||
language_settings::{
|
||||
AllLanguageSettings, InlayHintKind, LanguageSettings, LspInsertMode, language_settings,
|
||||
},
|
||||
point_from_lsp, point_to_lsp,
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
range_from_lsp, range_to_lsp,
|
||||
@@ -28,6 +30,7 @@ use lsp::{
|
||||
LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, OneOf, RenameOptions,
|
||||
ServerCapabilities,
|
||||
};
|
||||
use settings::Settings as _;
|
||||
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
|
||||
use std::{cmp::Reverse, mem, ops::Range, path::Path, sync::Arc};
|
||||
use text::{BufferId, LineEnding};
|
||||
@@ -974,69 +977,62 @@ async fn location_links_from_proto(
|
||||
let mut links = Vec::new();
|
||||
|
||||
for link in proto_links {
|
||||
links.push(location_link_from_proto(link, &lsp_store, &mut cx).await?)
|
||||
let origin = match link.origin {
|
||||
Some(origin) => {
|
||||
let buffer_id = BufferId::new(origin.buffer_id)?;
|
||||
let buffer = lsp_store
|
||||
.update(&mut cx, |lsp_store, cx| {
|
||||
lsp_store.wait_for_remote_buffer(buffer_id, cx)
|
||||
})?
|
||||
.await?;
|
||||
let start = origin
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin start"))?;
|
||||
let end = origin
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin end"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
|
||||
.await?;
|
||||
Some(Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
|
||||
let buffer_id = BufferId::new(target.buffer_id)?;
|
||||
let buffer = lsp_store
|
||||
.update(&mut cx, |lsp_store, cx| {
|
||||
lsp_store.wait_for_remote_buffer(buffer_id, cx)
|
||||
})?
|
||||
.await?;
|
||||
let start = target
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target start"))?;
|
||||
let end = target
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target end"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
|
||||
.await?;
|
||||
let target = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
|
||||
links.push(LocationLink { origin, target })
|
||||
}
|
||||
|
||||
Ok(links)
|
||||
}
|
||||
|
||||
pub async fn location_link_from_proto(
|
||||
link: proto::LocationLink,
|
||||
lsp_store: &Entity<LspStore>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<LocationLink> {
|
||||
let origin = match link.origin {
|
||||
Some(origin) => {
|
||||
let buffer_id = BufferId::new(origin.buffer_id)?;
|
||||
let buffer = lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.wait_for_remote_buffer(buffer_id, cx)
|
||||
})?
|
||||
.await?;
|
||||
let start = origin
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin start"))?;
|
||||
let end = origin
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin end"))?;
|
||||
buffer
|
||||
.update(cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
|
||||
.await?;
|
||||
Some(Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
|
||||
let buffer_id = BufferId::new(target.buffer_id)?;
|
||||
let buffer = lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.wait_for_remote_buffer(buffer_id, cx)
|
||||
})?
|
||||
.await?;
|
||||
let start = target
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target start"))?;
|
||||
let end = target
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target end"))?;
|
||||
buffer
|
||||
.update(cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
|
||||
.await?;
|
||||
let target = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
Ok(LocationLink { origin, target })
|
||||
}
|
||||
|
||||
async fn location_links_from_lsp(
|
||||
message: Option<lsp::GotoDefinitionResponse>,
|
||||
lsp_store: Entity<LspStore>,
|
||||
@@ -1119,65 +1115,6 @@ async fn location_links_from_lsp(
|
||||
Ok(definitions)
|
||||
}
|
||||
|
||||
pub async fn location_link_from_lsp(
|
||||
link: lsp::LocationLink,
|
||||
lsp_store: &Entity<LspStore>,
|
||||
buffer: &Entity<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<LocationLink> {
|
||||
let (lsp_adapter, language_server) =
|
||||
language_server_for_buffer(&lsp_store, &buffer, server_id, cx)?;
|
||||
|
||||
let (origin_range, target_uri, target_range) = (
|
||||
link.origin_selection_range,
|
||||
link.target_uri,
|
||||
link.target_selection_range,
|
||||
);
|
||||
|
||||
let target_buffer_handle = lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.open_local_buffer_via_lsp(
|
||||
target_uri,
|
||||
language_server.server_id(),
|
||||
lsp_adapter.name.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
cx.update(|cx| {
|
||||
let origin_location = origin_range.map(|origin_range| {
|
||||
let origin_buffer = buffer.read(cx);
|
||||
let origin_start =
|
||||
origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
|
||||
let origin_end =
|
||||
origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
|
||||
Location {
|
||||
buffer: buffer.clone(),
|
||||
range: origin_buffer.anchor_after(origin_start)
|
||||
..origin_buffer.anchor_before(origin_end),
|
||||
}
|
||||
});
|
||||
|
||||
let target_buffer = target_buffer_handle.read(cx);
|
||||
let target_start =
|
||||
target_buffer.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
|
||||
let target_end =
|
||||
target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
|
||||
let target_location = Location {
|
||||
buffer: target_buffer_handle,
|
||||
range: target_buffer.anchor_after(target_start)
|
||||
..target_buffer.anchor_before(target_end),
|
||||
};
|
||||
|
||||
LocationLink {
|
||||
origin: origin_location,
|
||||
target: target_location,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn location_links_to_proto(
|
||||
links: Vec<LocationLink>,
|
||||
lsp_store: &mut LspStore,
|
||||
@@ -1186,50 +1123,43 @@ fn location_links_to_proto(
|
||||
) -> Vec<proto::LocationLink> {
|
||||
links
|
||||
.into_iter()
|
||||
.map(|definition| location_link_to_proto(definition, lsp_store, peer_id, cx))
|
||||
.collect()
|
||||
}
|
||||
.map(|definition| {
|
||||
let origin = definition.origin.map(|origin| {
|
||||
lsp_store
|
||||
.buffer_store()
|
||||
.update(cx, |buffer_store, cx| {
|
||||
buffer_store.create_buffer_for_peer(&origin.buffer, peer_id, cx)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
pub fn location_link_to_proto(
|
||||
location: LocationLink,
|
||||
lsp_store: &mut LspStore,
|
||||
peer_id: PeerId,
|
||||
cx: &mut App,
|
||||
) -> proto::LocationLink {
|
||||
let origin = location.origin.map(|origin| {
|
||||
lsp_store
|
||||
.buffer_store()
|
||||
.update(cx, |buffer_store, cx| {
|
||||
buffer_store.create_buffer_for_peer(&origin.buffer, peer_id, cx)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
let buffer_id = origin.buffer.read(cx).remote_id().into();
|
||||
proto::Location {
|
||||
start: Some(serialize_anchor(&origin.range.start)),
|
||||
end: Some(serialize_anchor(&origin.range.end)),
|
||||
buffer_id,
|
||||
}
|
||||
});
|
||||
|
||||
let buffer_id = origin.buffer.read(cx).remote_id().into();
|
||||
proto::Location {
|
||||
start: Some(serialize_anchor(&origin.range.start)),
|
||||
end: Some(serialize_anchor(&origin.range.end)),
|
||||
buffer_id,
|
||||
}
|
||||
});
|
||||
lsp_store
|
||||
.buffer_store()
|
||||
.update(cx, |buffer_store, cx| {
|
||||
buffer_store.create_buffer_for_peer(&definition.target.buffer, peer_id, cx)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
lsp_store
|
||||
.buffer_store()
|
||||
.update(cx, |buffer_store, cx| {
|
||||
buffer_store.create_buffer_for_peer(&location.target.buffer, peer_id, cx)
|
||||
let buffer_id = definition.target.buffer.read(cx).remote_id().into();
|
||||
let target = proto::Location {
|
||||
start: Some(serialize_anchor(&definition.target.range.start)),
|
||||
end: Some(serialize_anchor(&definition.target.range.end)),
|
||||
buffer_id,
|
||||
};
|
||||
|
||||
proto::LocationLink {
|
||||
origin,
|
||||
target: Some(target),
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
let buffer_id = location.target.buffer.read(cx).remote_id().into();
|
||||
let target = proto::Location {
|
||||
start: Some(serialize_anchor(&location.target.range.start)),
|
||||
end: Some(serialize_anchor(&location.target.range.end)),
|
||||
buffer_id,
|
||||
};
|
||||
|
||||
proto::LocationLink {
|
||||
origin,
|
||||
target: Some(target),
|
||||
}
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
@@ -2158,7 +2088,7 @@ impl LspCommand for GetCompletions {
|
||||
.map(Arc::new);
|
||||
|
||||
let mut completion_edits = Vec::new();
|
||||
buffer.update(&mut cx, |buffer, _cx| {
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
||||
|
||||
@@ -2195,11 +2125,21 @@ impl LspCommand for GetCompletions {
|
||||
// If the language server provides a range to overwrite, then
|
||||
// check that the range is valid.
|
||||
Some(completion_text_edit) => {
|
||||
match parse_completion_text_edit(&completion_text_edit, &snapshot) {
|
||||
let completion_mode = AllLanguageSettings::get_global(cx)
|
||||
.defaults
|
||||
.completions
|
||||
.lsp_insert_mode;
|
||||
|
||||
match parse_completion_text_edit(
|
||||
&completion_text_edit,
|
||||
&snapshot,
|
||||
completion_mode,
|
||||
) {
|
||||
Some(edit) => edit,
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
|
||||
// If the language server does not provide a range, then infer
|
||||
// the range based on the syntax tree.
|
||||
None => {
|
||||
@@ -2251,12 +2191,7 @@ impl LspCommand for GetCompletions {
|
||||
.as_ref()
|
||||
.unwrap_or(&lsp_completion.label)
|
||||
.clone();
|
||||
|
||||
ParsedCompletionEdit {
|
||||
replace_range: range,
|
||||
insert_range: None,
|
||||
new_text: text,
|
||||
}
|
||||
(range, text)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2272,8 +2207,8 @@ impl LspCommand for GetCompletions {
|
||||
Ok(completions
|
||||
.into_iter()
|
||||
.zip(completion_edits)
|
||||
.map(|(mut lsp_completion, mut edit)| {
|
||||
LineEnding::normalize(&mut edit.new_text);
|
||||
.map(|(mut lsp_completion, (old_range, mut new_text))| {
|
||||
LineEnding::normalize(&mut new_text);
|
||||
if lsp_completion.data.is_none() {
|
||||
if let Some(default_data) = lsp_defaults
|
||||
.as_ref()
|
||||
@@ -2285,10 +2220,9 @@ impl LspCommand for GetCompletions {
|
||||
}
|
||||
}
|
||||
CoreCompletion {
|
||||
replace_range: edit.replace_range,
|
||||
new_text: edit.new_text,
|
||||
old_range,
|
||||
new_text,
|
||||
source: CompletionSource::Lsp {
|
||||
insert_range: edit.insert_range,
|
||||
server_id,
|
||||
lsp_completion: Box::new(lsp_completion),
|
||||
lsp_defaults: lsp_defaults.clone(),
|
||||
@@ -2378,53 +2312,91 @@ impl LspCommand for GetCompletions {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ParsedCompletionEdit {
|
||||
pub replace_range: Range<Anchor>,
|
||||
pub insert_range: Option<Range<Anchor>>,
|
||||
pub new_text: String,
|
||||
}
|
||||
|
||||
pub(crate) fn parse_completion_text_edit(
|
||||
edit: &lsp::CompletionTextEdit,
|
||||
snapshot: &BufferSnapshot,
|
||||
) -> Option<ParsedCompletionEdit> {
|
||||
let (replace_range, insert_range, new_text) = match edit {
|
||||
lsp::CompletionTextEdit::Edit(edit) => (edit.range, None, &edit.new_text),
|
||||
lsp::CompletionTextEdit::InsertAndReplace(edit) => {
|
||||
(edit.replace, Some(edit.insert), &edit.new_text)
|
||||
}
|
||||
};
|
||||
|
||||
let replace_range = {
|
||||
let range = range_from_lsp(replace_range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end)
|
||||
};
|
||||
|
||||
let insert_range = match insert_range {
|
||||
None => None,
|
||||
Some(insert_range) => {
|
||||
let range = range_from_lsp(insert_range);
|
||||
completion_mode: LspInsertMode,
|
||||
) -> Option<(Range<Anchor>, String)> {
|
||||
match edit {
|
||||
lsp::CompletionTextEdit::Edit(edit) => {
|
||||
let range = range_from_lsp(edit.range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion (insert) out of expected range");
|
||||
return None;
|
||||
log::info!("completion out of expected range");
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
))
|
||||
}
|
||||
Some(snapshot.anchor_before(start)..snapshot.anchor_after(end))
|
||||
}
|
||||
};
|
||||
|
||||
Some(ParsedCompletionEdit {
|
||||
insert_range: insert_range,
|
||||
replace_range: replace_range,
|
||||
new_text: new_text.clone(),
|
||||
})
|
||||
lsp::CompletionTextEdit::InsertAndReplace(edit) => {
|
||||
let replace = match completion_mode {
|
||||
LspInsertMode::Insert => false,
|
||||
LspInsertMode::Replace => true,
|
||||
LspInsertMode::ReplaceSubsequence => {
|
||||
let range_to_replace = range_from_lsp(edit.replace);
|
||||
|
||||
let start = snapshot.clip_point_utf16(range_to_replace.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range_to_replace.end, Bias::Left);
|
||||
if start != range_to_replace.start.0 || end != range_to_replace.end.0 {
|
||||
false
|
||||
} else {
|
||||
let mut completion_text = edit.new_text.chars();
|
||||
|
||||
let mut text_to_replace = snapshot.chars_for_range(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
);
|
||||
|
||||
// is `text_to_replace` a subsequence of `completion_text`
|
||||
text_to_replace.all(|needle_ch| {
|
||||
completion_text.any(|haystack_ch| haystack_ch == needle_ch)
|
||||
})
|
||||
}
|
||||
}
|
||||
LspInsertMode::ReplaceSuffix => {
|
||||
let range_after_cursor = lsp::Range {
|
||||
start: edit.insert.end,
|
||||
end: edit.replace.end,
|
||||
};
|
||||
let range_after_cursor = range_from_lsp(range_after_cursor);
|
||||
|
||||
let start = snapshot.clip_point_utf16(range_after_cursor.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range_after_cursor.end, Bias::Left);
|
||||
if start != range_after_cursor.start.0 || end != range_after_cursor.end.0 {
|
||||
false
|
||||
} else {
|
||||
let text_after_cursor = snapshot
|
||||
.text_for_range(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
)
|
||||
.collect::<String>();
|
||||
edit.new_text.ends_with(&text_after_cursor)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let range = range_from_lsp(match replace {
|
||||
true => edit.replace,
|
||||
false => edit.insert,
|
||||
});
|
||||
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
|
||||
@@ -39,7 +39,8 @@ use language::{
|
||||
LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
|
||||
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
language_settings::{
|
||||
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings,
|
||||
AllLanguageSettings, FormatOnSave, Formatter, LanguageSettings, LspInsertMode,
|
||||
SelectedFormatter, language_settings,
|
||||
},
|
||||
point_to_lsp,
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
@@ -279,7 +280,7 @@ impl LocalLspStore {
|
||||
let initialization_params = cx.update(|cx| {
|
||||
let mut params = language_server.default_initialize_params(cx);
|
||||
params.initialization_options = initialization_options;
|
||||
adapter.adapter.prepare_initialize_params(params, cx)
|
||||
adapter.adapter.prepare_initialize_params(params)
|
||||
})??;
|
||||
|
||||
Self::setup_lsp_messages(
|
||||
@@ -3427,9 +3428,6 @@ impl LspStore {
|
||||
|
||||
client.add_entity_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
|
||||
client.add_entity_request_handler(Self::handle_lsp_command::<lsp_ext_command::OpenDocs>);
|
||||
client.add_entity_request_handler(
|
||||
Self::handle_lsp_command::<lsp_ext_command::GetLspRunnables>,
|
||||
);
|
||||
client.add_entity_request_handler(
|
||||
Self::handle_lsp_command::<lsp_ext_command::SwitchSourceHeader>,
|
||||
);
|
||||
@@ -5135,6 +5133,7 @@ impl LspStore {
|
||||
&buffer_snapshot,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.log_err()
|
||||
@@ -5168,6 +5167,7 @@ impl LspStore {
|
||||
snapshot: &BufferSnapshot,
|
||||
completions: Rc<RefCell<Box<[Completion]>>>,
|
||||
completion_index: usize,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
let server_id = server.server_id();
|
||||
let can_resolve = server
|
||||
@@ -5205,38 +5205,41 @@ impl LspStore {
|
||||
};
|
||||
let resolved_completion = request.await?;
|
||||
|
||||
let mut updated_insert_range = None;
|
||||
if let Some(text_edit) = resolved_completion.text_edit.as_ref() {
|
||||
// Technically we don't have to parse the whole `text_edit`, since the only
|
||||
// language server we currently use that does update `text_edit` in `completionItem/resolve`
|
||||
// is `typescript-language-server` and they only update `text_edit.new_text`.
|
||||
// But we should not rely on that.
|
||||
let edit = parse_completion_text_edit(text_edit, snapshot);
|
||||
let completion_mode = cx
|
||||
.read_global(|_: &SettingsStore, cx| {
|
||||
AllLanguageSettings::get_global(cx)
|
||||
.defaults
|
||||
.completions
|
||||
.lsp_insert_mode
|
||||
})
|
||||
.unwrap_or(LspInsertMode::Insert);
|
||||
let edit = parse_completion_text_edit(text_edit, snapshot, completion_mode);
|
||||
|
||||
if let Some(mut parsed_edit) = edit {
|
||||
LineEnding::normalize(&mut parsed_edit.new_text);
|
||||
if let Some((old_range, mut new_text)) = edit {
|
||||
LineEnding::normalize(&mut new_text);
|
||||
|
||||
let mut completions = completions.borrow_mut();
|
||||
let completion = &mut completions[completion_index];
|
||||
|
||||
completion.new_text = parsed_edit.new_text;
|
||||
completion.replace_range = parsed_edit.replace_range;
|
||||
|
||||
updated_insert_range = parsed_edit.insert_range;
|
||||
completion.new_text = new_text;
|
||||
completion.old_range = old_range;
|
||||
}
|
||||
}
|
||||
|
||||
let mut completions = completions.borrow_mut();
|
||||
let completion = &mut completions[completion_index];
|
||||
if let CompletionSource::Lsp {
|
||||
insert_range,
|
||||
lsp_completion,
|
||||
resolved,
|
||||
server_id: completion_server_id,
|
||||
..
|
||||
} = &mut completion.source
|
||||
{
|
||||
*insert_range = updated_insert_range;
|
||||
if *resolved {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -5378,19 +5381,12 @@ impl LspStore {
|
||||
let completion = &mut completions[completion_index];
|
||||
completion.documentation = Some(documentation);
|
||||
if let CompletionSource::Lsp {
|
||||
insert_range,
|
||||
lsp_completion,
|
||||
resolved,
|
||||
server_id: completion_server_id,
|
||||
lsp_defaults: _,
|
||||
} = &mut completion.source
|
||||
{
|
||||
let completion_insert_range = response
|
||||
.old_insert_start
|
||||
.and_then(deserialize_anchor)
|
||||
.zip(response.old_insert_end.and_then(deserialize_anchor));
|
||||
*insert_range = completion_insert_range.map(|(start, end)| start..end);
|
||||
|
||||
if *resolved {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -5402,14 +5398,14 @@ impl LspStore {
|
||||
*resolved = true;
|
||||
}
|
||||
|
||||
let replace_range = response
|
||||
.old_replace_start
|
||||
let old_range = response
|
||||
.old_start
|
||||
.and_then(deserialize_anchor)
|
||||
.zip(response.old_replace_end.and_then(deserialize_anchor));
|
||||
if let Some((old_replace_start, old_replace_end)) = replace_range {
|
||||
.zip(response.old_end.and_then(deserialize_anchor));
|
||||
if let Some((old_start, old_end)) = old_range {
|
||||
if !response.new_text.is_empty() {
|
||||
completion.new_text = response.new_text;
|
||||
completion.replace_range = old_replace_start..old_replace_end;
|
||||
completion.old_range = old_start..old_end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5434,7 +5430,7 @@ impl LspStore {
|
||||
project_id,
|
||||
buffer_id: buffer_id.into(),
|
||||
completion: Some(Self::serialize_completion(&CoreCompletion {
|
||||
replace_range: completion.replace_range,
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
source: completion.source,
|
||||
})),
|
||||
@@ -5478,6 +5474,7 @@ impl LspStore {
|
||||
&snapshot,
|
||||
completions.clone(),
|
||||
completion_index,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.context("resolving completion")?;
|
||||
@@ -5505,7 +5502,7 @@ impl LspStore {
|
||||
buffer.start_transaction();
|
||||
|
||||
for (range, text) in edits {
|
||||
let primary = &completion.replace_range;
|
||||
let primary = &completion.old_range;
|
||||
let start_within = primary.start.cmp(&range.start, buffer).is_le()
|
||||
&& primary.end.cmp(&range.start, buffer).is_ge();
|
||||
let end_within = range.start.cmp(&primary.end, buffer).is_le()
|
||||
@@ -7709,10 +7706,8 @@ impl LspStore {
|
||||
|
||||
// If we have a new buffer_id, that means we're talking to a new client
|
||||
// and want to check for new text_edits in the completion too.
|
||||
let mut old_replace_start = None;
|
||||
let mut old_replace_end = None;
|
||||
let mut old_insert_start = None;
|
||||
let mut old_insert_end = None;
|
||||
let mut old_start = None;
|
||||
let mut old_end = None;
|
||||
let mut new_text = String::default();
|
||||
if let Ok(buffer_id) = BufferId::new(envelope.payload.buffer_id) {
|
||||
let buffer_snapshot = this.update(&mut cx, |this, cx| {
|
||||
@@ -7721,18 +7716,23 @@ impl LspStore {
|
||||
})??;
|
||||
|
||||
if let Some(text_edit) = completion.text_edit.as_ref() {
|
||||
let edit = parse_completion_text_edit(text_edit, &buffer_snapshot);
|
||||
let completion_mode = cx
|
||||
.read_global(|_: &SettingsStore, cx| {
|
||||
AllLanguageSettings::get_global(cx)
|
||||
.defaults
|
||||
.completions
|
||||
.lsp_insert_mode
|
||||
})
|
||||
.unwrap_or(LspInsertMode::Insert);
|
||||
|
||||
if let Some(mut edit) = edit {
|
||||
LineEnding::normalize(&mut edit.new_text);
|
||||
let edit = parse_completion_text_edit(text_edit, &buffer_snapshot, completion_mode);
|
||||
|
||||
new_text = edit.new_text;
|
||||
old_replace_start = Some(serialize_anchor(&edit.replace_range.start));
|
||||
old_replace_end = Some(serialize_anchor(&edit.replace_range.end));
|
||||
if let Some(insert_range) = edit.insert_range {
|
||||
old_insert_start = Some(serialize_anchor(&insert_range.start));
|
||||
old_insert_end = Some(serialize_anchor(&insert_range.end));
|
||||
}
|
||||
if let Some((old_range, mut text_edit_new_text)) = edit {
|
||||
LineEnding::normalize(&mut text_edit_new_text);
|
||||
|
||||
new_text = text_edit_new_text;
|
||||
old_start = Some(serialize_anchor(&old_range.start));
|
||||
old_end = Some(serialize_anchor(&old_range.end));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7740,12 +7740,10 @@ impl LspStore {
|
||||
Ok(proto::ResolveCompletionDocumentationResponse {
|
||||
documentation,
|
||||
documentation_is_markdown,
|
||||
old_replace_start,
|
||||
old_replace_end,
|
||||
old_start,
|
||||
old_end,
|
||||
new_text,
|
||||
lsp_completion,
|
||||
old_insert_start,
|
||||
old_insert_end,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8047,7 +8045,7 @@ impl LspStore {
|
||||
this.apply_additional_edits_for_completion(
|
||||
buffer,
|
||||
Rc::new(RefCell::new(Box::new([Completion {
|
||||
replace_range: completion.replace_range,
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
source: completion.source,
|
||||
documentation: None,
|
||||
@@ -8370,6 +8368,7 @@ impl LspStore {
|
||||
self.buffer_store.update(cx, |buffer_store, cx| {
|
||||
for buffer in buffer_store.buffers() {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
// TODO kb clean inlays
|
||||
buffer.update_diagnostics(server_id, DiagnosticSet::new([], buffer), cx);
|
||||
buffer.set_completion_triggers(server_id, Default::default(), cx);
|
||||
});
|
||||
@@ -9102,26 +9101,18 @@ impl LspStore {
|
||||
|
||||
pub(crate) fn serialize_completion(completion: &CoreCompletion) -> proto::Completion {
|
||||
let mut serialized_completion = proto::Completion {
|
||||
old_replace_start: Some(serialize_anchor(&completion.replace_range.start)),
|
||||
old_replace_end: Some(serialize_anchor(&completion.replace_range.end)),
|
||||
old_start: Some(serialize_anchor(&completion.old_range.start)),
|
||||
old_end: Some(serialize_anchor(&completion.old_range.end)),
|
||||
new_text: completion.new_text.clone(),
|
||||
..proto::Completion::default()
|
||||
};
|
||||
match &completion.source {
|
||||
CompletionSource::Lsp {
|
||||
insert_range,
|
||||
server_id,
|
||||
lsp_completion,
|
||||
lsp_defaults,
|
||||
resolved,
|
||||
} => {
|
||||
let (old_insert_start, old_insert_end) = insert_range
|
||||
.as_ref()
|
||||
.map(|range| (serialize_anchor(&range.start), serialize_anchor(&range.end)))
|
||||
.unzip();
|
||||
|
||||
serialized_completion.old_insert_start = old_insert_start;
|
||||
serialized_completion.old_insert_end = old_insert_end;
|
||||
serialized_completion.source = proto::completion::Source::Lsp as i32;
|
||||
serialized_completion.server_id = server_id.0 as u64;
|
||||
serialized_completion.lsp_completion = serde_json::to_vec(lsp_completion).unwrap();
|
||||
@@ -9149,31 +9140,20 @@ impl LspStore {
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_completion(completion: proto::Completion) -> Result<CoreCompletion> {
|
||||
let old_replace_start = completion
|
||||
.old_replace_start
|
||||
let old_start = completion
|
||||
.old_start
|
||||
.and_then(deserialize_anchor)
|
||||
.context("invalid old start")?;
|
||||
let old_replace_end = completion
|
||||
.old_replace_end
|
||||
let old_end = completion
|
||||
.old_end
|
||||
.and_then(deserialize_anchor)
|
||||
.context("invalid old end")?;
|
||||
let insert_range = {
|
||||
match completion.old_insert_start.zip(completion.old_insert_end) {
|
||||
Some((start, end)) => {
|
||||
let start = deserialize_anchor(start).context("invalid insert old start")?;
|
||||
let end = deserialize_anchor(end).context("invalid insert old end")?;
|
||||
Some(start..end)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
Ok(CoreCompletion {
|
||||
replace_range: old_replace_start..old_replace_end,
|
||||
old_range: old_start..old_end,
|
||||
new_text: completion.new_text,
|
||||
source: match proto::completion::Source::from_i32(completion.source) {
|
||||
Some(proto::completion::Source::Custom) => CompletionSource::Custom,
|
||||
Some(proto::completion::Source::Lsp) => CompletionSource::Lsp {
|
||||
insert_range,
|
||||
server_id: LanguageServerId::from_proto(completion.server_id),
|
||||
lsp_completion: serde_json::from_slice(&completion.lsp_completion)?,
|
||||
lsp_defaults: completion
|
||||
@@ -9362,7 +9342,7 @@ async fn populate_labels_for_completions(
|
||||
completions.push(Completion {
|
||||
label,
|
||||
documentation,
|
||||
replace_range: completion.replace_range,
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
insert_text_mode: lsp_completion.insert_text_mode,
|
||||
source: completion.source,
|
||||
@@ -9376,7 +9356,7 @@ async fn populate_labels_for_completions(
|
||||
completions.push(Completion {
|
||||
label,
|
||||
documentation: None,
|
||||
replace_range: completion.replace_range,
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
source: completion.source,
|
||||
insert_text_mode: None,
|
||||
|
||||
@@ -1,27 +1,12 @@
|
||||
use crate::{
|
||||
LocationLink,
|
||||
lsp_command::{
|
||||
LspCommand, location_link_from_lsp, location_link_from_proto, location_link_to_proto,
|
||||
},
|
||||
lsp_store::LspStore,
|
||||
make_text_document_identifier,
|
||||
};
|
||||
use crate::{lsp_command::LspCommand, lsp_store::LspStore, make_text_document_identifier};
|
||||
use anyhow::{Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::{App, AsyncApp, Entity};
|
||||
use language::{
|
||||
Buffer, point_to_lsp,
|
||||
proto::{deserialize_anchor, serialize_anchor},
|
||||
};
|
||||
use language::{Buffer, point_to_lsp, proto::deserialize_anchor};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use rpc::proto::{self, PeerId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use task::TaskTemplate;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use text::{BufferId, PointUtf16, ToPointUtf16};
|
||||
|
||||
pub enum LspExpandMacro {}
|
||||
@@ -378,245 +363,3 @@ impl LspCommand for SwitchSourceHeader {
|
||||
BufferId::new(message.buffer_id)
|
||||
}
|
||||
}
|
||||
|
||||
// https://rust-analyzer.github.io/book/contributing/lsp-extensions.html#runnables
|
||||
// Taken from https://github.com/rust-lang/rust-analyzer/blob/a73a37a757a58b43a796d3eb86a1f7dfd0036659/crates/rust-analyzer/src/lsp/ext.rs#L425-L489
|
||||
pub enum Runnables {}
|
||||
|
||||
impl lsp::request::Request for Runnables {
|
||||
type Params = RunnablesParams;
|
||||
type Result = Vec<Runnable>;
|
||||
const METHOD: &'static str = "experimental/runnables";
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RunnablesParams {
|
||||
pub text_document: lsp::TextDocumentIdentifier,
|
||||
pub position: Option<lsp::Position>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Runnable {
|
||||
pub label: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub location: Option<lsp::LocationLink>,
|
||||
pub kind: RunnableKind,
|
||||
pub args: RunnableArgs,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum RunnableArgs {
|
||||
Cargo(CargoRunnableArgs),
|
||||
Shell(ShellRunnableArgs),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum RunnableKind {
|
||||
Cargo,
|
||||
Shell,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CargoRunnableArgs {
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||
pub environment: HashMap<String, String>,
|
||||
pub cwd: PathBuf,
|
||||
/// Command to be executed instead of cargo
|
||||
pub override_cargo: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workspace_root: Option<PathBuf>,
|
||||
// command, --package and --lib stuff
|
||||
pub cargo_args: Vec<String>,
|
||||
// stuff after --
|
||||
pub executable_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ShellRunnableArgs {
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||
pub environment: HashMap<String, String>,
|
||||
pub cwd: PathBuf,
|
||||
pub program: String,
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GetLspRunnables {
|
||||
pub buffer_id: BufferId,
|
||||
pub position: Option<text::Anchor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LspRunnables {
|
||||
pub runnables: Vec<(Option<LocationLink>, TaskTemplate)>,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetLspRunnables {
|
||||
type Response = LspRunnables;
|
||||
type LspRequest = Runnables;
|
||||
type ProtoRequest = proto::LspExtRunnables;
|
||||
|
||||
fn display_name(&self) -> &str {
|
||||
"LSP Runnables"
|
||||
}
|
||||
|
||||
fn to_lsp(
|
||||
&self,
|
||||
path: &Path,
|
||||
buffer: &Buffer,
|
||||
_: &Arc<LanguageServer>,
|
||||
_: &App,
|
||||
) -> Result<RunnablesParams> {
|
||||
let url = match lsp::Url::from_file_path(path) {
|
||||
Ok(url) => url,
|
||||
Err(()) => anyhow::bail!("Failed to parse path {path:?} as lsp::Url"),
|
||||
};
|
||||
Ok(RunnablesParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(url),
|
||||
position: self
|
||||
.position
|
||||
.map(|anchor| point_to_lsp(anchor.to_point_utf16(&buffer.snapshot()))),
|
||||
})
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
lsp_runnables: Vec<Runnable>,
|
||||
lsp_store: Entity<LspStore>,
|
||||
buffer: Entity<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<LspRunnables> {
|
||||
let mut runnables = Vec::with_capacity(lsp_runnables.len());
|
||||
|
||||
for runnable in lsp_runnables {
|
||||
let location = match runnable.location {
|
||||
Some(location) => Some(
|
||||
location_link_from_lsp(location, &lsp_store, &buffer, server_id, &mut cx)
|
||||
.await?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
let mut task_template = TaskTemplate::default();
|
||||
task_template.label = runnable.label;
|
||||
match runnable.args {
|
||||
RunnableArgs::Cargo(cargo) => {
|
||||
match cargo.override_cargo {
|
||||
Some(override_cargo) => {
|
||||
let mut override_parts =
|
||||
override_cargo.split(" ").map(|s| s.to_string());
|
||||
task_template.command = override_parts
|
||||
.next()
|
||||
.unwrap_or_else(|| override_cargo.clone());
|
||||
task_template.args.extend(override_parts);
|
||||
}
|
||||
None => task_template.command = "cargo".to_string(),
|
||||
};
|
||||
task_template.env = cargo.environment;
|
||||
task_template.cwd = Some(
|
||||
cargo
|
||||
.workspace_root
|
||||
.unwrap_or(cargo.cwd)
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
task_template.args.extend(cargo.cargo_args);
|
||||
if !cargo.executable_args.is_empty() {
|
||||
task_template.args.push("--".to_string());
|
||||
task_template.args.extend(cargo.executable_args);
|
||||
}
|
||||
}
|
||||
RunnableArgs::Shell(shell) => {
|
||||
task_template.command = shell.program;
|
||||
task_template.args = shell.args;
|
||||
task_template.env = shell.environment;
|
||||
task_template.cwd = Some(shell.cwd.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
runnables.push((location, task_template));
|
||||
}
|
||||
|
||||
Ok(LspRunnables { runnables })
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtRunnables {
|
||||
proto::LspExtRunnables {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id().to_proto(),
|
||||
position: self.position.as_ref().map(serialize_anchor),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
message: proto::LspExtRunnables,
|
||||
_: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
_: AsyncApp,
|
||||
) -> Result<Self> {
|
||||
let buffer_id = Self::buffer_id_from_proto(&message)?;
|
||||
let position = message.position.and_then(deserialize_anchor);
|
||||
Ok(Self {
|
||||
buffer_id,
|
||||
position,
|
||||
})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: LspRunnables,
|
||||
lsp_store: &mut LspStore,
|
||||
peer_id: PeerId,
|
||||
_: &clock::Global,
|
||||
cx: &mut App,
|
||||
) -> proto::LspExtRunnablesResponse {
|
||||
proto::LspExtRunnablesResponse {
|
||||
runnables: response
|
||||
.runnables
|
||||
.into_iter()
|
||||
.map(|(location, task_template)| proto::LspRunnable {
|
||||
location: location
|
||||
.map(|location| location_link_to_proto(location, lsp_store, peer_id, cx)),
|
||||
task_template: serde_json::to_vec(&task_template).unwrap(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::LspExtRunnablesResponse,
|
||||
lsp_store: Entity<LspStore>,
|
||||
_: Entity<Buffer>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<LspRunnables> {
|
||||
let mut runnables = LspRunnables {
|
||||
runnables: Vec::new(),
|
||||
};
|
||||
|
||||
for lsp_runnable in message.runnables {
|
||||
let location = match lsp_runnable.location {
|
||||
Some(location) => {
|
||||
Some(location_link_from_proto(location, &lsp_store, &mut cx).await?)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let task_template = serde_json::from_slice(&lsp_runnable.task_template)
|
||||
.context("deserializing task template from proto")?;
|
||||
runnables.runnables.push((location, task_template));
|
||||
}
|
||||
|
||||
Ok(runnables)
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::LspExtRunnables) -> Result<BufferId> {
|
||||
BufferId::new(message.buffer_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ pub const RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||
|
||||
/// Experimental: Informs the end user about the state of the server
|
||||
///
|
||||
/// [Rust Analyzer Specification](https://rust-analyzer.github.io/book/contributing/lsp-extensions.html#server-status)
|
||||
/// [Rust Analyzer Specification](https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#server-status)
|
||||
#[derive(Debug)]
|
||||
enum ServerStatus {}
|
||||
|
||||
@@ -38,10 +38,13 @@ pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server:
|
||||
let name = language_server.name();
|
||||
let server_id = language_server.server_id();
|
||||
|
||||
let this = lsp_store;
|
||||
|
||||
language_server
|
||||
.on_notification::<ServerStatus, _>({
|
||||
let name = name.to_string();
|
||||
move |params, cx| {
|
||||
let this = this.clone();
|
||||
let name = name.to_string();
|
||||
if let Some(ref message) = params.message {
|
||||
let message = message.trim();
|
||||
@@ -50,10 +53,10 @@ pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server:
|
||||
"Language server {name} (id {server_id}) status update: {message}"
|
||||
);
|
||||
match params.health {
|
||||
ServerHealthStatus::Ok => log::info!("{formatted_message}"),
|
||||
ServerHealthStatus::Warning => log::warn!("{formatted_message}"),
|
||||
ServerHealthStatus::Ok => log::info!("{}", formatted_message),
|
||||
ServerHealthStatus::Warning => log::warn!("{}", formatted_message),
|
||||
ServerHealthStatus::Error => {
|
||||
log::error!("{formatted_message}");
|
||||
log::error!("{}", formatted_message);
|
||||
let (tx, _rx) = smol::channel::bounded(1);
|
||||
let request = LanguageServerPromptRequest {
|
||||
level: PromptLevel::Critical,
|
||||
@@ -62,7 +65,7 @@ pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server:
|
||||
response_channel: tx,
|
||||
lsp_name: name.clone(),
|
||||
};
|
||||
lsp_store
|
||||
let _ = this
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(LspStoreEvent::LanguageServerPrompt(request));
|
||||
})
|
||||
|
||||
@@ -359,14 +359,8 @@ pub struct InlayHint {
|
||||
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
|
||||
pub enum CompletionIntent {
|
||||
/// The user intends to 'commit' this result, if possible
|
||||
/// completion confirmations should run side effects.
|
||||
///
|
||||
/// For LSP completions, will respect the setting `completions.lsp_insert_mode`.
|
||||
/// completion confirmations should run side effects
|
||||
Complete,
|
||||
/// Similar to [Self::Complete], but behaves like `lsp_insert_mode` is set to `insert`.
|
||||
CompleteWithInsert,
|
||||
/// Similar to [Self::Complete], but behaves like `lsp_insert_mode` is set to `replace`.
|
||||
CompleteWithReplace,
|
||||
/// The user intends to continue 'composing' this completion
|
||||
/// completion confirmations should not run side effects and
|
||||
/// let the user continue composing their action
|
||||
@@ -383,11 +377,11 @@ impl CompletionIntent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to `CoreCompletion`, but with extra metadata attached.
|
||||
/// A completion provided by a language server
|
||||
#[derive(Clone)]
|
||||
pub struct Completion {
|
||||
/// The range of text that will be replaced by this completion.
|
||||
pub replace_range: Range<Anchor>,
|
||||
/// The range of the buffer that will be replaced.
|
||||
pub old_range: Range<Anchor>,
|
||||
/// The new text that will be inserted.
|
||||
pub new_text: String,
|
||||
/// A label for this completion that is shown in the menu.
|
||||
@@ -410,8 +404,6 @@ pub struct Completion {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CompletionSource {
|
||||
Lsp {
|
||||
/// The alternate `insert` range, if provided by the LSP server.
|
||||
insert_range: Option<Range<Anchor>>,
|
||||
/// The id of the language server that produced this completion.
|
||||
server_id: LanguageServerId,
|
||||
/// The raw completion provided by the language server.
|
||||
@@ -516,7 +508,7 @@ impl CompletionSource {
|
||||
impl std::fmt::Debug for Completion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Completion")
|
||||
.field("replace_range", &self.replace_range)
|
||||
.field("old_range", &self.old_range)
|
||||
.field("new_text", &self.new_text)
|
||||
.field("label", &self.label)
|
||||
.field("documentation", &self.documentation)
|
||||
@@ -525,10 +517,10 @@ impl std::fmt::Debug for Completion {
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic completion that can come from different sources.
|
||||
/// A completion provided by a language server
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct CoreCompletion {
|
||||
replace_range: Range<Anchor>,
|
||||
old_range: Range<Anchor>,
|
||||
new_text: String,
|
||||
source: CompletionSource,
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use task::{TaskTemplates, VsCodeTaskFile};
|
||||
use util::{ResultExt, serde::default_true};
|
||||
use util::ResultExt;
|
||||
use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId};
|
||||
|
||||
use crate::{
|
||||
@@ -278,28 +278,12 @@ pub struct BinarySettings {
|
||||
pub ignore_system_version: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct LspSettings {
|
||||
pub binary: Option<BinarySettings>,
|
||||
pub initialization_options: Option<serde_json::Value>,
|
||||
pub settings: Option<serde_json::Value>,
|
||||
/// If the server supports sending tasks over LSP extensions,
|
||||
/// this setting can be used to enable or disable them in Zed.
|
||||
/// Default: true
|
||||
#[serde(default = "default_true")]
|
||||
pub enable_lsp_tasks: bool,
|
||||
}
|
||||
|
||||
impl Default for LspSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
binary: None,
|
||||
initialization_options: None,
|
||||
settings: None,
|
||||
enable_lsp_tasks: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
|
||||
@@ -459,8 +459,6 @@ async fn test_fallback_to_single_worktree_tasks(cx: &mut gpui::TestAppContext) {
|
||||
active_item_context: Some((Some(worktree_id), None, TaskContext::default())),
|
||||
active_worktree_context: None,
|
||||
other_worktree_contexts: Vec::new(),
|
||||
lsp_task_sources: HashMap::default(),
|
||||
latest_selection: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -483,8 +481,6 @@ async fn test_fallback_to_single_worktree_tasks(cx: &mut gpui::TestAppContext) {
|
||||
worktree_context
|
||||
})),
|
||||
other_worktree_contexts: Vec::new(),
|
||||
lsp_task_sources: HashMap::default(),
|
||||
latest_selection: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -801,7 +797,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
.await
|
||||
.text_document,
|
||||
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path!("/dir/test3.rs")).unwrap()),
|
||||
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path!("/dir/test3.rs")).unwrap(),),
|
||||
);
|
||||
assert_eq!(
|
||||
fake_json_server
|
||||
@@ -3018,7 +3014,7 @@ async fn test_completions_with_text_edit(cx: &mut gpui::TestAppContext) {
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(completions[0].new_text, "textEditText");
|
||||
assert_eq!(
|
||||
completions[0].replace_range.to_offset(&snapshot),
|
||||
completions[0].old_range.to_offset(&snapshot),
|
||||
text.len() - 3..text.len()
|
||||
);
|
||||
}
|
||||
@@ -3101,7 +3097,7 @@ async fn test_completions_with_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(completions[0].new_text, "insertText");
|
||||
assert_eq!(
|
||||
completions[0].replace_range.to_offset(&snapshot),
|
||||
completions[0].old_range.to_offset(&snapshot),
|
||||
text.len() - 3..text.len()
|
||||
);
|
||||
}
|
||||
@@ -3143,7 +3139,7 @@ async fn test_completions_with_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(completions[0].new_text, "labelText");
|
||||
assert_eq!(
|
||||
completions[0].replace_range.to_offset(&snapshot),
|
||||
completions[0].old_range.to_offset(&snapshot),
|
||||
text.len() - 3..text.len()
|
||||
);
|
||||
}
|
||||
@@ -3213,7 +3209,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(completions[0].new_text, "fullyQualifiedName");
|
||||
assert_eq!(
|
||||
completions[0].replace_range.to_offset(&snapshot),
|
||||
completions[0].old_range.to_offset(&snapshot),
|
||||
text.len() - 3..text.len()
|
||||
);
|
||||
|
||||
@@ -3240,7 +3236,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(completions[0].new_text, "component");
|
||||
assert_eq!(
|
||||
completions[0].replace_range.to_offset(&snapshot),
|
||||
completions[0].old_range.to_offset(&snapshot),
|
||||
text.len() - 4..text.len() - 1
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,17 +12,13 @@ use anyhow::Result;
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
use gpui::{App, AppContext as _, Entity, SharedString, Task};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
ContextProvider, File, Language, LanguageToolchainStore, Location,
|
||||
language_settings::language_settings,
|
||||
};
|
||||
use lsp::{LanguageServerId, LanguageServerName};
|
||||
use language::{ContextProvider, File, Language, LanguageToolchainStore, Location};
|
||||
use settings::{InvalidSettingsError, TaskKind, parse_json_with_comments};
|
||||
use task::{
|
||||
DebugTaskDefinition, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
|
||||
TaskVariables, VariableName,
|
||||
};
|
||||
use text::{BufferId, Point, ToPoint};
|
||||
use text::{Point, ToPoint};
|
||||
use util::{NumericPrefixWithSuffix, ResultExt as _, paths::PathExt as _, post_inc};
|
||||
use worktree::WorktreeId;
|
||||
|
||||
@@ -59,8 +55,6 @@ pub enum TaskSourceKind {
|
||||
},
|
||||
/// Languages-specific tasks coming from extensions.
|
||||
Language { name: SharedString },
|
||||
/// Language-specific tasks coming from LSP servers.
|
||||
Lsp(LanguageServerId),
|
||||
}
|
||||
|
||||
/// A collection of task contexts, derived from the current state of the workspace.
|
||||
@@ -74,8 +68,6 @@ pub struct TaskContexts {
|
||||
pub active_worktree_context: Option<(WorktreeId, TaskContext)>,
|
||||
/// If there are multiple worktrees in the workspace, all non-active ones are included here.
|
||||
pub other_worktree_contexts: Vec<(WorktreeId, TaskContext)>,
|
||||
pub lsp_task_sources: HashMap<LanguageServerName, Vec<BufferId>>,
|
||||
pub latest_selection: Option<text::Anchor>,
|
||||
}
|
||||
|
||||
impl TaskContexts {
|
||||
@@ -112,19 +104,18 @@ impl TaskContexts {
|
||||
impl TaskSourceKind {
|
||||
pub fn to_id_base(&self) -> String {
|
||||
match self {
|
||||
Self::UserInput => "oneshot".to_string(),
|
||||
Self::AbsPath { id_base, abs_path } => {
|
||||
TaskSourceKind::UserInput => "oneshot".to_string(),
|
||||
TaskSourceKind::AbsPath { id_base, abs_path } => {
|
||||
format!("{id_base}_{}", abs_path.display())
|
||||
}
|
||||
Self::Worktree {
|
||||
TaskSourceKind::Worktree {
|
||||
id,
|
||||
id_base,
|
||||
directory_in_worktree,
|
||||
} => {
|
||||
format!("{id_base}_{id}_{}", directory_in_worktree.display())
|
||||
}
|
||||
Self::Language { name } => format!("language_{name}"),
|
||||
Self::Lsp(server_id) => format!("lsp_{server_id}"),
|
||||
TaskSourceKind::Language { name } => format!("language_{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,11 +156,6 @@ impl Inventory {
|
||||
});
|
||||
let global_tasks = self.global_templates_from_settings();
|
||||
let language_tasks = language
|
||||
.filter(|language| {
|
||||
language_settings(Some(language.name()), file.as_ref(), cx)
|
||||
.tasks
|
||||
.enabled
|
||||
})
|
||||
.and_then(|language| language.context_provider()?.associated_tasks(file, cx))
|
||||
.into_iter()
|
||||
.flat_map(|tasks| tasks.0.into_iter())
|
||||
@@ -185,10 +171,10 @@ impl Inventory {
|
||||
/// Joins the new resolutions with the resolved tasks that were used (spawned) before,
|
||||
/// orders them so that the most recently used come first, all equally used ones are ordered so that the most specific tasks come first.
|
||||
/// Deduplicates the tasks by their labels and context and splits the ordered list into two: used tasks and the rest, newly resolved tasks.
|
||||
pub fn used_and_current_resolved_tasks<'a>(
|
||||
&'a self,
|
||||
task_contexts: &'a TaskContexts,
|
||||
cx: &'a App,
|
||||
pub fn used_and_current_resolved_tasks(
|
||||
&self,
|
||||
task_contexts: &TaskContexts,
|
||||
cx: &App,
|
||||
) -> (
|
||||
Vec<(TaskSourceKind, ResolvedTask)>,
|
||||
Vec<(TaskSourceKind, ResolvedTask)>,
|
||||
@@ -241,13 +227,7 @@ impl Inventory {
|
||||
|
||||
let not_used_score = post_inc(&mut lru_score);
|
||||
let global_tasks = self.global_templates_from_settings();
|
||||
|
||||
let language_tasks = language
|
||||
.filter(|language| {
|
||||
language_settings(Some(language.name()), file.as_ref(), cx)
|
||||
.tasks
|
||||
.enabled
|
||||
})
|
||||
.and_then(|language| language.context_provider()?.associated_tasks(file, cx))
|
||||
.into_iter()
|
||||
.flat_map(|tasks| tasks.0.into_iter())
|
||||
@@ -495,7 +475,6 @@ fn task_lru_comparator(
|
||||
|
||||
fn task_source_kind_preference(kind: &TaskSourceKind) -> u32 {
|
||||
match kind {
|
||||
TaskSourceKind::Lsp(..) => 0,
|
||||
TaskSourceKind::Language { .. } => 1,
|
||||
TaskSourceKind::UserInput => 2,
|
||||
TaskSourceKind::Worktree { .. } => 3,
|
||||
@@ -719,7 +698,7 @@ mod tests {
|
||||
async fn test_task_list_sorting(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let inventory = cx.update(Inventory::new);
|
||||
let initial_tasks = resolved_task_names(&inventory, None, cx);
|
||||
let initial_tasks = resolved_task_names(&inventory, None, cx).await;
|
||||
assert!(
|
||||
initial_tasks.is_empty(),
|
||||
"No tasks expected for empty inventory, but got {initial_tasks:?}"
|
||||
@@ -753,7 +732,7 @@ mod tests {
|
||||
&expected_initial_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
&expected_initial_state,
|
||||
"Tasks with equal amount of usages should be sorted alphanumerically"
|
||||
);
|
||||
@@ -764,7 +743,7 @@ mod tests {
|
||||
&expected_initial_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
vec![
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
@@ -782,7 +761,7 @@ mod tests {
|
||||
&expected_initial_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
vec![
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
@@ -818,7 +797,7 @@ mod tests {
|
||||
&expected_updated_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
vec![
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
@@ -835,7 +814,7 @@ mod tests {
|
||||
&expected_updated_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
vec![
|
||||
"11_hello".to_string(),
|
||||
"3_task".to_string(),
|
||||
@@ -1008,21 +987,21 @@ mod tests {
|
||||
TaskStore::init(None);
|
||||
}
|
||||
|
||||
fn resolved_task_names(
|
||||
async fn resolved_task_names(
|
||||
inventory: &Entity<Inventory>,
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let (used, current) = inventory.update(cx, |inventory, cx| {
|
||||
let mut task_contexts = TaskContexts::default();
|
||||
task_contexts.active_worktree_context =
|
||||
worktree.map(|worktree| (worktree, TaskContext::default()));
|
||||
let (used, current) = inventory.used_and_current_resolved_tasks(&task_contexts, cx);
|
||||
used.into_iter()
|
||||
.chain(current)
|
||||
.map(|(_, task)| task.original_task().label.clone())
|
||||
.collect()
|
||||
})
|
||||
inventory.used_and_current_resolved_tasks(&task_contexts, cx)
|
||||
});
|
||||
used.into_iter()
|
||||
.chain(current)
|
||||
.map(|(_, task)| task.original_task().label.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn mock_tasks_from_names<'a>(task_names: impl Iterator<Item = &'a str> + 'a) -> String {
|
||||
@@ -1045,17 +1024,17 @@ mod tests {
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<(TaskSourceKind, String)> {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let (used, current) = inventory.update(cx, |inventory, cx| {
|
||||
let mut task_contexts = TaskContexts::default();
|
||||
task_contexts.active_worktree_context =
|
||||
worktree.map(|worktree| (worktree, TaskContext::default()));
|
||||
let (used, current) = inventory.used_and_current_resolved_tasks(&task_contexts, cx);
|
||||
let mut all = used;
|
||||
all.extend(current);
|
||||
all.into_iter()
|
||||
.map(|(source_kind, task)| (source_kind, task.resolved_label))
|
||||
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
|
||||
.collect()
|
||||
})
|
||||
inventory.used_and_current_resolved_tasks(&task_contexts, cx)
|
||||
});
|
||||
let mut all = used;
|
||||
all.extend(current);
|
||||
all.into_iter()
|
||||
.map(|(source_kind, task)| (source_kind, task.resolved_label))
|
||||
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,8 +198,8 @@ message ApplyCompletionAdditionalEditsResponse {
|
||||
}
|
||||
|
||||
message Completion {
|
||||
Anchor old_replace_start = 1;
|
||||
Anchor old_replace_end = 2;
|
||||
Anchor old_start = 1;
|
||||
Anchor old_end = 2;
|
||||
string new_text = 3;
|
||||
uint64 server_id = 4;
|
||||
bytes lsp_completion = 5;
|
||||
@@ -208,8 +208,6 @@ message Completion {
|
||||
optional bytes lsp_defaults = 8;
|
||||
optional Anchor buffer_word_start = 9;
|
||||
optional Anchor buffer_word_end = 10;
|
||||
Anchor old_insert_start = 11;
|
||||
Anchor old_insert_end = 12;
|
||||
|
||||
enum Source {
|
||||
Lsp = 0;
|
||||
@@ -430,12 +428,10 @@ message ResolveCompletionDocumentation {
|
||||
message ResolveCompletionDocumentationResponse {
|
||||
string documentation = 1;
|
||||
bool documentation_is_markdown = 2;
|
||||
Anchor old_replace_start = 3;
|
||||
Anchor old_replace_end = 4;
|
||||
Anchor old_start = 3;
|
||||
Anchor old_end = 4;
|
||||
string new_text = 5;
|
||||
bytes lsp_completion = 6;
|
||||
Anchor old_insert_start = 7;
|
||||
Anchor old_insert_end = 8;
|
||||
}
|
||||
|
||||
message ResolveInlayHint {
|
||||
@@ -703,18 +699,3 @@ message LanguageServerIdForName {
|
||||
message LanguageServerIdForNameResponse {
|
||||
optional uint64 server_id = 1;
|
||||
}
|
||||
|
||||
message LspExtRunnables {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
optional Anchor position = 3;
|
||||
}
|
||||
|
||||
message LspExtRunnablesResponse {
|
||||
repeated LspRunnable runnables = 1;
|
||||
}
|
||||
|
||||
message LspRunnable {
|
||||
bytes task_template = 1;
|
||||
optional LocationLink location = 2;
|
||||
}
|
||||
|
||||
@@ -372,15 +372,12 @@ message Envelope {
|
||||
GetDocumentSymbolsResponse get_document_symbols_response = 331;
|
||||
|
||||
LanguageServerIdForName language_server_id_for_name = 332;
|
||||
LanguageServerIdForNameResponse language_server_id_for_name_response = 333;
|
||||
LanguageServerIdForNameResponse language_server_id_for_name_response = 333; // current max
|
||||
|
||||
LoadCommitDiff load_commit_diff = 334;
|
||||
LoadCommitDiffResponse load_commit_diff_response = 335;
|
||||
|
||||
StopLanguageServers stop_language_servers = 336;
|
||||
|
||||
LspExtRunnables lsp_ext_runnables = 337;
|
||||
LspExtRunnablesResponse lsp_ext_runnables_response = 338; // current max
|
||||
StopLanguageServers stop_language_servers = 336; // current max
|
||||
}
|
||||
|
||||
reserved 87 to 88;
|
||||
|
||||
@@ -171,8 +171,6 @@ messages!(
|
||||
(LspExtExpandMacroResponse, Background),
|
||||
(LspExtOpenDocs, Background),
|
||||
(LspExtOpenDocsResponse, Background),
|
||||
(LspExtRunnables, Background),
|
||||
(LspExtRunnablesResponse, Background),
|
||||
(LspExtSwitchSourceHeader, Background),
|
||||
(LspExtSwitchSourceHeaderResponse, Background),
|
||||
(MarkNotificationRead, Foreground),
|
||||
@@ -416,7 +414,6 @@ request_messages!(
|
||||
(LanguageServerIdForName, LanguageServerIdForNameResponse),
|
||||
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
||||
(LspExtOpenDocs, LspExtOpenDocsResponse),
|
||||
(LspExtRunnables, LspExtRunnablesResponse),
|
||||
(SetRoomParticipantRole, Ack),
|
||||
(BlameBuffer, BlameBufferResponse),
|
||||
(RejoinRemoteProjects, RejoinRemoteProjectsResponse),
|
||||
@@ -540,7 +537,6 @@ entity_messages!(
|
||||
UpdateWorktreeSettings,
|
||||
LspExtExpandMacro,
|
||||
LspExtOpenDocs,
|
||||
LspExtRunnables,
|
||||
AdvertiseContexts,
|
||||
OpenContext,
|
||||
CreateContext,
|
||||
|
||||
@@ -18,7 +18,9 @@ pub enum ComponentStory {
|
||||
ContextMenu,
|
||||
Cursor,
|
||||
DefaultColors,
|
||||
Disclosure,
|
||||
Focus,
|
||||
Icon,
|
||||
IconButton,
|
||||
Keybinding,
|
||||
List,
|
||||
@@ -33,6 +35,7 @@ pub enum ComponentStory {
|
||||
ToggleButton,
|
||||
ViewportUnits,
|
||||
WithRemSize,
|
||||
Vector,
|
||||
}
|
||||
|
||||
impl ComponentStory {
|
||||
@@ -48,7 +51,9 @@ impl ComponentStory {
|
||||
Self::ContextMenu => cx.new(|_| ui::ContextMenuStory).into(),
|
||||
Self::Cursor => cx.new(|_| crate::stories::CursorStory).into(),
|
||||
Self::DefaultColors => DefaultColorsStory::model(cx).into(),
|
||||
Self::Disclosure => cx.new(|_| ui::DisclosureStory).into(),
|
||||
Self::Focus => FocusStory::model(window, cx).into(),
|
||||
Self::Icon => cx.new(|_| ui::IconStory).into(),
|
||||
Self::IconButton => cx.new(|_| ui::IconButtonStory).into(),
|
||||
Self::Keybinding => cx.new(|_| ui::KeybindingStory).into(),
|
||||
Self::List => cx.new(|_| ui::ListStory).into(),
|
||||
@@ -63,6 +68,7 @@ impl ComponentStory {
|
||||
Self::ToggleButton => cx.new(|_| ui::ToggleButtonStory).into(),
|
||||
Self::ViewportUnits => cx.new(|_| crate::stories::ViewportUnitsStory).into(),
|
||||
Self::WithRemSize => cx.new(|_| crate::stories::WithRemSizeStory).into(),
|
||||
Self::Vector => cx.new(|_| ui::VectorStory).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ impl PickerDelegate for TabSwitcherDelegate {
|
||||
IconButton::new("close_tab", IconName::Close)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(indicator_color)
|
||||
.tooltip(Tooltip::for_action_title("Close", &CloseSelectedItem))
|
||||
.tooltip(Tooltip::text("Close"))
|
||||
.on_click(cx.listener(move |picker, _, window, cx| {
|
||||
cx.stop_propagation();
|
||||
picker.delegate.close_item_at(ix, window, cx);
|
||||
|
||||
@@ -13,13 +13,11 @@ path = "src/tasks_ui.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
debugger_ui.workspace = true
|
||||
editor.workspace = true
|
||||
file_icons.workspace = true
|
||||
fuzzy.workspace = true
|
||||
feature_flags.workspace = true
|
||||
itertools.workspace = true
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
picker.workspace = true
|
||||
|
||||
@@ -7,7 +7,6 @@ use gpui::{
|
||||
Focusable, InteractiveElement, ParentElement, Render, SharedString, Styled, Subscription, Task,
|
||||
WeakEntity, Window, rems,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
||||
use project::{TaskSourceKind, task_store::TaskStore};
|
||||
use task::{
|
||||
@@ -222,66 +221,42 @@ impl PickerDelegate for TasksModalDelegate {
|
||||
cx: &mut Context<picker::Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let task_type = self.task_modal_type.clone();
|
||||
let candidates = match &self.candidates {
|
||||
Some(candidates) => Task::ready(string_match_candidates(candidates, task_type)),
|
||||
None => {
|
||||
if let Some(task_inventory) = self.task_store.read(cx).task_inventory().cloned() {
|
||||
let (used, current) = task_inventory
|
||||
.read(cx)
|
||||
.used_and_current_resolved_tasks(&self.task_contexts, cx);
|
||||
let workspace = self.workspace.clone();
|
||||
let lsp_task_sources = self.task_contexts.lsp_task_sources.clone();
|
||||
let task_position = self.task_contexts.latest_selection;
|
||||
|
||||
cx.spawn(async move |picker, cx| {
|
||||
let Ok(lsp_tasks) = workspace.update(cx, |workspace, cx| {
|
||||
editor::lsp_tasks(
|
||||
workspace.project().clone(),
|
||||
&lsp_task_sources,
|
||||
task_position,
|
||||
cx,
|
||||
)
|
||||
}) else {
|
||||
cx.spawn_in(window, async move |picker, cx| {
|
||||
let Some(candidates) = picker
|
||||
.update(cx, |picker, cx| match &mut picker.delegate.candidates {
|
||||
Some(candidates) => string_match_candidates(candidates.iter(), task_type),
|
||||
None => {
|
||||
let Some(task_inventory) = picker
|
||||
.delegate
|
||||
.task_store
|
||||
.read(cx)
|
||||
.task_inventory()
|
||||
.cloned()
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let lsp_tasks = lsp_tasks.await;
|
||||
picker
|
||||
.update(cx, |picker, _| {
|
||||
picker.delegate.last_used_candidate_index = if used.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(used.len() - 1)
|
||||
};
|
||||
let (used, current) = task_inventory
|
||||
.read(cx)
|
||||
.used_and_current_resolved_tasks(&picker.delegate.task_contexts, cx);
|
||||
picker.delegate.last_used_candidate_index = if used.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(used.len() - 1)
|
||||
};
|
||||
|
||||
let mut new_candidates = used;
|
||||
new_candidates.extend(lsp_tasks.into_iter().flat_map(
|
||||
|(kind, tasks_with_locations)| {
|
||||
tasks_with_locations
|
||||
.into_iter()
|
||||
.sorted_by_key(|(location, task)| {
|
||||
(location.is_none(), task.resolved_label.clone())
|
||||
})
|
||||
.map(move |(_, task)| (kind.clone(), task))
|
||||
},
|
||||
));
|
||||
new_candidates.extend(current);
|
||||
let match_candidates =
|
||||
string_match_candidates(&new_candidates, task_type);
|
||||
let _ = picker.delegate.candidates.insert(new_candidates);
|
||||
match_candidates
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
} else {
|
||||
Task::ready(Vec::new())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cx.spawn_in(window, async move |picker, cx| {
|
||||
let candidates = candidates.await;
|
||||
let mut new_candidates = used;
|
||||
new_candidates.extend(current);
|
||||
let match_candidates =
|
||||
string_match_candidates(new_candidates.iter(), task_type);
|
||||
let _ = picker.delegate.candidates.insert(new_candidates);
|
||||
match_candidates
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let matches = fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
@@ -451,7 +426,6 @@ impl PickerDelegate for TasksModalDelegate {
|
||||
color: Color::Default,
|
||||
};
|
||||
let icon = match source_kind {
|
||||
TaskSourceKind::Lsp(..) => Some(Icon::new(IconName::Bolt)),
|
||||
TaskSourceKind::UserInput => Some(Icon::new(IconName::Terminal)),
|
||||
TaskSourceKind::AbsPath { .. } => Some(Icon::new(IconName::Settings)),
|
||||
TaskSourceKind::Worktree { .. } => Some(Icon::new(IconName::FileTree)),
|
||||
@@ -723,11 +697,10 @@ impl PickerDelegate for TasksModalDelegate {
|
||||
}
|
||||
|
||||
fn string_match_candidates<'a>(
|
||||
candidates: impl IntoIterator<Item = &'a (TaskSourceKind, ResolvedTask)> + 'a,
|
||||
candidates: impl Iterator<Item = &'a (TaskSourceKind, ResolvedTask)> + 'a,
|
||||
task_modal_type: TaskModal,
|
||||
) -> Vec<StringMatchCandidate> {
|
||||
candidates
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (_, candidate))| match candidate.task_type() {
|
||||
TaskType::Script => task_modal_type == TaskModal::ScriptModal,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
use collections::HashMap;
|
||||
use debugger_ui::Start;
|
||||
use editor::Editor;
|
||||
use feature_flags::{Debugger, FeatureFlagViewExt};
|
||||
@@ -313,17 +313,6 @@ fn task_contexts(workspace: &Workspace, window: &mut Window, cx: &mut App) -> Ta
|
||||
})
|
||||
});
|
||||
|
||||
let lsp_task_sources = active_editor
|
||||
.as_ref()
|
||||
.map(|active_editor| active_editor.update(cx, |editor, cx| editor.lsp_task_sources(cx)))
|
||||
.unwrap_or_default();
|
||||
|
||||
let latest_selection = active_editor.as_ref().map(|active_editor| {
|
||||
active_editor.update(cx, |editor, _| {
|
||||
editor.selections.newest_anchor().head().text_anchor
|
||||
})
|
||||
});
|
||||
|
||||
let mut worktree_abs_paths = workspace
|
||||
.worktrees(cx)
|
||||
.filter(|worktree| is_visible_directory(worktree, cx))
|
||||
@@ -336,9 +325,6 @@ fn task_contexts(workspace: &Workspace, window: &mut Window, cx: &mut App) -> Ta
|
||||
cx.background_spawn(async move {
|
||||
let mut task_contexts = TaskContexts::default();
|
||||
|
||||
task_contexts.lsp_task_sources = lsp_task_sources;
|
||||
task_contexts.latest_selection = latest_selection;
|
||||
|
||||
if let Some(editor_context_task) = editor_context_task {
|
||||
if let Some(editor_context) = editor_context_task.await {
|
||||
task_contexts.active_item_context =
|
||||
|
||||
@@ -2231,7 +2231,6 @@ impl BufferSnapshot {
|
||||
} else if *anchor == Anchor::MAX {
|
||||
self.visible_text.len()
|
||||
} else {
|
||||
debug_assert!(anchor.buffer_id == Some(self.remote_id));
|
||||
let anchor_key = InsertionFragmentKey {
|
||||
timestamp: anchor.timestamp,
|
||||
split_offset: anchor.offset,
|
||||
|
||||
@@ -28,7 +28,6 @@ strum.workspace = true
|
||||
theme.workspace = true
|
||||
ui_macros.workspace = true
|
||||
util.workspace = true
|
||||
documented = "0.9.1"
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
pub use component::{
|
||||
Component, ComponentScope, example_group, example_group_with_title, single_example,
|
||||
};
|
||||
pub use documented::Documented;
|
||||
pub use ui_macros::RegisterComponent;
|
||||
@@ -73,5 +73,7 @@ pub use table::*;
|
||||
pub use toggle::*;
|
||||
pub use tooltip::*;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use image::story::*;
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use documented::Documented;
|
||||
use gpui::{AnyElement, Hsla, ImageSource, Img, IntoElement, Styled, img};
|
||||
|
||||
/// An element that renders a user avatar with customizable appearance options.
|
||||
@@ -15,7 +14,7 @@ use gpui::{AnyElement, Hsla, ImageSource, Img, IntoElement, Styled, img};
|
||||
/// .grayscale(true)
|
||||
/// .border_color(gpui::red());
|
||||
/// ```
|
||||
#[derive(IntoElement, Documented, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
pub struct Avatar {
|
||||
image: Img,
|
||||
size: Option<AbsoluteLength>,
|
||||
@@ -220,102 +219,84 @@ impl RenderOnce for AvatarAvailabilityIndicator {
|
||||
}
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl Component for Avatar {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Collaboration
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(Avatar::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
impl ComponentPreview for Avatar {
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
let example_avatar = "https://avatars.githubusercontent.com/u/1714999?v=4";
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Avatar::new(example_avatar).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Small",
|
||||
Avatar::new(example_avatar).size(px(24.)).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
Avatar::new(example_avatar).size(px(48.)).into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Avatar::new(example_avatar).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Grayscale",
|
||||
Avatar::new(example_avatar)
|
||||
.grayscale(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Border",
|
||||
Avatar::new(example_avatar)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Audio Status",
|
||||
vec![
|
||||
single_example(
|
||||
"Muted",
|
||||
Avatar::new(example_avatar)
|
||||
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Deafened",
|
||||
Avatar::new(example_avatar)
|
||||
.indicator(AvatarAudioStatusIndicator::new(
|
||||
AudioStatus::Deafened,
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Availability",
|
||||
vec![
|
||||
single_example(
|
||||
"Free",
|
||||
Avatar::new(example_avatar)
|
||||
.indicator(AvatarAvailabilityIndicator::new(
|
||||
CollaboratorAvailability::Free,
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Busy",
|
||||
Avatar::new(example_avatar)
|
||||
.indicator(AvatarAvailabilityIndicator::new(
|
||||
CollaboratorAvailability::Busy,
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example("Default", Avatar::new(example_avatar).into_any_element()),
|
||||
single_example(
|
||||
"Small",
|
||||
Avatar::new(example_avatar).size(px(24.)).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
Avatar::new(example_avatar).size(px(48.)).into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example("Default", Avatar::new(example_avatar).into_any_element()),
|
||||
single_example(
|
||||
"Grayscale",
|
||||
Avatar::new(example_avatar)
|
||||
.grayscale(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Border",
|
||||
Avatar::new(example_avatar)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Audio Status",
|
||||
vec![
|
||||
single_example(
|
||||
"Muted",
|
||||
Avatar::new(example_avatar)
|
||||
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Deafened",
|
||||
Avatar::new(example_avatar)
|
||||
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Availability",
|
||||
vec![
|
||||
single_example(
|
||||
"Free",
|
||||
Avatar::new(example_avatar)
|
||||
.indicator(AvatarAvailabilityIndicator::new(
|
||||
CollaboratorAvailability::Free,
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Busy",
|
||||
Avatar::new(example_avatar)
|
||||
.indicator(AvatarAvailabilityIndicator::new(
|
||||
CollaboratorAvailability::Busy,
|
||||
))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ pub enum Severity {
|
||||
/// .icon_position(IconPosition::End),
|
||||
/// )
|
||||
/// ```
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Notification")]
|
||||
pub struct Banner {
|
||||
severity: Severity,
|
||||
children: Option<AnyElement>,
|
||||
@@ -136,12 +137,8 @@ impl RenderOnce for Banner {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Banner {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Notification
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
impl ComponentPreview for Banner {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
let severity_examples = vec![
|
||||
single_example(
|
||||
"Default",
|
||||
@@ -188,10 +185,8 @@ impl Component for Banner {
|
||||
),
|
||||
];
|
||||
|
||||
Some(
|
||||
example_group(severity_examples)
|
||||
.vertical()
|
||||
.into_any_element(),
|
||||
)
|
||||
example_group(severity_examples)
|
||||
.vertical()
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::component_prelude::*;
|
||||
use component::{ComponentPreview, example_group_with_title, single_example};
|
||||
use gpui::{AnyElement, AnyView, DefiniteLength};
|
||||
use ui_macros::RegisterComponent;
|
||||
use ui_macros::IntoComponent;
|
||||
|
||||
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label};
|
||||
use crate::{
|
||||
@@ -77,7 +77,8 @@ use super::button_icon::ButtonIcon;
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
#[derive(IntoElement, Documented, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Input")]
|
||||
pub struct Button {
|
||||
base: ButtonLike,
|
||||
label: SharedString,
|
||||
@@ -465,135 +466,121 @@ impl RenderOnce for Button {
|
||||
}
|
||||
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl Component for Button {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
"ButtonA"
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("A button triggers an event or action.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Button Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Button::new("default", "Default").into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Filled",
|
||||
Button::new("filled", "Filled")
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Subtle",
|
||||
Button::new("outline", "Subtle")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Tinted",
|
||||
Button::new("tinted_accent_style", "Accent")
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Transparent",
|
||||
Button::new("transparent", "Transparent")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Tint Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Accent",
|
||||
Button::new("tinted_accent", "Accent")
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Error",
|
||||
Button::new("tinted_negative", "Error")
|
||||
.style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
Button::new("tinted_warning", "Warning")
|
||||
.style(ButtonStyle::Tinted(TintColor::Warning))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Success",
|
||||
Button::new("tinted_positive", "Success")
|
||||
.style(ButtonStyle::Tinted(TintColor::Success))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Special States",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Button::new("default_state", "Default").into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Disabled",
|
||||
Button::new("disabled", "Disabled")
|
||||
.disabled(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
Button::new("selected", "Selected")
|
||||
.toggle_state(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Buttons with Icons",
|
||||
vec![
|
||||
single_example(
|
||||
"Icon Start",
|
||||
Button::new("icon_start", "Icon Start")
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::Start)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Icon End",
|
||||
Button::new("icon_end", "Icon End")
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::End)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Icon Color",
|
||||
Button::new("icon_color", "Icon Color")
|
||||
.icon(IconName::Check)
|
||||
.icon_color(Color::Accent)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
impl ComponentPreview for Button {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Button Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Button::new("default", "Default").into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Filled",
|
||||
Button::new("filled", "Filled")
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Subtle",
|
||||
Button::new("outline", "Subtle")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Tinted",
|
||||
Button::new("tinted_accent_style", "Accent")
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Transparent",
|
||||
Button::new("transparent", "Transparent")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Tint Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Accent",
|
||||
Button::new("tinted_accent", "Accent")
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Error",
|
||||
Button::new("tinted_negative", "Error")
|
||||
.style(ButtonStyle::Tinted(TintColor::Error))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
Button::new("tinted_warning", "Warning")
|
||||
.style(ButtonStyle::Tinted(TintColor::Warning))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Success",
|
||||
Button::new("tinted_positive", "Success")
|
||||
.style(ButtonStyle::Tinted(TintColor::Success))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Special States",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Button::new("default_state", "Default").into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Disabled",
|
||||
Button::new("disabled", "Disabled")
|
||||
.disabled(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
Button::new("selected", "Selected")
|
||||
.toggle_state(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Buttons with Icons",
|
||||
vec![
|
||||
single_example(
|
||||
"Icon Start",
|
||||
Button::new("icon_start", "Icon Start")
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::Start)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Icon End",
|
||||
Button::new("icon_end", "Icon End")
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::End)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Icon Color",
|
||||
Button::new("icon_color", "Icon Color")
|
||||
.icon(IconName::Check)
|
||||
.icon_color(Color::Accent)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use gpui::Hsla;
|
||||
///
|
||||
/// Can be used as either an icon alongside a label, like in [`Button`](crate::Button),
|
||||
/// or as a standalone icon, like in [`IconButton`](crate::IconButton).
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub(super) struct ButtonIcon {
|
||||
icon: IconName,
|
||||
size: IconSize,
|
||||
@@ -39,6 +39,7 @@ impl ButtonIcon {
|
||||
if let Some(size) = size.into() {
|
||||
self.size = size;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
@@ -46,6 +47,7 @@ impl ButtonIcon {
|
||||
if let Some(color) = color.into() {
|
||||
self.color = color;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
@@ -118,82 +120,3 @@ impl RenderOnce for ButtonIcon {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ButtonIcon {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"ButtonIcon"
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("An icon component specifically designed for use within buttons.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic Usage",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
ButtonIcon::new(IconName::Star).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Custom Size",
|
||||
ButtonIcon::new(IconName::Star)
|
||||
.size(IconSize::Medium)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Custom Color",
|
||||
ButtonIcon::new(IconName::Star)
|
||||
.color(Color::Accent)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"States",
|
||||
vec![
|
||||
single_example(
|
||||
"Selected",
|
||||
ButtonIcon::new(IconName::Star)
|
||||
.toggle_state(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Disabled",
|
||||
ButtonIcon::new(IconName::Star)
|
||||
.disabled(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"With Indicator",
|
||||
vec![
|
||||
single_example(
|
||||
"Default Indicator",
|
||||
ButtonIcon::new(IconName::Star)
|
||||
.indicator(Indicator::dot())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Custom Indicator",
|
||||
ButtonIcon::new(IconName::Star)
|
||||
.indicator(Indicator::dot().color(Color::Error))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use documented::Documented;
|
||||
use gpui::{
|
||||
AnyElement, AnyView, ClickEvent, CursorStyle, DefiniteLength, Hsla, MouseButton,
|
||||
MouseDownEvent, MouseUpEvent, Rems, relative, transparent_black,
|
||||
};
|
||||
use gpui::{AnyElement, AnyView, ClickEvent, Hsla, Rems, transparent_black};
|
||||
use gpui::{CursorStyle, DefiniteLength, MouseButton, MouseDownEvent, MouseUpEvent, relative};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{DynamicSpacing, ElevationIndex, prelude::*};
|
||||
@@ -346,7 +343,7 @@ impl ButtonSize {
|
||||
/// unconstrained and may make the UI feel less consistent.
|
||||
///
|
||||
/// This is also used to build the prebuilt buttons.
|
||||
#[derive(IntoElement, Documented, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct ButtonLike {
|
||||
pub(super) base: Div,
|
||||
id: ElementId,
|
||||
@@ -588,99 +585,3 @@ impl RenderOnce for ButtonLike {
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ButtonLike {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
// ButtonLike should be at the bottom of the button list
|
||||
"ButtonZ"
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(ButtonLike::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group(vec![
|
||||
single_example(
|
||||
"Default",
|
||||
ButtonLike::new("default")
|
||||
.child(Label::new("Default"))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Filled",
|
||||
ButtonLike::new("filled")
|
||||
.style(ButtonStyle::Filled)
|
||||
.child(Label::new("Filled"))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Subtle",
|
||||
ButtonLike::new("outline")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(Label::new("Subtle"))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Tinted",
|
||||
ButtonLike::new("tinted_accent_style")
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.child(Label::new("Accent"))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Transparent",
|
||||
ButtonLike::new("transparent")
|
||||
.style(ButtonStyle::Transparent)
|
||||
.child(Label::new("Transparent"))
|
||||
.into_any_element(),
|
||||
),
|
||||
]),
|
||||
example_group_with_title(
|
||||
"Button Group Constructors",
|
||||
vec![
|
||||
single_example(
|
||||
"Left Rounded",
|
||||
ButtonLike::new_rounded_left("left_rounded")
|
||||
.child(Label::new("Left Rounded"))
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Right Rounded",
|
||||
ButtonLike::new_rounded_right("right_rounded")
|
||||
.child(Label::new("Right Rounded"))
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Button Group",
|
||||
h_flex()
|
||||
.gap_px()
|
||||
.child(
|
||||
ButtonLike::new_rounded_left("bg_left")
|
||||
.child(Label::new("Left"))
|
||||
.style(ButtonStyle::Filled),
|
||||
)
|
||||
.child(
|
||||
ButtonLike::new_rounded_right("bg_right")
|
||||
.child(Label::new("Right"))
|
||||
.style(ButtonStyle::Filled),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ pub enum IconButtonShape {
|
||||
Wide,
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Input")]
|
||||
pub struct IconButton {
|
||||
base: ButtonLike,
|
||||
shape: IconButtonShape,
|
||||
@@ -209,169 +210,159 @@ impl RenderOnce for IconButton {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for IconButton {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
"ButtonB"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Icon Button Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
IconButton::new("default", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Filled",
|
||||
IconButton::new("filled", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Subtle",
|
||||
IconButton::new("subtle", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Tinted",
|
||||
IconButton::new("tinted", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Transparent",
|
||||
IconButton::new("transparent", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Icon Button Shapes",
|
||||
vec![
|
||||
single_example(
|
||||
"Square",
|
||||
IconButton::new("square", IconName::Check)
|
||||
.shape(IconButtonShape::Square)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Wide",
|
||||
IconButton::new("wide", IconName::Check)
|
||||
.shape(IconButtonShape::Wide)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Icon Button Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"XSmall",
|
||||
IconButton::new("xsmall", IconName::Check)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Small",
|
||||
IconButton::new("small", IconName::Check)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Medium",
|
||||
IconButton::new("medium", IconName::Check)
|
||||
.icon_size(IconSize::Medium)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"XLarge",
|
||||
IconButton::new("xlarge", IconName::Check)
|
||||
.icon_size(IconSize::XLarge)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Special States",
|
||||
vec![
|
||||
single_example(
|
||||
"Disabled",
|
||||
IconButton::new("disabled", IconName::Check)
|
||||
.disabled(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
IconButton::new("selected", IconName::Check)
|
||||
.toggle_state(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Indicator",
|
||||
IconButton::new("indicator", IconName::Check)
|
||||
.indicator(Indicator::dot().color(Color::Success))
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Custom Colors",
|
||||
vec![
|
||||
single_example(
|
||||
"Custom Icon Color",
|
||||
IconButton::new("custom_color", IconName::Check)
|
||||
.icon_color(Color::Accent)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Alpha",
|
||||
IconButton::new("alpha", IconName::Check)
|
||||
.alpha(0.5)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
impl ComponentPreview for IconButton {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Icon Button Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
IconButton::new("default", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Filled",
|
||||
IconButton::new("filled", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Subtle",
|
||||
IconButton::new("subtle", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Tinted",
|
||||
IconButton::new("tinted", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Transparent",
|
||||
IconButton::new("transparent", IconName::Check)
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Icon Button Shapes",
|
||||
vec![
|
||||
single_example(
|
||||
"Square",
|
||||
IconButton::new("square", IconName::Check)
|
||||
.shape(IconButtonShape::Square)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Wide",
|
||||
IconButton::new("wide", IconName::Check)
|
||||
.shape(IconButtonShape::Wide)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Icon Button Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"Small",
|
||||
IconButton::new("small", IconName::Check)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Small",
|
||||
IconButton::new("small", IconName::Check)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Medium",
|
||||
IconButton::new("medium", IconName::Check)
|
||||
.icon_size(IconSize::Medium)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"XLarge",
|
||||
IconButton::new("xlarge", IconName::Check)
|
||||
.icon_size(IconSize::XLarge)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Special States",
|
||||
vec![
|
||||
single_example(
|
||||
"Disabled",
|
||||
IconButton::new("disabled", IconName::Check)
|
||||
.disabled(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
IconButton::new("selected", IconName::Check)
|
||||
.toggle_state(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Indicator",
|
||||
IconButton::new("indicator", IconName::Check)
|
||||
.indicator(Indicator::dot().color(Color::Success))
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Custom Colors",
|
||||
vec![
|
||||
single_example(
|
||||
"Custom Icon Color",
|
||||
IconButton::new("custom_color", IconName::Check)
|
||||
.icon_color(Color::Accent)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Alpha",
|
||||
IconButton::new("alpha", IconName::Check)
|
||||
.alpha(0.5)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::Background)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ pub enum ToggleButtonPosition {
|
||||
Last,
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Input")]
|
||||
pub struct ToggleButton {
|
||||
base: ButtonLike,
|
||||
position_in_group: Option<ToggleButtonPosition>,
|
||||
@@ -154,139 +155,129 @@ impl RenderOnce for ToggleButton {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ToggleButton {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn sort_name() -> &'static str {
|
||||
"ButtonC"
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Button Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Off",
|
||||
ToggleButton::new("off", "Off")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"On",
|
||||
ToggleButton::new("on", "On")
|
||||
.layer(ElevationIndex::Background)
|
||||
.toggle_state(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Off – Disabled",
|
||||
ToggleButton::new("disabled_off", "Disabled Off")
|
||||
.layer(ElevationIndex::Background)
|
||||
.disabled(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"On – Disabled",
|
||||
ToggleButton::new("disabled_on", "Disabled On")
|
||||
.layer(ElevationIndex::Background)
|
||||
.disabled(true)
|
||||
.toggle_state(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Button Group",
|
||||
vec![
|
||||
single_example(
|
||||
"Three Buttons",
|
||||
h_flex()
|
||||
.child(
|
||||
ToggleButton::new("three_btn_first", "First")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.first()
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("three_btn_middle", "Middle")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.middle()
|
||||
.toggle_state(true)
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("three_btn_last", "Last")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.last()
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Two Buttons",
|
||||
h_flex()
|
||||
.child(
|
||||
ToggleButton::new("two_btn_first", "First")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.first()
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("two_btn_last", "Last")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.last()
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Alternate Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"None",
|
||||
ToggleButton::new("none", "None")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::None)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Compact",
|
||||
ToggleButton::new("compact", "Compact")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Compact)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
ToggleButton::new("large", "Large")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
impl ComponentPreview for ToggleButton {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Button Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Off",
|
||||
ToggleButton::new("off", "Off")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"On",
|
||||
ToggleButton::new("on", "On")
|
||||
.layer(ElevationIndex::Background)
|
||||
.toggle_state(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Off – Disabled",
|
||||
ToggleButton::new("disabled_off", "Disabled Off")
|
||||
.layer(ElevationIndex::Background)
|
||||
.disabled(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"On – Disabled",
|
||||
ToggleButton::new("disabled_on", "Disabled On")
|
||||
.layer(ElevationIndex::Background)
|
||||
.disabled(true)
|
||||
.toggle_state(true)
|
||||
.style(ButtonStyle::Filled)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Button Group",
|
||||
vec![
|
||||
single_example(
|
||||
"Three Buttons",
|
||||
h_flex()
|
||||
.child(
|
||||
ToggleButton::new("three_btn_first", "First")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.first()
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("three_btn_middle", "Middle")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.middle()
|
||||
.toggle_state(true)
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("three_btn_last", "Last")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.last()
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Two Buttons",
|
||||
h_flex()
|
||||
.child(
|
||||
ToggleButton::new("two_btn_first", "First")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.first()
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("two_btn_last", "Last")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.last()
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Alternate Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"None",
|
||||
ToggleButton::new("none", "None")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::None)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Compact",
|
||||
ToggleButton::new("compact", "Compact")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Compact)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
ToggleButton::new("large", "Large")
|
||||
.layer(ElevationIndex::Background)
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::component_prelude::*;
|
||||
use crate::prelude::*;
|
||||
use component::{ComponentPreview, example_group, single_example};
|
||||
use gpui::{AnyElement, IntoElement, ParentElement, StyleRefinement, Styled};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@@ -23,7 +23,8 @@ pub fn h_container() -> ContentGroup {
|
||||
}
|
||||
|
||||
/// A flexible container component that can hold other elements.
|
||||
#[derive(IntoElement, Documented, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Layout")]
|
||||
pub struct ContentGroup {
|
||||
base: Div,
|
||||
border: bool,
|
||||
@@ -82,56 +83,51 @@ impl RenderOnce for ContentGroup {
|
||||
this.border_1().border_color(cx.theme().colors().border)
|
||||
})
|
||||
.rounded_sm()
|
||||
.p_2()
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ContentGroup {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(ContentGroup::DOCS)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
example_group(vec![
|
||||
single_example(
|
||||
"Default",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.child(Label::new("Default ContentGroup"))
|
||||
.into_any_element(),
|
||||
).description("A contained style for laying out groups of content. Has a default background and border color."),
|
||||
single_example(
|
||||
"Without Border",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.borderless()
|
||||
.child(Label::new("Borderless ContentGroup"))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Without Fill",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.unfilled()
|
||||
.child(Label::new("Unfilled ContentGroup"))
|
||||
.into_any_element(),
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl ComponentPreview for ContentGroup {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
example_group(vec![
|
||||
single_example(
|
||||
"Default",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.child(Label::new("Default ContentBox"))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Without Border",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.borderless()
|
||||
.child(Label::new("Borderless ContentBox"))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Without Fill",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.unfilled()
|
||||
.child(Label::new("Unfilled ContentBox"))
|
||||
.into_any_element(),
|
||||
)
|
||||
.grow(),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::{ClickEvent, CursorStyle};
|
||||
|
||||
use crate::{Color, IconButton, IconButtonShape, IconName, IconSize, prelude::*};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct Disclosure {
|
||||
id: ElementId,
|
||||
is_open: bool,
|
||||
@@ -84,55 +84,3 @@ impl RenderOnce for Disclosure {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Disclosure {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Navigation
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"An interactive element used to show or hide content, typically used in expandable sections or tree-like structures.",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Disclosure States",
|
||||
vec![
|
||||
single_example(
|
||||
"Closed",
|
||||
Disclosure::new("closed", false).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Open",
|
||||
Disclosure::new("open", true).into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Interactive Example",
|
||||
vec![single_example(
|
||||
"Toggleable",
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Disclosure::new("interactive", false)
|
||||
// .on_toggle(Some(Arc::new(|_, _, cx| {
|
||||
// cx.refresh();
|
||||
// })))
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(Label::new("Click to toggle"))
|
||||
.into_any_element(),
|
||||
)],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ impl DividerColor {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct Divider {
|
||||
style: DividerStyle,
|
||||
direction: DividerDirection,
|
||||
@@ -158,90 +158,3 @@ impl Divider {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Divider {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"Visual separator used to create divisions between groups of content or sections in a layout.",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Horizontal Dividers",
|
||||
vec![
|
||||
single_example("Default", Divider::horizontal().into_any_element()),
|
||||
single_example(
|
||||
"Border Color",
|
||||
Divider::horizontal()
|
||||
.color(DividerColor::Border)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Inset",
|
||||
Divider::horizontal().inset().into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Dashed",
|
||||
Divider::horizontal_dashed().into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Vertical Dividers",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
div().h_16().child(Divider::vertical()).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Border Color",
|
||||
div()
|
||||
.h_16()
|
||||
.child(Divider::vertical().color(DividerColor::Border))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Inset",
|
||||
div()
|
||||
.h_16()
|
||||
.child(Divider::vertical().inset())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Dashed",
|
||||
div()
|
||||
.h_16()
|
||||
.child(Divider::vertical_dashed())
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Example Usage",
|
||||
vec![single_example(
|
||||
"Between Content",
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.px_4()
|
||||
.child(Label::new("Section One"))
|
||||
.child(Divider::horizontal())
|
||||
.child(Label::new("Section Two"))
|
||||
.child(Divider::horizontal_dashed())
|
||||
.child(Label::new("Section Three"))
|
||||
.into_any_element(),
|
||||
)],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ enum LabelKind {
|
||||
Element(AnyElement),
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct DropdownMenu {
|
||||
id: ElementId,
|
||||
label: LabelKind,
|
||||
@@ -72,69 +72,6 @@ impl RenderOnce for DropdownMenu {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for DropdownMenu {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"DropdownMenu"
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"A dropdown menu displays a list of actions or options. A dropdown menu is always activated by clicking a trigger (or via a keybinding).",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let menu = ContextMenu::build(window, cx, |this, _, _| {
|
||||
this.entry("Option 1", None, |_, _| {})
|
||||
.entry("Option 2", None, |_, _| {})
|
||||
.entry("Option 3", None, |_, _| {})
|
||||
.separator()
|
||||
.entry("Option 4", None, |_, _| {})
|
||||
});
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic Usage",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
DropdownMenu::new("default", "Select an option", menu.clone())
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Full Width",
|
||||
DropdownMenu::new(
|
||||
"full-width",
|
||||
"Full Width Dropdown",
|
||||
menu.clone(),
|
||||
)
|
||||
.full_width(true)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"States",
|
||||
vec![single_example(
|
||||
"Disabled",
|
||||
DropdownMenu::new("disabled", "Disabled Dropdown", menu.clone())
|
||||
.disabled(true)
|
||||
.into_any_element(),
|
||||
)],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct DropdownMenuTrigger {
|
||||
label: LabelKind,
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
use crate::component_prelude::*;
|
||||
use crate::prelude::*;
|
||||
use crate::{Avatar, prelude::*};
|
||||
use gpui::{AnyElement, StyleRefinement};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::Avatar;
|
||||
|
||||
/// An element that displays a collection of (usually) faces stacked
|
||||
/// horizontally, with the left-most face on top, visually descending
|
||||
/// from left to right.
|
||||
/// A facepile is a collection of faces stacked horizontally–
|
||||
/// always with the leftmost face on top and descending in z-index
|
||||
///
|
||||
/// Facepiles are used to display a group of people or things,
|
||||
/// such as a list of participants in a collaboration session.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ## Default
|
||||
///
|
||||
/// A default, horizontal facepile.
|
||||
///
|
||||
/// ```
|
||||
/// use ui::{Avatar, Facepile, EXAMPLE_FACES};
|
||||
///
|
||||
/// Facepile::new(
|
||||
/// EXAMPLE_FACES.iter().take(3).iter().map(|&url|
|
||||
/// Avatar::new(url).into_any_element()).collect())
|
||||
/// ```
|
||||
#[derive(IntoElement, Documented, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
pub struct Facepile {
|
||||
base: Div,
|
||||
faces: SmallVec<[AnyElement; 2]>,
|
||||
@@ -78,37 +60,27 @@ impl RenderOnce for Facepile {
|
||||
}
|
||||
}
|
||||
|
||||
pub const EXAMPLE_FACES: [&'static str; 6] = [
|
||||
"https://avatars.githubusercontent.com/u/326587?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/1789?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
||||
];
|
||||
impl ComponentPreview for Facepile {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
let faces: [&'static str; 6] = [
|
||||
"https://avatars.githubusercontent.com/u/326587?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/1789?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
||||
];
|
||||
|
||||
impl Component for Facepile {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Collaboration
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"Displays a collection of avatars or initials in a compact format. Often used to represent active collaborators or a subset of contributors.",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group_with_title(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Facepile Examples",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Facepile::new(
|
||||
EXAMPLE_FACES
|
||||
faces
|
||||
.iter()
|
||||
.map(|&url| Avatar::new(url).into_any_element())
|
||||
.collect(),
|
||||
@@ -118,7 +90,7 @@ impl Component for Facepile {
|
||||
single_example(
|
||||
"Custom Size",
|
||||
Facepile::new(
|
||||
EXAMPLE_FACES
|
||||
faces
|
||||
.iter()
|
||||
.map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
|
||||
.collect(),
|
||||
@@ -126,8 +98,19 @@ impl Component for Facepile {
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
)])
|
||||
.into_any_element(),
|
||||
)
|
||||
),
|
||||
example_group_with_title(
|
||||
"Special Cases",
|
||||
vec![
|
||||
single_example("Empty Facepile", Facepile::empty().into_any_element()),
|
||||
single_example(
|
||||
"Single Face",
|
||||
Facepile::new(vec![Avatar::new(faces[0]).into_any_element()].into())
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ impl IconSource {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
pub struct Icon {
|
||||
source: IconSource,
|
||||
color: Color,
|
||||
@@ -265,54 +265,43 @@ impl RenderOnce for IconWithIndicator {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Icon {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"A versatile icon component that supports SVG and image-based icons with customizable size, color, and transformations.",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example("Default", Icon::new(IconName::Star).into_any_element()),
|
||||
single_example(
|
||||
"Small",
|
||||
Icon::new(IconName::Star)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
Icon::new(IconName::Star)
|
||||
.size(IconSize::XLarge)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Colors",
|
||||
vec![
|
||||
single_example("Default", Icon::new(IconName::Bell).into_any_element()),
|
||||
single_example(
|
||||
"Custom Color",
|
||||
Icon::new(IconName::Bell)
|
||||
.color(Color::Error)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl ComponentPreview for Icon {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example("Default", Icon::new(IconName::Star).into_any_element()),
|
||||
single_example(
|
||||
"Small",
|
||||
Icon::new(IconName::Star)
|
||||
.size(IconSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
Icon::new(IconName::Star)
|
||||
.size(IconSize::XLarge)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Colors",
|
||||
vec![
|
||||
single_example("Default", Icon::new(IconName::Bell).into_any_element()),
|
||||
single_example(
|
||||
"Custom Color",
|
||||
Icon::new(IconName::Bell)
|
||||
.color(Color::Error)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use gpui::{AnyElement, IntoElement, Point};
|
||||
|
||||
use crate::{IconDecoration, IconDecorationKind, prelude::*};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
pub struct DecoratedIcon {
|
||||
icon: Icon,
|
||||
decoration: Option<IconDecoration>,
|
||||
@@ -24,18 +24,9 @@ impl RenderOnce for DecoratedIcon {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for DecoratedIcon {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"An icon with an optional decoration overlay (like an X, triangle, or dot) that can be positioned relative to the icon",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl ComponentPreview for DecoratedIcon {
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
let decoration_x = IconDecoration::new(
|
||||
IconDecorationKind::X,
|
||||
cx.theme().colors().surface_background,
|
||||
@@ -69,38 +60,32 @@ impl Component for DecoratedIcon {
|
||||
y: px(-2.),
|
||||
});
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group_with_title(
|
||||
"Decorations",
|
||||
vec![
|
||||
single_example(
|
||||
"No Decoration",
|
||||
DecoratedIcon::new(Icon::new(IconName::FileDoc), None)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"X Decoration",
|
||||
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_x))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Triangle Decoration",
|
||||
DecoratedIcon::new(
|
||||
Icon::new(IconName::FileDoc),
|
||||
Some(decoration_triangle),
|
||||
)
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group_with_title(
|
||||
"Decorations",
|
||||
vec![
|
||||
single_example(
|
||||
"No Decoration",
|
||||
DecoratedIcon::new(Icon::new(IconName::FileDoc), None).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"X Decoration",
|
||||
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_x))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Dot Decoration",
|
||||
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_dot))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
)])
|
||||
.into_any_element(),
|
||||
)
|
||||
),
|
||||
single_example(
|
||||
"Triangle Decoration",
|
||||
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_triangle))
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Dot Decoration",
|
||||
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_dot))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
)])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use strum::{EnumIter, EnumString, IntoStaticStr};
|
||||
use ui_macros::{DerivePathStr, path_str};
|
||||
|
||||
use crate::Color;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -31,7 +30,7 @@ pub enum VectorName {
|
||||
/// A [`Vector`] is different from an [`crate::Icon`] in that it is intended
|
||||
/// to be displayed at a specific size, or series of sizes, rather
|
||||
/// than conforming to the standard size of an icon.
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct Vector {
|
||||
path: &'static str,
|
||||
color: Color,
|
||||
@@ -62,6 +61,7 @@ impl Vector {
|
||||
/// Sets the vector size.
|
||||
pub fn size(mut self, size: impl Into<Size<Rems>>) -> Self {
|
||||
let size = size.into();
|
||||
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
@@ -83,72 +83,24 @@ impl RenderOnce for Vector {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Vector {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Images
|
||||
}
|
||||
#[cfg(feature = "stories")]
|
||||
pub mod story {
|
||||
use gpui::Render;
|
||||
use story::{Story, StoryItem, StorySection};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"Vector"
|
||||
}
|
||||
use crate::prelude::*;
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("A vector image component that can be displayed at specific sizes.")
|
||||
}
|
||||
use super::{Vector, VectorName};
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic Usage",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Vector::square(VectorName::ZedLogo, rems(8.)).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Custom Size",
|
||||
Vector::new(VectorName::ZedLogo, rems(12.), rems(6.))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Colored",
|
||||
vec![
|
||||
single_example(
|
||||
"Accent Color",
|
||||
Vector::square(VectorName::ZedLogo, rems(8.))
|
||||
.color(Color::Accent)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Error Color",
|
||||
Vector::square(VectorName::ZedLogo, rems(8.))
|
||||
.color(Color::Error)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Different Vectors",
|
||||
vec![
|
||||
single_example(
|
||||
"Zed Logo",
|
||||
Vector::square(VectorName::ZedLogo, rems(8.)).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Zed X Copilot",
|
||||
Vector::square(VectorName::ZedXCopilot, rems(8.))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
pub struct VectorStory;
|
||||
|
||||
impl Render for VectorStory {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container().child(StorySection::new().children(VectorName::iter().map(
|
||||
|vector| StoryItem::new(format!("{:?}", vector), Vector::square(vector, rems(8.))),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::AnyIcon;
|
||||
use crate::prelude::*;
|
||||
use crate::{AnyIcon, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
enum IndicatorKind {
|
||||
@@ -9,7 +8,7 @@ enum IndicatorKind {
|
||||
Icon(AnyIcon),
|
||||
}
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct Indicator {
|
||||
kind: IndicatorKind,
|
||||
border_color: Option<Color>,
|
||||
@@ -83,95 +82,3 @@ impl RenderOnce for Indicator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Indicator {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Status
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"Visual indicators used to represent status, notifications, or draw attention to specific elements.",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Dot Indicators",
|
||||
vec![
|
||||
single_example("Default", Indicator::dot().into_any_element()),
|
||||
single_example(
|
||||
"Success",
|
||||
Indicator::dot().color(Color::Success).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
Indicator::dot().color(Color::Warning).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Error",
|
||||
Indicator::dot().color(Color::Error).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Border",
|
||||
Indicator::dot()
|
||||
.color(Color::Accent)
|
||||
.border_color(Color::Default)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Bar Indicators",
|
||||
vec![
|
||||
single_example("Default", Indicator::bar().into_any_element()),
|
||||
single_example(
|
||||
"Success",
|
||||
Indicator::bar().color(Color::Success).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
Indicator::bar().color(Color::Warning).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Error",
|
||||
Indicator::bar().color(Color::Error).into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Icon Indicators",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
Indicator::icon(Icon::new(IconName::Circle)).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Success",
|
||||
Indicator::icon(Icon::new(IconName::Check))
|
||||
.color(Color::Success)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
Indicator::icon(Icon::new(IconName::Warning))
|
||||
.color(Color::Warning)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Error",
|
||||
Indicator::icon(Icon::new(IconName::X))
|
||||
.color(Color::Error)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use gpui::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
#[derive(Debug, IntoElement, Clone, RegisterComponent)]
|
||||
#[derive(Debug, IntoElement, Clone)]
|
||||
pub struct KeyBinding {
|
||||
/// A keybinding consists of a key and a set of modifier keys.
|
||||
/// More then one keybinding produces a chord.
|
||||
@@ -449,93 +449,6 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode
|
||||
text
|
||||
}
|
||||
|
||||
impl Component for KeyBinding {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"KeyBinding"
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"A component that displays a key binding, supporting different platform styles and vim mode.",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic Usage",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
KeyBinding::new(
|
||||
gpui::KeyBinding::new("ctrl-s", gpui::NoAction, None),
|
||||
cx,
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Mac Style",
|
||||
KeyBinding::new(
|
||||
gpui::KeyBinding::new("cmd-s", gpui::NoAction, None),
|
||||
cx,
|
||||
)
|
||||
.platform_style(PlatformStyle::Mac)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Windows Style",
|
||||
KeyBinding::new(
|
||||
gpui::KeyBinding::new("ctrl-s", gpui::NoAction, None),
|
||||
cx,
|
||||
)
|
||||
.platform_style(PlatformStyle::Windows)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Vim Mode",
|
||||
vec![single_example(
|
||||
"Vim Mode Enabled",
|
||||
KeyBinding::new(gpui::KeyBinding::new("dd", gpui::NoAction, None), cx)
|
||||
.vim_mode(true)
|
||||
.into_any_element(),
|
||||
)],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Complex Bindings",
|
||||
vec![
|
||||
single_example(
|
||||
"Multiple Keys",
|
||||
KeyBinding::new(
|
||||
gpui::KeyBinding::new("ctrl-k ctrl-b", gpui::NoAction, None),
|
||||
cx,
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Shift",
|
||||
KeyBinding::new(
|
||||
gpui::KeyBinding::new("shift-cmd-p", gpui::NoAction, None),
|
||||
cx,
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -18,7 +18,7 @@ use theme::Appearance;
|
||||
/// .prefix("Save:")
|
||||
/// .size(Pixels::from(14.0));
|
||||
/// ```
|
||||
#[derive(Debug, IntoElement, RegisterComponent)]
|
||||
#[derive(Debug, IntoElement, IntoComponent)]
|
||||
pub struct KeybindingHint {
|
||||
prefix: Option<SharedString>,
|
||||
suffix: Option<SharedString>,
|
||||
@@ -205,81 +205,68 @@ impl RenderOnce for KeybindingHint {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for KeybindingHint {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("Displays a keyboard shortcut hint with optional prefix and suffix text")
|
||||
}
|
||||
|
||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl ComponentPreview for KeybindingHint {
|
||||
fn preview(window: &mut Window, cx: &mut App) -> 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));
|
||||
|
||||
let bg_color = cx.theme().colors().surface_background;
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic",
|
||||
vec![
|
||||
single_example(
|
||||
"With Prefix",
|
||||
KeybindingHint::with_prefix(
|
||||
"Go to Start:",
|
||||
enter.clone(),
|
||||
bg_color,
|
||||
)
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic",
|
||||
vec![
|
||||
single_example(
|
||||
"With Prefix",
|
||||
KeybindingHint::with_prefix("Go to Start:", enter.clone(), bg_color)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Suffix",
|
||||
KeybindingHint::with_suffix(enter.clone(), "Go to End", bg_color)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Prefix and Suffix",
|
||||
KeybindingHint::new(enter.clone(), bg_color)
|
||||
.prefix("Confirm:")
|
||||
.suffix("Execute selected action")
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"Small",
|
||||
KeybindingHint::new(enter.clone(), bg_color)
|
||||
.size(Pixels::from(12.0))
|
||||
.prefix("Small:")
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Medium",
|
||||
KeybindingHint::new(enter.clone(), bg_color)
|
||||
.size(Pixels::from(16.0))
|
||||
.suffix("Medium")
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
KeybindingHint::new(enter.clone(), bg_color)
|
||||
.size(Pixels::from(20.0))
|
||||
.prefix("Large:")
|
||||
.suffix("Size")
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
),
|
||||
single_example(
|
||||
"With Suffix",
|
||||
KeybindingHint::with_suffix(enter.clone(), "Go to End", bg_color)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Prefix and Suffix",
|
||||
KeybindingHint::new(enter.clone(), bg_color)
|
||||
.prefix("Confirm:")
|
||||
.suffix("Execute selected action")
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"Small",
|
||||
KeybindingHint::new(enter.clone(), bg_color)
|
||||
.size(Pixels::from(12.0))
|
||||
.prefix("Small:")
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Medium",
|
||||
KeybindingHint::new(enter.clone(), bg_color)
|
||||
.size(Pixels::from(16.0))
|
||||
.suffix("Medium")
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
KeybindingHint::new(enter.clone(), bg_color)
|
||||
.size(Pixels::from(20.0))
|
||||
.prefix("Large:")
|
||||
.suffix("Size")
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::{FontWeight, HighlightStyle, StyledText};
|
||||
|
||||
use crate::{LabelCommon, LabelLike, LabelSize, LineHeightStyle, prelude::*};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct HighlightedLabel {
|
||||
base: LabelLike,
|
||||
label: SharedString,
|
||||
@@ -129,99 +129,3 @@ impl RenderOnce for HighlightedLabel {
|
||||
.child(StyledText::new(self.label).with_default_highlights(&text_style, highlights))
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for HighlightedLabel {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Typography
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"HighlightedLabel"
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("A label with highlighted characters based on specified indices.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic Usage",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
HighlightedLabel::new("Highlighted Text", vec![0, 1, 2, 3]).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Custom Color",
|
||||
HighlightedLabel::new("Colored Highlight", vec![0, 1, 7, 8, 9])
|
||||
.color(Color::Accent)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example(
|
||||
"Bold",
|
||||
HighlightedLabel::new("Bold Highlight", vec![0, 1, 2, 3])
|
||||
.weight(FontWeight::BOLD)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Italic",
|
||||
HighlightedLabel::new("Italic Highlight", vec![0, 1, 6, 7, 8])
|
||||
.italic()
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Underline",
|
||||
HighlightedLabel::new("Underlined Highlight", vec![0, 1, 10, 11, 12])
|
||||
.underline()
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"Small",
|
||||
HighlightedLabel::new("Small Highlight", vec![0, 1, 5, 6, 7])
|
||||
.size(LabelSize::Small)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Large",
|
||||
HighlightedLabel::new("Large Highlight", vec![0, 1, 5, 6, 7])
|
||||
.size(LabelSize::Large)
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Special Cases",
|
||||
vec![
|
||||
single_example(
|
||||
"Single Line",
|
||||
HighlightedLabel::new("Single Line Highlight\nWith Newline", vec![0, 1, 7, 8, 9])
|
||||
.single_line()
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Truncate",
|
||||
HighlightedLabel::new("This is a very long text that should be truncated with highlights", vec![0, 1, 2, 3, 4, 5])
|
||||
.truncate()
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ use gpui::StyleRefinement;
|
||||
///
|
||||
/// let my_label = Label::new("Deleted").strikethrough(true);
|
||||
/// ```
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
pub struct Label {
|
||||
base: LabelLike,
|
||||
label: SharedString,
|
||||
@@ -58,6 +58,9 @@ impl Label {
|
||||
}
|
||||
}
|
||||
|
||||
// nate: If we are going to do this, we might as well just
|
||||
// impl Styled for Label and not constrain styles
|
||||
|
||||
// Style methods.
|
||||
impl Label {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
@@ -197,17 +200,12 @@ impl RenderOnce for Label {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Label {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::None
|
||||
}
|
||||
mod label_preview {
|
||||
use crate::prelude::*;
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("A text label component that supports various styles, sizes, and formatting options.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
// View this component preview using `workspace: open component-preview`
|
||||
impl ComponentPreview for Label {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
@@ -253,6 +251,6 @@ impl Component for Label {
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,70 +232,3 @@ impl RenderOnce for LabelLike {
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for LabelLike {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Typography
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"LabelLike"
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(
|
||||
"A flexible, customizable label-like component that serves as a base for other label types.",
|
||||
)
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example("Default", LabelLike::new().child("Default size").into_any_element()),
|
||||
single_example("Large", LabelLike::new().size(LabelSize::Large).child("Large size").into_any_element()),
|
||||
single_example("Small", LabelLike::new().size(LabelSize::Small).child("Small size").into_any_element()),
|
||||
single_example("XSmall", LabelLike::new().size(LabelSize::XSmall).child("Extra small size").into_any_element()),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example("Bold", LabelLike::new().weight(FontWeight::BOLD).child("Bold text").into_any_element()),
|
||||
single_example("Italic", LabelLike::new().italic().child("Italic text").into_any_element()),
|
||||
single_example("Underline", LabelLike::new().underline().child("Underlined text").into_any_element()),
|
||||
single_example("Strikethrough", LabelLike::new().strikethrough().child("Strikethrough text").into_any_element()),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Colors",
|
||||
vec![
|
||||
single_example("Default", LabelLike::new().child("Default color").into_any_element()),
|
||||
single_example("Accent", LabelLike::new().color(Color::Accent).child("Accent color").into_any_element()),
|
||||
single_example("Error", LabelLike::new().color(Color::Error).child("Error color").into_any_element()),
|
||||
single_example("Alpha", LabelLike::new().alpha(0.5).child("50% opacity").into_any_element()),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Line Height",
|
||||
vec![
|
||||
single_example("Default", LabelLike::new().child("Default line height\nMulti-line text").into_any_element()),
|
||||
single_example("UI Label", LabelLike::new().line_height_style(LineHeightStyle::UiLabel).child("UI label line height\nMulti-line text").into_any_element()),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Special Cases",
|
||||
vec![
|
||||
single_example("Single Line", LabelLike::new().single_line().child("This is a very long text that should be displayed in a single line").into_any_element()),
|
||||
single_example("Truncate", LabelLike::new().truncate().child("This is a very long text that should be truncated with an ellipsis").into_any_element()),
|
||||
],
|
||||
),
|
||||
])
|
||||
.into_any_element()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::prelude::*;
|
||||
use gpui::IntoElement;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
#[component(scope = "Notification")]
|
||||
pub struct AlertModal {
|
||||
id: ElementId,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
@@ -76,33 +77,23 @@ impl ParentElement for AlertModal {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for AlertModal {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Notification
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("A modal dialog that presents an alert message with primary and dismiss actions.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.p_4()
|
||||
.children(vec![example_group(
|
||||
vec![
|
||||
single_example(
|
||||
"Basic Alert",
|
||||
AlertModal::new("simple-modal", "Do you want to leave the current call?")
|
||||
.child("The current window will be closed, and connections to any shared projects will be terminated."
|
||||
)
|
||||
.primary_action("Leave Call")
|
||||
.into_any_element(),
|
||||
)
|
||||
],
|
||||
)])
|
||||
.into_any_element()
|
||||
)
|
||||
impl ComponentPreview for AlertModal {
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.p_4()
|
||||
.children(vec![example_group(
|
||||
vec![
|
||||
single_example(
|
||||
"Basic Alert",
|
||||
AlertModal::new("simple-modal", "Do you want to leave the current call?")
|
||||
.child("The current window will be closed, and connections to any shared projects will be terminated."
|
||||
)
|
||||
.primary_action("Leave Call")
|
||||
.into_any_element(),
|
||||
)
|
||||
],
|
||||
)])
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@ use smallvec::SmallVec;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::Checkbox;
|
||||
|
||||
#[derive(IntoElement, RegisterComponent)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct SettingsContainer {
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
@@ -35,55 +33,3 @@ impl RenderOnce for SettingsContainer {
|
||||
v_flex().px_2().gap_1().children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SettingsContainer {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Layout
|
||||
}
|
||||
|
||||
fn name() -> &'static str {
|
||||
"SettingsContainer"
|
||||
}
|
||||
|
||||
fn description() -> Option<&'static str> {
|
||||
Some("A container for organizing and displaying settings in a structured manner.")
|
||||
}
|
||||
|
||||
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![
|
||||
example_group_with_title(
|
||||
"Basic Usage",
|
||||
vec![
|
||||
single_example(
|
||||
"Empty Container",
|
||||
SettingsContainer::new().into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"With Content",
|
||||
SettingsContainer::new()
|
||||
.child(Label::new("Setting 1"))
|
||||
.child(Label::new("Setting 2"))
|
||||
.child(Label::new("Setting 3"))
|
||||
.into_any_element(),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"With Different Elements",
|
||||
vec![single_example(
|
||||
"Mixed Content",
|
||||
SettingsContainer::new()
|
||||
.child(Label::new("Text Setting"))
|
||||
.child(Checkbox::new("checkbox", ToggleState::Unselected))
|
||||
.child(Button::new("button", "Click me"))
|
||||
.into_any_element(),
|
||||
)],
|
||||
),
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user