Compare commits
1 Commits
gpui-butto
...
conflict-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6c071c363 |
@@ -2,14 +2,16 @@
|
||||
{
|
||||
"label": "Debug Zed (CodeLLDB)",
|
||||
"adapter": "CodeLLDB",
|
||||
"program": "target/debug/zed",
|
||||
"request": "launch"
|
||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT"
|
||||
},
|
||||
{
|
||||
"label": "Debug Zed (GDB)",
|
||||
"adapter": "GDB",
|
||||
"program": "target/debug/zed",
|
||||
"program": "$ZED_WORKTREE_ROOT/target/debug/zed",
|
||||
"request": "launch",
|
||||
"cwd": "$ZED_WORKTREE_ROOT",
|
||||
"initialize_args": {
|
||||
"stopAtBeginningOfMainSubprogram": true
|
||||
}
|
||||
|
||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -2792,7 +2792,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion 0.3.2",
|
||||
"async-tungstenite",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"clock",
|
||||
"cocoa 0.26.0",
|
||||
@@ -2804,7 +2803,6 @@ dependencies = [
|
||||
"gpui_tokio",
|
||||
"http_client",
|
||||
"http_client_tls",
|
||||
"httparse",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
@@ -2825,7 +2823,6 @@ dependencies = [
|
||||
"time",
|
||||
"tiny_http",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-socks",
|
||||
"url",
|
||||
"util",
|
||||
@@ -5068,7 +5065,6 @@ dependencies = [
|
||||
"task",
|
||||
"toml 0.8.20",
|
||||
"util",
|
||||
"wasi-preview1-component-adapter-provider",
|
||||
"wasm-encoder 0.221.3",
|
||||
"wasmparser 0.221.3",
|
||||
"wit-component 0.221.3",
|
||||
@@ -16215,12 +16211,6 @@ dependencies = [
|
||||
"wit-bindgen-rt 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi-preview1-component-adapter-provider"
|
||||
version = "29.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcd9f21bbde82ba59e415a8725e6ad0d0d7e9e460b1a3ccbca5bdee952c1a324"
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -594,7 +594,6 @@ url = "2.2"
|
||||
urlencoding = "2.1.2"
|
||||
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
|
||||
walkdir = "2.3"
|
||||
wasi-preview1-component-adapter-provider = "29"
|
||||
wasm-encoder = "0.221"
|
||||
wasmparser = "0.221"
|
||||
wasmtime = { version = "29", default-features = false, features = [
|
||||
@@ -788,9 +787,6 @@ let_underscore_future = "allow"
|
||||
# running afoul of the borrow checker.
|
||||
too_many_arguments = "allow"
|
||||
|
||||
# We often have large enum variants yet we rarely actually bother with splitting them up.
|
||||
large_enum_variant = "allow"
|
||||
|
||||
[workspace.metadata.cargo-machete]
|
||||
ignored = [
|
||||
"bindgen",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.87-bookworm as builder
|
||||
FROM rust:1.86-bookworm as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -766,7 +766,7 @@
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"shift-find": "project_panel::NewSearchInDirectory",
|
||||
"ctrl-alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrevious",
|
||||
"escape": "menu::Cancel"
|
||||
|
||||
@@ -825,7 +825,7 @@
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"cmd-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrevious",
|
||||
"escape": "menu::Cancel"
|
||||
|
||||
@@ -99,6 +99,9 @@
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
"version_control.deleted": "#e06c76ff",
|
||||
"version_control.conflict_marker.ours": "#a1c1811a",
|
||||
"version_control.conflict_marker.theirs": "#74ade81a",
|
||||
"version_control.conflict_marker.border": "#d3b0201f",
|
||||
"conflict": "#dec184ff",
|
||||
"conflict.background": "#dec1841a",
|
||||
"conflict.border": "#5d4c2fff",
|
||||
@@ -478,6 +481,11 @@
|
||||
"version_control.added": "#27a657ff",
|
||||
"version_control.modified": "#d3b020ff",
|
||||
"version_control.deleted": "#e06c76ff",
|
||||
"version_control.conflict.ours_background": "#FF0000",
|
||||
"version_control.conflict.ours_border": "#FF0000",
|
||||
"version_control.conflict.theirs_background": "#e2e2faff",
|
||||
"version_control.conflict.theirs_border": "#cbcdf6ff",
|
||||
"version_control.conflict.divider_background": "#faf2e6ff",
|
||||
"conflict": "#a48819ff",
|
||||
"conflict.background": "#faf2e6ff",
|
||||
"conflict.border": "#f4e7d1ff",
|
||||
|
||||
@@ -185,14 +185,12 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle
|
||||
let ui_font_size = TextSize::Default.rems(cx);
|
||||
let buffer_font_size = TextSize::Small.rems(cx);
|
||||
let mut text_style = window.text_style();
|
||||
let line_height = buffer_font_size * 1.75;
|
||||
|
||||
text_style.refine(&TextStyleRefinement {
|
||||
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||
font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
|
||||
font_features: Some(theme_settings.ui_font.features.clone()),
|
||||
font_size: Some(ui_font_size.into()),
|
||||
line_height: Some(line_height.into()),
|
||||
color: Some(cx.theme().colors().text),
|
||||
..Default::default()
|
||||
});
|
||||
@@ -1722,11 +1720,10 @@ impl ActiveThread {
|
||||
.on_action(cx.listener(Self::confirm_editing_message))
|
||||
.capture_action(cx.listener(Self::paste))
|
||||
.min_h_6()
|
||||
.w_full()
|
||||
.flex_grow()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.child(state.context_strip.clone())
|
||||
.child(div().pt(px(-3.)).px_neg_0p5().child(EditorElement::new(
|
||||
.child(EditorElement::new(
|
||||
&state.editor,
|
||||
EditorStyle {
|
||||
background: colors.editor_background,
|
||||
@@ -1735,7 +1732,8 @@ impl ActiveThread {
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
..Default::default()
|
||||
},
|
||||
)))
|
||||
))
|
||||
.child(state.context_strip.clone())
|
||||
}
|
||||
|
||||
fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
@@ -1923,6 +1921,16 @@ impl ActiveThread {
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.when(!message_is_empty, |parent| {
|
||||
parent.child(div().min_h_6().child(self.render_message_content(
|
||||
message_id,
|
||||
rendered_message,
|
||||
has_tool_uses,
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)))
|
||||
})
|
||||
.when(!added_context.is_empty(), |parent| {
|
||||
parent.child(h_flex().flex_wrap().gap_1().children(
|
||||
added_context.into_iter().map(|added_context| {
|
||||
@@ -1941,16 +1949,6 @@ impl ActiveThread {
|
||||
}),
|
||||
))
|
||||
})
|
||||
.when(!message_is_empty, |parent| {
|
||||
parent.child(div().pt_0p5().min_h_6().child(self.render_message_content(
|
||||
message_id,
|
||||
rendered_message,
|
||||
has_tool_uses,
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
)))
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
});
|
||||
@@ -1976,7 +1974,6 @@ impl ActiveThread {
|
||||
h_flex()
|
||||
.p_2p5()
|
||||
.gap_1()
|
||||
.items_end()
|
||||
.children(message_content)
|
||||
.when_some(editing_message_state, |this, state| {
|
||||
let focus_handle = state.editor.focus_handle(cx).clone();
|
||||
@@ -1990,7 +1987,6 @@ impl ActiveThread {
|
||||
)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Error)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
@@ -2008,12 +2004,11 @@ impl ActiveThread {
|
||||
.child(
|
||||
IconButton::new(
|
||||
"confirm-edit-message",
|
||||
IconName::Return,
|
||||
IconName::Check,
|
||||
)
|
||||
.disabled(state.editor.read(cx).is_empty(cx))
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Success)
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |window, cx| {
|
||||
@@ -2033,6 +2028,9 @@ impl ActiveThread {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when(editing_message_state.is_none(), |this| {
|
||||
this.tooltip(Tooltip::text("Click To Edit"))
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
let message_segments = message.segments.clone();
|
||||
move |this, _, window, cx| {
|
||||
@@ -2073,16 +2071,6 @@ impl ActiveThread {
|
||||
|
||||
let panel_background = cx.theme().colors().panel_background;
|
||||
|
||||
let backdrop = div()
|
||||
.id("backdrop")
|
||||
.stop_mouse_events_except_scroll()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
.size_full()
|
||||
.bg(panel_background)
|
||||
.opacity(0.8)
|
||||
.on_click(cx.listener(Self::handle_cancel_click));
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.map(|parent| {
|
||||
@@ -2252,7 +2240,15 @@ impl ActiveThread {
|
||||
})
|
||||
.when(after_editing_message, |parent| {
|
||||
// Backdrop to dim out the whole thread below the editing user message
|
||||
parent.relative().child(backdrop)
|
||||
parent.relative().child(
|
||||
div()
|
||||
.stop_mouse_events_except_scroll()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
.size_full()
|
||||
.bg(panel_background)
|
||||
.opacity(0.8),
|
||||
)
|
||||
})
|
||||
.into_any()
|
||||
}
|
||||
@@ -2367,7 +2363,6 @@ impl ActiveThread {
|
||||
move |el, range, metadata, _, cx| {
|
||||
let can_expand = metadata.line_count
|
||||
>= MAX_UNCOLLAPSED_LINES_IN_CODE_BLOCK;
|
||||
|
||||
if !can_expand {
|
||||
return el;
|
||||
}
|
||||
@@ -2375,7 +2370,6 @@ impl ActiveThread {
|
||||
let is_expanded = active_thread
|
||||
.read(cx)
|
||||
.is_codeblock_expanded(message_id, range.start);
|
||||
|
||||
if is_expanded {
|
||||
return el;
|
||||
}
|
||||
@@ -3398,14 +3392,14 @@ impl ActiveThread {
|
||||
self.expanded_code_blocks
|
||||
.get(&(message_id, ix))
|
||||
.copied()
|
||||
.unwrap_or(true)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn toggle_codeblock_expanded(&mut self, message_id: MessageId, ix: usize) {
|
||||
let is_expanded = self
|
||||
.expanded_code_blocks
|
||||
.entry((message_id, ix))
|
||||
.or_insert(true);
|
||||
.or_insert(false);
|
||||
*is_expanded = !*is_expanded;
|
||||
}
|
||||
}
|
||||
@@ -3421,7 +3415,6 @@ impl Render for ActiveThread {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.relative()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.on_mouse_move(cx.listener(|this, _, _, cx| {
|
||||
this.show_scrollbar = true;
|
||||
this.hide_scrollbar_later(cx);
|
||||
|
||||
@@ -30,6 +30,7 @@ pub(crate) struct ConfigureContextServerModal {
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Configuration {
|
||||
NotAvailable,
|
||||
Required(ConfigurationRequiredState),
|
||||
|
||||
@@ -2135,7 +2135,6 @@ impl AgentPanel {
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().panel_background)
|
||||
.when(recent_history.is_empty(), |this| {
|
||||
let configuration_error_ref = &configuration_error;
|
||||
this.child(
|
||||
|
||||
@@ -84,12 +84,6 @@ impl ContextStrip {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether or not the context strip has items to display
|
||||
pub fn has_context_items(&self, cx: &App) -> bool {
|
||||
self.context_store.read(cx).context().next().is_some()
|
||||
|| self.suggested_context(cx).is_some()
|
||||
}
|
||||
|
||||
fn added_contexts(&self, cx: &App) -> Vec<AddedContext> {
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
let project = workspace.read(cx).project().read(cx);
|
||||
@@ -110,14 +104,14 @@ impl ContextStrip {
|
||||
}
|
||||
}
|
||||
|
||||
fn suggested_context(&self, cx: &App) -> Option<SuggestedContext> {
|
||||
fn suggested_context(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
|
||||
match self.suggest_context_kind {
|
||||
SuggestContextKind::File => self.suggested_file(cx),
|
||||
SuggestContextKind::Thread => self.suggested_thread(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn suggested_file(&self, cx: &App) -> Option<SuggestedContext> {
|
||||
fn suggested_file(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let active_item = workspace.read(cx).active_item(cx)?;
|
||||
|
||||
@@ -144,7 +138,7 @@ impl ContextStrip {
|
||||
})
|
||||
}
|
||||
|
||||
fn suggested_thread(&self, cx: &App) -> Option<SuggestedContext> {
|
||||
fn suggested_thread(&self, cx: &Context<Self>) -> Option<SuggestedContext> {
|
||||
if !self.context_picker.read(cx).allow_threads() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||
editor.move_to_end(&Default::default(), window, cx)
|
||||
});
|
||||
}
|
||||
} else if self.context_strip.read(cx).has_context_items(cx) {
|
||||
} else {
|
||||
self.context_strip.focus_handle(cx).focus(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ impl MessageEditor {
|
||||
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.context_picker_menu_handle.is_deployed() {
|
||||
cx.propagate();
|
||||
} else if self.context_strip.read(cx).has_context_items(cx) {
|
||||
} else {
|
||||
self.context_strip.focus_handle(cx).focus(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,17 +425,16 @@ impl ToolUseState {
|
||||
|
||||
let content = match tool_result {
|
||||
ToolResultContent::Text(text) => {
|
||||
let text = if text.len() < tool_output_limit {
|
||||
text
|
||||
} else {
|
||||
let truncated = truncate_lines_to_byte_limit(&text, tool_output_limit);
|
||||
let truncated = truncate_lines_to_byte_limit(&text, tool_output_limit);
|
||||
|
||||
LanguageModelToolResultContent::Text(
|
||||
format!(
|
||||
"Tool result too long. The first {} bytes:\n\n{}",
|
||||
truncated.len(),
|
||||
truncated
|
||||
)
|
||||
};
|
||||
LanguageModelToolResultContent::Text(text.into())
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
ToolResultContent::Image(language_model_image) => {
|
||||
if language_model_image.estimate_tokens() < tool_output_limit {
|
||||
|
||||
@@ -163,10 +163,8 @@ impl AskPassSession {
|
||||
#[cfg(unix)]
|
||||
fn get_shell_safe_zed_path() -> anyhow::Result<String> {
|
||||
let zed_path = std::env::current_exe()
|
||||
.context("Failed to determine current executable path for use in askpass")?
|
||||
.context("Failed to figure out current executable path for use in askpass")?
|
||||
.to_string_lossy()
|
||||
// see https://github.com/rust-lang/rust/issues/69343
|
||||
.trim_end_matches(" (deleted)")
|
||||
.to_string();
|
||||
|
||||
// NOTE: this was previously enabled, however, it caused errors when it shouldn't have
|
||||
|
||||
@@ -692,7 +692,7 @@ impl JsonSchema for LanguageModelProviderSetting {
|
||||
schemars::schema::SchemaObject {
|
||||
enum_values: Some(vec![
|
||||
"anthropic".into(),
|
||||
"amazon-bedrock".into(),
|
||||
"bedrock".into(),
|
||||
"google".into(),
|
||||
"lmstudio".into(),
|
||||
"ollama".into(),
|
||||
|
||||
@@ -15,7 +15,7 @@ use gpui::{AppContext, TestAppContext};
|
||||
use indoc::{formatdoc, indoc};
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequestTool, LanguageModelToolResult,
|
||||
LanguageModelToolResultContent, LanguageModelToolUse, LanguageModelToolUseId, SelectedModel,
|
||||
LanguageModelToolResultContent, LanguageModelToolUse, LanguageModelToolUseId,
|
||||
};
|
||||
use project::Project;
|
||||
use rand::prelude::*;
|
||||
@@ -25,7 +25,6 @@ use std::{
|
||||
cmp::Reverse,
|
||||
fmt::{self, Display},
|
||||
io::Write as _,
|
||||
str::FromStr,
|
||||
sync::mpsc,
|
||||
};
|
||||
use util::path;
|
||||
@@ -1217,7 +1216,7 @@ fn report_progress(evaluated_count: usize, failed_count: usize, iterations: usiz
|
||||
passed_count as f64 / evaluated_count as f64
|
||||
};
|
||||
print!(
|
||||
"\r\x1b[KEvaluated {}/{} ({:.2}% passed)",
|
||||
"\r\x1b[KEvaluated {}/{} ({:.2}%)",
|
||||
evaluated_count,
|
||||
iterations,
|
||||
passed_ratio * 100.0
|
||||
@@ -1256,21 +1255,13 @@ impl EditAgentTest {
|
||||
|
||||
fs.insert_tree("/root", json!({})).await;
|
||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let agent_model = SelectedModel::from_str(
|
||||
&std::env::var("ZED_AGENT_MODEL")
|
||||
.unwrap_or("anthropic/claude-3-7-sonnet-latest".into()),
|
||||
)
|
||||
.unwrap();
|
||||
let judge_model = SelectedModel::from_str(
|
||||
&std::env::var("ZED_JUDGE_MODEL")
|
||||
.unwrap_or("anthropic/claude-3-7-sonnet-latest".into()),
|
||||
)
|
||||
.unwrap();
|
||||
let (agent_model, judge_model) = cx
|
||||
.update(|cx| {
|
||||
cx.spawn(async move |cx| {
|
||||
let agent_model = Self::load_model(&agent_model, cx).await;
|
||||
let judge_model = Self::load_model(&judge_model, cx).await;
|
||||
let agent_model =
|
||||
Self::load_model("anthropic", "claude-3-7-sonnet-latest", cx).await;
|
||||
let judge_model =
|
||||
Self::load_model("anthropic", "claude-3-7-sonnet-latest", cx).await;
|
||||
(agent_model.unwrap(), judge_model.unwrap())
|
||||
})
|
||||
})
|
||||
@@ -1285,17 +1276,15 @@ impl EditAgentTest {
|
||||
}
|
||||
|
||||
async fn load_model(
|
||||
selected_model: &SelectedModel,
|
||||
provider: &str,
|
||||
id: &str,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Arc<dyn LanguageModel>> {
|
||||
let (provider, model) = cx.update(|cx| {
|
||||
let models = LanguageModelRegistry::read_global(cx);
|
||||
let model = models
|
||||
.available_models(cx)
|
||||
.find(|model| {
|
||||
model.provider_id() == selected_model.provider
|
||||
&& model.id() == selected_model.model
|
||||
})
|
||||
.find(|model| model.provider_id().0 == provider && model.id().0 == id)
|
||||
.unwrap();
|
||||
let provider = models.provider(&model.provider_id()).unwrap();
|
||||
(provider, model)
|
||||
|
||||
@@ -1249,7 +1249,7 @@ pub struct ActiveDiagnosticGroup {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub(crate) enum ActiveDiagnostic {
|
||||
None,
|
||||
All,
|
||||
|
||||
@@ -19,7 +19,6 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
|
||||
anyhow.workspace = true
|
||||
async-recursion = "0.3"
|
||||
async-tungstenite = { workspace = true, features = ["tokio", "tokio-rustls-manual-roots"] }
|
||||
base64.workspace = true
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
@@ -30,7 +29,6 @@ gpui.workspace = true
|
||||
gpui_tokio.workspace = true
|
||||
http_client.workspace = true
|
||||
http_client_tls.workspace = true
|
||||
httparse = "1.10"
|
||||
log.workspace = true
|
||||
paths.workspace = true
|
||||
parking_lot.workspace = true
|
||||
@@ -49,7 +47,6 @@ text.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
tokio-native-tls = "0.3"
|
||||
tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io"] }
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod test;
|
||||
|
||||
mod proxy;
|
||||
mod socks;
|
||||
pub mod telemetry;
|
||||
pub mod user;
|
||||
pub mod zed_urls;
|
||||
@@ -24,13 +24,13 @@ use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use proxy::connect_proxy_stream;
|
||||
use rand::prelude::*;
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use socks::connect_socks_proxy_stream;
|
||||
use std::pin::Pin;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
@@ -1156,7 +1156,7 @@ impl Client {
|
||||
let handle = cx.update(|cx| gpui_tokio::Tokio::handle(cx)).ok().unwrap();
|
||||
let _guard = handle.enter();
|
||||
match proxy {
|
||||
Some(proxy) => connect_proxy_stream(&proxy, rpc_host).await?,
|
||||
Some(proxy) => connect_socks_proxy_stream(&proxy, rpc_host).await?,
|
||||
None => Box::new(TcpStream::connect(rpc_host).await?),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
//! client proxy
|
||||
|
||||
mod http_proxy;
|
||||
mod socks_proxy;
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use http_client::Url;
|
||||
use http_proxy::{HttpProxyType, connect_http_proxy_stream, parse_http_proxy};
|
||||
use socks_proxy::{SocksVersion, connect_socks_proxy_stream, parse_socks_proxy};
|
||||
|
||||
pub(crate) async fn connect_proxy_stream(
|
||||
proxy: &Url,
|
||||
rpc_host: (&str, u16),
|
||||
) -> Result<Box<dyn AsyncReadWrite>> {
|
||||
let Some(((proxy_domain, proxy_port), proxy_type)) = parse_proxy_type(proxy) else {
|
||||
// If parsing the proxy URL fails, we must avoid falling back to an insecure connection.
|
||||
// SOCKS proxies are often used in contexts where security and privacy are critical,
|
||||
// so any fallback could expose users to significant risks.
|
||||
return Err(anyhow!("Parsing proxy url failed"));
|
||||
};
|
||||
|
||||
// Connect to proxy and wrap protocol later
|
||||
let stream = tokio::net::TcpStream::connect((proxy_domain.as_str(), proxy_port))
|
||||
.await
|
||||
.context("Failed to connect to proxy")?;
|
||||
|
||||
let proxy_stream = match proxy_type {
|
||||
ProxyType::SocksProxy(proxy) => connect_socks_proxy_stream(stream, proxy, rpc_host).await?,
|
||||
ProxyType::HttpProxy(proxy) => {
|
||||
connect_http_proxy_stream(stream, proxy, rpc_host, &proxy_domain).await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(proxy_stream)
|
||||
}
|
||||
|
||||
enum ProxyType<'t> {
|
||||
SocksProxy(SocksVersion<'t>),
|
||||
HttpProxy(HttpProxyType<'t>),
|
||||
}
|
||||
|
||||
fn parse_proxy_type<'t>(proxy: &'t Url) -> Option<((String, u16), ProxyType<'t>)> {
|
||||
let scheme = proxy.scheme();
|
||||
let host = proxy.host()?.to_string();
|
||||
let port = proxy.port_or_known_default()?;
|
||||
let proxy_type = match scheme {
|
||||
scheme if scheme.starts_with("socks") => {
|
||||
Some(ProxyType::SocksProxy(parse_socks_proxy(scheme, proxy)))
|
||||
}
|
||||
scheme if scheme.starts_with("http") => {
|
||||
Some(ProxyType::HttpProxy(parse_http_proxy(scheme, proxy)))
|
||||
}
|
||||
_ => None,
|
||||
}?;
|
||||
|
||||
Some(((host, port), proxy_type))
|
||||
}
|
||||
|
||||
pub(crate) trait AsyncReadWrite:
|
||||
tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static
|
||||
{
|
||||
}
|
||||
impl<T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static> AsyncReadWrite
|
||||
for T
|
||||
{
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use base64::Engine;
|
||||
use httparse::{EMPTY_HEADER, Response};
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufStream},
|
||||
net::TcpStream,
|
||||
};
|
||||
use tokio_native_tls::{TlsConnector, native_tls};
|
||||
use url::Url;
|
||||
|
||||
use super::AsyncReadWrite;
|
||||
|
||||
pub(super) enum HttpProxyType<'t> {
|
||||
HTTP(Option<HttpProxyAuthorization<'t>>),
|
||||
HTTPS(Option<HttpProxyAuthorization<'t>>),
|
||||
}
|
||||
|
||||
pub(super) struct HttpProxyAuthorization<'t> {
|
||||
username: &'t str,
|
||||
password: &'t str,
|
||||
}
|
||||
|
||||
pub(super) fn parse_http_proxy<'t>(scheme: &str, proxy: &'t Url) -> HttpProxyType<'t> {
|
||||
let auth = proxy.password().map(|password| HttpProxyAuthorization {
|
||||
username: proxy.username(),
|
||||
password,
|
||||
});
|
||||
if scheme.starts_with("https") {
|
||||
HttpProxyType::HTTPS(auth)
|
||||
} else {
|
||||
HttpProxyType::HTTP(auth)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn connect_http_proxy_stream(
|
||||
stream: TcpStream,
|
||||
http_proxy: HttpProxyType<'_>,
|
||||
rpc_host: (&str, u16),
|
||||
proxy_domain: &str,
|
||||
) -> Result<Box<dyn AsyncReadWrite>> {
|
||||
match http_proxy {
|
||||
HttpProxyType::HTTP(auth) => http_connect(stream, rpc_host, auth).await,
|
||||
HttpProxyType::HTTPS(auth) => https_connect(stream, rpc_host, auth, proxy_domain).await,
|
||||
}
|
||||
.context("error connecting to http/https proxy")
|
||||
}
|
||||
|
||||
async fn http_connect<T>(
|
||||
stream: T,
|
||||
target: (&str, u16),
|
||||
auth: Option<HttpProxyAuthorization<'_>>,
|
||||
) -> Result<Box<dyn AsyncReadWrite>>
|
||||
where
|
||||
T: AsyncReadWrite,
|
||||
{
|
||||
let mut stream = BufStream::new(stream);
|
||||
let request = make_request(target, auth);
|
||||
stream.write_all(request.as_bytes()).await?;
|
||||
stream.flush().await?;
|
||||
check_response(&mut stream).await?;
|
||||
Ok(Box::new(stream))
|
||||
}
|
||||
|
||||
async fn https_connect<T>(
|
||||
stream: T,
|
||||
target: (&str, u16),
|
||||
auth: Option<HttpProxyAuthorization<'_>>,
|
||||
proxy_domain: &str,
|
||||
) -> Result<Box<dyn AsyncReadWrite>>
|
||||
where
|
||||
T: AsyncReadWrite,
|
||||
{
|
||||
let tls_connector = TlsConnector::from(native_tls::TlsConnector::new()?);
|
||||
let stream = tls_connector.connect(proxy_domain, stream).await?;
|
||||
http_connect(stream, target, auth).await
|
||||
}
|
||||
|
||||
fn make_request(target: (&str, u16), auth: Option<HttpProxyAuthorization<'_>>) -> String {
|
||||
let (host, port) = target;
|
||||
let mut request = format!(
|
||||
"CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\nProxy-Connection: Keep-Alive\r\n"
|
||||
);
|
||||
if let Some(HttpProxyAuthorization { username, password }) = auth {
|
||||
let auth =
|
||||
base64::prelude::BASE64_STANDARD.encode(format!("{username}:{password}").as_bytes());
|
||||
let auth = format!("Proxy-Authorization: Basic {auth}\r\n");
|
||||
request.push_str(&auth);
|
||||
}
|
||||
request.push_str("\r\n");
|
||||
request
|
||||
}
|
||||
|
||||
async fn check_response<T>(stream: &mut BufStream<T>) -> Result<()>
|
||||
where
|
||||
T: AsyncReadWrite,
|
||||
{
|
||||
let response = recv_response(stream).await?;
|
||||
let mut dummy_headers = [EMPTY_HEADER; MAX_RESPONSE_HEADERS];
|
||||
let mut parser = Response::new(&mut dummy_headers);
|
||||
parser.parse(response.as_bytes())?;
|
||||
|
||||
match parser.code {
|
||||
Some(code) => {
|
||||
if code == 200 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"Proxy connection failed with HTTP code: {code}"
|
||||
))
|
||||
}
|
||||
}
|
||||
None => Err(anyhow::anyhow!(
|
||||
"Proxy connection failed with no HTTP code: {}",
|
||||
parser.reason.unwrap_or("Unknown reason")
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_RESPONSE_HEADER_LENGTH: usize = 4096;
|
||||
const MAX_RESPONSE_HEADERS: usize = 16;
|
||||
|
||||
async fn recv_response<T>(stream: &mut BufStream<T>) -> Result<String>
|
||||
where
|
||||
T: AsyncReadWrite,
|
||||
{
|
||||
let mut response = String::new();
|
||||
loop {
|
||||
if stream.read_line(&mut response).await? == 0 {
|
||||
return Err(anyhow::anyhow!("End of stream"));
|
||||
}
|
||||
|
||||
if MAX_RESPONSE_HEADER_LENGTH < response.len() {
|
||||
return Err(anyhow::anyhow!("Maximum response header length exceeded"));
|
||||
}
|
||||
|
||||
if response.ends_with("\r\n\r\n") {
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use url::Url;
|
||||
|
||||
use super::{HttpProxyAuthorization, HttpProxyType, parse_http_proxy};
|
||||
|
||||
#[test]
|
||||
fn test_parse_http_proxy() {
|
||||
let proxy = Url::parse("http://proxy.example.com:1080").unwrap();
|
||||
let scheme = proxy.scheme();
|
||||
|
||||
let version = parse_http_proxy(scheme, &proxy);
|
||||
assert!(matches!(version, HttpProxyType::HTTP(None)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_http_proxy_with_auth() {
|
||||
let proxy = Url::parse("http://username:password@proxy.example.com:1080").unwrap();
|
||||
let scheme = proxy.scheme();
|
||||
|
||||
let version = parse_http_proxy(scheme, &proxy);
|
||||
assert!(matches!(
|
||||
version,
|
||||
HttpProxyType::HTTP(Some(HttpProxyAuthorization {
|
||||
username: "username",
|
||||
password: "password"
|
||||
}))
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,15 @@
|
||||
//! socks proxy
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::net::TcpStream;
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use http_client::Url;
|
||||
use tokio_socks::tcp::{Socks4Stream, Socks5Stream};
|
||||
use url::Url;
|
||||
|
||||
use super::AsyncReadWrite;
|
||||
|
||||
/// Identification to a Socks V4 Proxy
|
||||
pub(super) struct Socks4Identification<'a> {
|
||||
struct Socks4Identification<'a> {
|
||||
user_id: &'a str,
|
||||
}
|
||||
|
||||
/// Authorization to a Socks V5 Proxy
|
||||
pub(super) struct Socks5Authorization<'a> {
|
||||
struct Socks5Authorization<'a> {
|
||||
username: &'a str,
|
||||
password: &'a str,
|
||||
}
|
||||
@@ -22,50 +18,45 @@ pub(super) struct Socks5Authorization<'a> {
|
||||
///
|
||||
/// V4 allows idenfication using a user_id
|
||||
/// V5 allows authorization using a username and password
|
||||
pub(super) enum SocksVersion<'a> {
|
||||
enum SocksVersion<'a> {
|
||||
V4(Option<Socks4Identification<'a>>),
|
||||
V5(Option<Socks5Authorization<'a>>),
|
||||
}
|
||||
|
||||
pub(super) fn parse_socks_proxy<'t>(scheme: &str, proxy: &'t Url) -> SocksVersion<'t> {
|
||||
if scheme.starts_with("socks4") {
|
||||
let identification = match proxy.username() {
|
||||
"" => None,
|
||||
username => Some(Socks4Identification { user_id: username }),
|
||||
};
|
||||
SocksVersion::V4(identification)
|
||||
} else {
|
||||
let authorization = proxy.password().map(|password| Socks5Authorization {
|
||||
username: proxy.username(),
|
||||
password,
|
||||
});
|
||||
SocksVersion::V5(authorization)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn connect_socks_proxy_stream(
|
||||
stream: TcpStream,
|
||||
socks_version: SocksVersion<'_>,
|
||||
pub(crate) async fn connect_socks_proxy_stream(
|
||||
proxy: &Url,
|
||||
rpc_host: (&str, u16),
|
||||
) -> Result<Box<dyn AsyncReadWrite>> {
|
||||
match socks_version {
|
||||
let Some((socks_proxy, version)) = parse_socks_proxy(proxy) else {
|
||||
// If parsing the proxy URL fails, we must avoid falling back to an insecure connection.
|
||||
// SOCKS proxies are often used in contexts where security and privacy are critical,
|
||||
// so any fallback could expose users to significant risks.
|
||||
return Err(anyhow!("Parsing proxy url failed"));
|
||||
};
|
||||
|
||||
// Connect to proxy and wrap protocol later
|
||||
let stream = tokio::net::TcpStream::connect(socks_proxy)
|
||||
.await
|
||||
.context("Failed to connect to socks proxy")?;
|
||||
|
||||
let socks: Box<dyn AsyncReadWrite> = match version {
|
||||
SocksVersion::V4(None) => {
|
||||
let socks = Socks4Stream::connect_with_socket(stream, rpc_host)
|
||||
.await
|
||||
.context("error connecting to socks")?;
|
||||
Ok(Box::new(socks))
|
||||
Box::new(socks)
|
||||
}
|
||||
SocksVersion::V4(Some(Socks4Identification { user_id })) => {
|
||||
let socks = Socks4Stream::connect_with_userid_and_socket(stream, rpc_host, user_id)
|
||||
.await
|
||||
.context("error connecting to socks")?;
|
||||
Ok(Box::new(socks))
|
||||
Box::new(socks)
|
||||
}
|
||||
SocksVersion::V5(None) => {
|
||||
let socks = Socks5Stream::connect_with_socket(stream, rpc_host)
|
||||
.await
|
||||
.context("error connecting to socks")?;
|
||||
Ok(Box::new(socks))
|
||||
Box::new(socks)
|
||||
}
|
||||
SocksVersion::V5(Some(Socks5Authorization { username, password })) => {
|
||||
let socks = Socks5Stream::connect_with_password_and_socket(
|
||||
@@ -73,9 +64,44 @@ pub(super) async fn connect_socks_proxy_stream(
|
||||
)
|
||||
.await
|
||||
.context("error connecting to socks")?;
|
||||
Ok(Box::new(socks))
|
||||
Box::new(socks)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(socks)
|
||||
}
|
||||
|
||||
fn parse_socks_proxy(proxy: &Url) -> Option<((String, u16), SocksVersion<'_>)> {
|
||||
let scheme = proxy.scheme();
|
||||
let socks_version = if scheme.starts_with("socks4") {
|
||||
let identification = match proxy.username() {
|
||||
"" => None,
|
||||
username => Some(Socks4Identification { user_id: username }),
|
||||
};
|
||||
SocksVersion::V4(identification)
|
||||
} else if scheme.starts_with("socks") {
|
||||
let authorization = proxy.password().map(|password| Socks5Authorization {
|
||||
username: proxy.username(),
|
||||
password,
|
||||
});
|
||||
SocksVersion::V5(authorization)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let host = proxy.host()?.to_string();
|
||||
let port = proxy.port_or_known_default()?;
|
||||
|
||||
Some(((host, port), socks_version))
|
||||
}
|
||||
|
||||
pub(crate) trait AsyncReadWrite:
|
||||
tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static
|
||||
{
|
||||
}
|
||||
impl<T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static> AsyncReadWrite
|
||||
for T
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -87,18 +113,20 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_socks4() {
|
||||
let proxy = Url::parse("socks4://proxy.example.com:1080").unwrap();
|
||||
let scheme = proxy.scheme();
|
||||
|
||||
let version = parse_socks_proxy(scheme, &proxy);
|
||||
let ((host, port), version) = parse_socks_proxy(&proxy).unwrap();
|
||||
assert_eq!(host, "proxy.example.com");
|
||||
assert_eq!(port, 1080);
|
||||
assert!(matches!(version, SocksVersion::V4(None)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_socks4_with_identification() {
|
||||
let proxy = Url::parse("socks4://userid@proxy.example.com:1080").unwrap();
|
||||
let scheme = proxy.scheme();
|
||||
|
||||
let version = parse_socks_proxy(scheme, &proxy);
|
||||
let ((host, port), version) = parse_socks_proxy(&proxy).unwrap();
|
||||
assert_eq!(host, "proxy.example.com");
|
||||
assert_eq!(port, 1080);
|
||||
assert!(matches!(
|
||||
version,
|
||||
SocksVersion::V4(Some(Socks4Identification { user_id: "userid" }))
|
||||
@@ -108,18 +136,20 @@ mod tests {
|
||||
#[test]
|
||||
fn parse_socks5() {
|
||||
let proxy = Url::parse("socks5://proxy.example.com:1080").unwrap();
|
||||
let scheme = proxy.scheme();
|
||||
|
||||
let version = parse_socks_proxy(scheme, &proxy);
|
||||
let ((host, port), version) = parse_socks_proxy(&proxy).unwrap();
|
||||
assert_eq!(host, "proxy.example.com");
|
||||
assert_eq!(port, 1080);
|
||||
assert!(matches!(version, SocksVersion::V5(None)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_socks5_with_authorization() {
|
||||
let proxy = Url::parse("socks5://username:password@proxy.example.com:1080").unwrap();
|
||||
let scheme = proxy.scheme();
|
||||
|
||||
let version = parse_socks_proxy(scheme, &proxy);
|
||||
let ((host, port), version) = parse_socks_proxy(&proxy).unwrap();
|
||||
assert_eq!(host, "proxy.example.com");
|
||||
assert_eq!(port, 1080);
|
||||
assert!(matches!(
|
||||
version,
|
||||
SocksVersion::V5(Some(Socks5Authorization {
|
||||
@@ -128,4 +158,19 @@ mod tests {
|
||||
}))
|
||||
))
|
||||
}
|
||||
|
||||
/// If parsing the proxy URL fails, we must avoid falling back to an insecure connection.
|
||||
/// SOCKS proxies are often used in contexts where security and privacy are critical,
|
||||
/// so any fallback could expose users to significant risks.
|
||||
#[tokio::test]
|
||||
async fn fails_on_bad_proxy() {
|
||||
// Should fail connecting because http is not a valid Socks proxy scheme
|
||||
let proxy = Url::parse("http://localhost:2313").unwrap();
|
||||
|
||||
let result = connect_socks_proxy_stream(&proxy, ("test", 1080)).await;
|
||||
match result {
|
||||
Err(e) => assert_eq!(e.to_string(), "Parsing proxy url failed"),
|
||||
Ok(_) => panic!("Connecting on bad proxy should fail"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -543,7 +543,7 @@ pub struct MembershipUpdated {
|
||||
|
||||
/// The result of setting a member's role.
|
||||
#[derive(Debug)]
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum SetMemberRoleResult {
|
||||
InviteUpdated(Channel),
|
||||
MembershipUpdated(MembershipUpdated),
|
||||
|
||||
@@ -36,7 +36,6 @@ use util::{ResultExt as _, maybe};
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const REVISION: Option<&'static str> = option_env!("GITHUB_SHA");
|
||||
|
||||
#[expect(clippy::result_large_err)]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
if let Err(error) = env::load_dotenv() {
|
||||
|
||||
@@ -36,8 +36,8 @@ fn room_participants(room: &Entity<Room>, cx: &mut TestAppContext) -> RoomPartic
|
||||
room.read_with(cx, |room, _| {
|
||||
let mut remote = room
|
||||
.remote_participants()
|
||||
.values()
|
||||
.map(|participant| participant.user.github_login.clone())
|
||||
.iter()
|
||||
.map(|(_, participant)| participant.user.github_login.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let mut pending = room
|
||||
.pending_participants()
|
||||
|
||||
@@ -7,11 +7,11 @@ use crate::notifications::collab_notification::CollabNotification;
|
||||
pub struct CollabNotificationStory;
|
||||
|
||||
impl Render for CollabNotificationStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let window_container = |width, height| div().w(px(width)).h(px(height));
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<CollabNotification>(cx))
|
||||
Story::container()
|
||||
.child(Story::title_for::<CollabNotification>())
|
||||
.child(
|
||||
StorySection::new().child(StoryItem::new(
|
||||
"Incoming Call Notification",
|
||||
|
||||
@@ -21,8 +21,8 @@ use project::{
|
||||
use ui::{
|
||||
App, Clickable, Color, Context, Div, Icon, IconButton, IconName, Indicator, InteractiveElement,
|
||||
IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, Render, RenderOnce,
|
||||
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Tooltip, Window,
|
||||
div, h_flex, px, v_flex,
|
||||
Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, Styled, Window, div,
|
||||
h_flex, px, v_flex,
|
||||
};
|
||||
use util::{ResultExt, maybe};
|
||||
use workspace::Workspace;
|
||||
@@ -259,11 +259,6 @@ impl LineBreakpoint {
|
||||
dir, name, line
|
||||
)))
|
||||
.cursor_pointer()
|
||||
.tooltip(Tooltip::text(if breakpoint.state.is_enabled() {
|
||||
"Disable Breakpoint"
|
||||
} else {
|
||||
"Enable Breakpoint"
|
||||
}))
|
||||
.on_click({
|
||||
let weak = weak.clone();
|
||||
let path = path.clone();
|
||||
@@ -295,9 +290,6 @@ impl LineBreakpoint {
|
||||
)))
|
||||
.start_slot(indicator)
|
||||
.rounded()
|
||||
.on_secondary_mouse_down(|_, _, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.end_hover_slot(
|
||||
IconButton::new(
|
||||
SharedString::from(format!(
|
||||
@@ -431,20 +423,12 @@ impl ExceptionBreakpoint {
|
||||
self.id
|
||||
)))
|
||||
.rounded()
|
||||
.on_secondary_mouse_down(|_, _, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.start_slot(
|
||||
div()
|
||||
.id(SharedString::from(format!(
|
||||
"exception-breakpoint-ui-item-{}-click-handler",
|
||||
self.id
|
||||
)))
|
||||
.tooltip(Tooltip::text(if self.is_enabled {
|
||||
"Disable Exception Breakpoint"
|
||||
} else {
|
||||
"Enable Exception Breakpoint"
|
||||
}))
|
||||
.on_click(move |_, _, cx| {
|
||||
list.update(cx, |this, cx| {
|
||||
this.session.update(cx, |this, cx| {
|
||||
|
||||
@@ -129,9 +129,6 @@ impl ModuleList {
|
||||
.w_full()
|
||||
.group("")
|
||||
.id(("module-list", ix))
|
||||
.on_any_mouse_down(|_, _, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.when(module.path.is_some(), |this| {
|
||||
this.on_click({
|
||||
let path = module
|
||||
|
||||
@@ -39,6 +39,7 @@ pub struct StackFrameList {
|
||||
_refresh_task: Task<()>,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum StackFrameEntry {
|
||||
Normal(dap::StackFrame),
|
||||
@@ -393,9 +394,6 @@ impl StackFrameList {
|
||||
.when(is_selected_frame, |this| {
|
||||
this.bg(cx.theme().colors().element_hover)
|
||||
})
|
||||
.on_any_mouse_down(|_, _, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.selected_ix = Some(ix);
|
||||
this.activate_selected_entry(window, cx);
|
||||
@@ -483,9 +481,6 @@ impl StackFrameList {
|
||||
.when(is_selected, |this| {
|
||||
this.bg(cx.theme().colors().element_hover)
|
||||
})
|
||||
.on_any_mouse_down(|_, _, cx| {
|
||||
cx.stop_propagation();
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.selected_ix = Some(ix);
|
||||
this.activate_selected_entry(window, cx);
|
||||
|
||||
@@ -40,6 +40,7 @@ pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);
|
||||
pub const MENU_ASIDE_MIN_WIDTH: Pixels = px(260.);
|
||||
pub const MENU_ASIDE_MAX_WIDTH: Pixels = px(500.);
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum CodeContextMenu {
|
||||
Completions(CompletionsMenu),
|
||||
CodeActions(CodeActionsMenu),
|
||||
@@ -927,6 +928,7 @@ impl CodeActionContents {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone)]
|
||||
pub enum CodeActionsItem {
|
||||
Task(TaskSourceKind, ResolvedTask),
|
||||
|
||||
@@ -282,6 +282,7 @@ struct Transform {
|
||||
block: Option<Block>,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone)]
|
||||
pub enum Block {
|
||||
Custom(Arc<CustomBlock>),
|
||||
|
||||
@@ -107,9 +107,9 @@ pub use items::MAX_TAB_TITLE_LEN;
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
||||
CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
|
||||
EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
|
||||
Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
|
||||
CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
|
||||
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
|
||||
TransactionId, TreeSitterOptions, WordsQuery,
|
||||
language_settings::{
|
||||
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
|
||||
all_language_settings, language_settings,
|
||||
@@ -1311,7 +1311,7 @@ pub struct ActiveDiagnosticGroup {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub(crate) enum ActiveDiagnostic {
|
||||
None,
|
||||
All,
|
||||
@@ -3912,7 +3912,7 @@ impl Editor {
|
||||
pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
|
||||
self.transact(window, cx, |this, window, cx| {
|
||||
let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
|
||||
let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
|
||||
let selections = this.selections.all::<usize>(cx);
|
||||
let multi_buffer = this.buffer.read(cx);
|
||||
let buffer = multi_buffer.snapshot(cx);
|
||||
@@ -3920,21 +3920,17 @@ impl Editor {
|
||||
.iter()
|
||||
.map(|selection| {
|
||||
let start_point = selection.start.to_point(&buffer);
|
||||
let mut existing_indent =
|
||||
let mut indent =
|
||||
buffer.indent_size_for_line(MultiBufferRow(start_point.row));
|
||||
existing_indent.len = cmp::min(existing_indent.len, start_point.column);
|
||||
indent.len = cmp::min(indent.len, start_point.column);
|
||||
let start = selection.start;
|
||||
let end = selection.end;
|
||||
let selection_is_empty = start == end;
|
||||
let language_scope = buffer.language_scope_at(start);
|
||||
let (
|
||||
comment_delimiter,
|
||||
doc_delimiter,
|
||||
insert_extra_newline,
|
||||
indent_on_newline,
|
||||
indent_on_extra_newline,
|
||||
) = if let Some(language) = &language_scope {
|
||||
let mut insert_extra_newline =
|
||||
let (comment_delimiter, insert_extra_newline) = if let Some(language) =
|
||||
&language_scope
|
||||
{
|
||||
let insert_extra_newline =
|
||||
insert_extra_newline_brackets(&buffer, start..end, language)
|
||||
|| insert_extra_newline_tree_sitter(&buffer, start..end);
|
||||
|
||||
@@ -3954,208 +3950,63 @@ impl Editor {
|
||||
let (snapshot, range) =
|
||||
buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
|
||||
|
||||
let num_of_whitespaces = snapshot
|
||||
.chars_for_range(range.clone())
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.count();
|
||||
let mut index_of_first_non_whitespace = 0;
|
||||
let comment_candidate = snapshot
|
||||
.chars_for_range(range)
|
||||
.skip(num_of_whitespaces)
|
||||
.skip_while(|c| {
|
||||
let should_skip = c.is_whitespace();
|
||||
if should_skip {
|
||||
index_of_first_non_whitespace += 1;
|
||||
}
|
||||
should_skip
|
||||
})
|
||||
.take(max_len_of_delimiter)
|
||||
.collect::<String>();
|
||||
let (delimiter, trimmed_len) =
|
||||
delimiters.iter().find_map(|delimiter| {
|
||||
let trimmed = delimiter.trim_end();
|
||||
if comment_candidate.starts_with(trimmed) {
|
||||
Some((delimiter, trimmed.len()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
let comment_prefix = delimiters.iter().find(|comment_prefix| {
|
||||
comment_candidate.starts_with(comment_prefix.as_ref())
|
||||
})?;
|
||||
let cursor_is_placed_after_comment_marker =
|
||||
num_of_whitespaces + trimmed_len <= start_point.column as usize;
|
||||
index_of_first_non_whitespace + comment_prefix.len()
|
||||
<= start_point.column as usize;
|
||||
if cursor_is_placed_after_comment_marker {
|
||||
Some(delimiter.clone())
|
||||
Some(comment_prefix.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let mut indent_on_newline = IndentSize::spaces(0);
|
||||
let mut indent_on_extra_newline = IndentSize::spaces(0);
|
||||
|
||||
let doc_delimiter = maybe!({
|
||||
if !selection_is_empty {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !multi_buffer.language_settings(cx).extend_comment_on_newline {
|
||||
return None;
|
||||
}
|
||||
|
||||
let DocumentationConfig {
|
||||
start: start_tag,
|
||||
end: end_tag,
|
||||
prefix: delimiter,
|
||||
tab_size: len,
|
||||
} = language.documentation()?;
|
||||
|
||||
let (snapshot, range) =
|
||||
buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
|
||||
|
||||
let num_of_whitespaces = snapshot
|
||||
.chars_for_range(range.clone())
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.count();
|
||||
|
||||
let cursor_is_after_start_tag = {
|
||||
let start_tag_len = start_tag.len();
|
||||
let start_tag_line = snapshot
|
||||
.chars_for_range(range.clone())
|
||||
.skip(num_of_whitespaces)
|
||||
.take(start_tag_len)
|
||||
.collect::<String>();
|
||||
if start_tag_line.starts_with(start_tag.as_ref()) {
|
||||
num_of_whitespaces + start_tag_len
|
||||
<= start_point.column as usize
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let cursor_is_after_delimiter = {
|
||||
let delimiter_trim = delimiter.trim_end();
|
||||
let delimiter_line = snapshot
|
||||
.chars_for_range(range.clone())
|
||||
.skip(num_of_whitespaces)
|
||||
.take(delimiter_trim.len())
|
||||
.collect::<String>();
|
||||
if delimiter_line.starts_with(delimiter_trim) {
|
||||
num_of_whitespaces + delimiter_trim.len()
|
||||
<= start_point.column as usize
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let cursor_is_before_end_tag_if_exists = {
|
||||
let num_of_whitespaces_rev = snapshot
|
||||
.reversed_chars_for_range(range.clone())
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.count();
|
||||
let mut line_iter = snapshot
|
||||
.reversed_chars_for_range(range)
|
||||
.skip(num_of_whitespaces_rev);
|
||||
let end_tag_exists = end_tag
|
||||
.chars()
|
||||
.rev()
|
||||
.all(|char| line_iter.next() == Some(char));
|
||||
if end_tag_exists {
|
||||
let max_point = snapshot.line_len(start_point.row) as usize;
|
||||
let ordering = (num_of_whitespaces_rev
|
||||
+ end_tag.len()
|
||||
+ start_point.column as usize)
|
||||
.cmp(&max_point);
|
||||
let cursor_is_before_end_tag =
|
||||
ordering != Ordering::Greater;
|
||||
if cursor_is_after_start_tag {
|
||||
if cursor_is_before_end_tag {
|
||||
insert_extra_newline = true;
|
||||
}
|
||||
let cursor_is_at_start_of_end_tag =
|
||||
ordering == Ordering::Equal;
|
||||
if cursor_is_at_start_of_end_tag {
|
||||
indent_on_extra_newline.len = (*len).into();
|
||||
}
|
||||
}
|
||||
cursor_is_before_end_tag
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
if (cursor_is_after_start_tag || cursor_is_after_delimiter)
|
||||
&& cursor_is_before_end_tag_if_exists
|
||||
{
|
||||
if cursor_is_after_start_tag {
|
||||
indent_on_newline.len = (*len).into();
|
||||
}
|
||||
Some(delimiter.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
(
|
||||
comment_delimiter,
|
||||
doc_delimiter,
|
||||
insert_extra_newline,
|
||||
indent_on_newline,
|
||||
indent_on_extra_newline,
|
||||
)
|
||||
(comment_delimiter, insert_extra_newline)
|
||||
} else {
|
||||
(
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
IndentSize::default(),
|
||||
IndentSize::default(),
|
||||
)
|
||||
(None, false)
|
||||
};
|
||||
|
||||
let prevent_auto_indent = doc_delimiter.is_some();
|
||||
let delimiter = comment_delimiter.or(doc_delimiter);
|
||||
|
||||
let capacity_for_delimiter =
|
||||
delimiter.as_deref().map(str::len).unwrap_or_default();
|
||||
let mut new_text = String::with_capacity(
|
||||
1 + capacity_for_delimiter
|
||||
+ existing_indent.len as usize
|
||||
+ indent_on_newline.len as usize
|
||||
+ indent_on_extra_newline.len as usize,
|
||||
);
|
||||
let capacity_for_delimiter = comment_delimiter
|
||||
.as_deref()
|
||||
.map(str::len)
|
||||
.unwrap_or_default();
|
||||
let mut new_text =
|
||||
String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
|
||||
new_text.push('\n');
|
||||
new_text.extend(existing_indent.chars());
|
||||
new_text.extend(indent_on_newline.chars());
|
||||
|
||||
if let Some(delimiter) = &delimiter {
|
||||
new_text.extend(indent.chars());
|
||||
if let Some(delimiter) = &comment_delimiter {
|
||||
new_text.push_str(delimiter);
|
||||
}
|
||||
|
||||
if insert_extra_newline {
|
||||
new_text.push('\n');
|
||||
new_text.extend(existing_indent.chars());
|
||||
new_text.extend(indent_on_extra_newline.chars());
|
||||
new_text = new_text.repeat(2);
|
||||
}
|
||||
|
||||
let anchor = buffer.anchor_after(end);
|
||||
let new_selection = selection.map(|_| anchor);
|
||||
(
|
||||
((start..end, new_text), prevent_auto_indent),
|
||||
(start..end, new_text),
|
||||
(insert_extra_newline, new_selection),
|
||||
)
|
||||
})
|
||||
.unzip()
|
||||
};
|
||||
|
||||
let mut auto_indent_edits = Vec::new();
|
||||
let mut edits = Vec::new();
|
||||
for (edit, prevent_auto_indent) in edits_with_flags {
|
||||
if prevent_auto_indent {
|
||||
edits.push(edit);
|
||||
} else {
|
||||
auto_indent_edits.push(edit);
|
||||
}
|
||||
}
|
||||
if !edits.is_empty() {
|
||||
this.edit(edits, cx);
|
||||
}
|
||||
if !auto_indent_edits.is_empty() {
|
||||
this.edit_with_autoindent(auto_indent_edits, cx);
|
||||
}
|
||||
|
||||
this.edit_with_autoindent(edits, cx);
|
||||
let buffer = this.buffer.read(cx).snapshot(cx);
|
||||
let new_selections = selection_info
|
||||
let new_selections = selection_fixup_info
|
||||
.into_iter()
|
||||
.map(|(extra_newline_inserted, new_selection)| {
|
||||
let mut cursor = new_selection.end.to_point(&buffer);
|
||||
@@ -18067,6 +17918,10 @@ impl Editor {
|
||||
.and_then(|lines| lines.last().map(|line| line.range.start));
|
||||
|
||||
self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
|
||||
let snapshot = editor
|
||||
.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
|
||||
.ok()?;
|
||||
|
||||
let inline_values = editor
|
||||
.update(cx, |editor, cx| {
|
||||
let Some(current_execution_position) = current_execution_position else {
|
||||
@@ -18094,40 +17949,22 @@ impl Editor {
|
||||
.context("refreshing debugger inlays")
|
||||
.log_err()?;
|
||||
|
||||
let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
|
||||
|
||||
for (buffer_id, inline_value) in inline_values
|
||||
.into_iter()
|
||||
.filter_map(|hint| Some((hint.position.buffer_id?, hint)))
|
||||
{
|
||||
buffer_inline_values
|
||||
.entry(buffer_id)
|
||||
.or_default()
|
||||
.push(inline_value);
|
||||
}
|
||||
|
||||
let (excerpt_id, buffer_id) = snapshot
|
||||
.excerpts()
|
||||
.next()
|
||||
.map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
let mut new_inlays = Vec::default();
|
||||
|
||||
for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
buffer_inline_values
|
||||
.get(&buffer_id)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.for_each(|hint| {
|
||||
let inlay = Inlay::debugger_hint(
|
||||
post_inc(&mut editor.next_inlay_id),
|
||||
Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
|
||||
hint.text(),
|
||||
);
|
||||
|
||||
new_inlays.push(inlay);
|
||||
});
|
||||
}
|
||||
|
||||
let new_inlays = inline_values
|
||||
.into_iter()
|
||||
.map(|debugger_value| {
|
||||
Inlay::debugger_hint(
|
||||
post_inc(&mut editor.next_inlay_id),
|
||||
Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
|
||||
debugger_value.text(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
|
||||
std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
|
||||
|
||||
@@ -20351,8 +20188,8 @@ impl EditorSnapshot {
|
||||
let participant_indices = collaboration_hub.user_participant_indices(cx);
|
||||
let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
|
||||
let collaborators_by_replica_id = collaborators_by_peer_id
|
||||
.values()
|
||||
.map(|collaborator| (collaborator.replica_id, collaborator))
|
||||
.iter()
|
||||
.map(|(_, collaborator)| (collaborator.replica_id, collaborator))
|
||||
.collect::<HashMap<_, _>>();
|
||||
self.buffer_snapshot
|
||||
.selections_in_range(range, false)
|
||||
|
||||
@@ -2755,7 +2755,7 @@ async fn test_newline_comments(cx: &mut TestAppContext) {
|
||||
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
line_comments: vec!["// ".into()],
|
||||
line_comments: vec!["//".into()],
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
@@ -2770,29 +2770,7 @@ async fn test_newline_comments(cx: &mut TestAppContext) {
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
// Foo
|
||||
// ˇ
|
||||
"});
|
||||
// Ensure that we add comment prefix when existing line contains space
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(
|
||||
indoc! {"
|
||||
// Foo
|
||||
//s
|
||||
// ˇ
|
||||
"}
|
||||
.replace("s", " ") // s is used as space placeholder to prevent format on save
|
||||
.as_str(),
|
||||
);
|
||||
// Ensure that we add comment prefix when existing line does not contain space
|
||||
cx.set_state(indoc! {"
|
||||
// Foo
|
||||
//ˇ
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
// Foo
|
||||
//
|
||||
// ˇ
|
||||
"});
|
||||
// Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
|
||||
cx.set_state(indoc! {"
|
||||
@@ -2819,177 +2797,6 @@ async fn test_newline_comments(cx: &mut TestAppContext) {
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
|
||||
init_test(cx, |settings| {
|
||||
settings.defaults.tab_size = NonZeroU32::new(4)
|
||||
});
|
||||
|
||||
let language = Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
documentation: Some(language::DocumentationConfig {
|
||||
start: "/**".into(),
|
||||
end: "*/".into(),
|
||||
prefix: "* ".into(),
|
||||
tab_size: NonZeroU32::new(1).unwrap(),
|
||||
}),
|
||||
..LanguageConfig::default()
|
||||
},
|
||||
None,
|
||||
));
|
||||
{
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
|
||||
cx.set_state(indoc! {"
|
||||
/**ˇ
|
||||
"});
|
||||
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/**
|
||||
* ˇ
|
||||
"});
|
||||
// Ensure that if cursor is before the comment start,
|
||||
// we do not actually insert a comment prefix.
|
||||
cx.set_state(indoc! {"
|
||||
ˇ/**
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
|
||||
ˇ/**
|
||||
"});
|
||||
// Ensure that if cursor is between it doesn't add comment prefix.
|
||||
cx.set_state(indoc! {"
|
||||
/*ˇ*
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/*
|
||||
ˇ*
|
||||
"});
|
||||
// Ensure that if suffix exists on same line after cursor it adds new line.
|
||||
cx.set_state(indoc! {"
|
||||
/**ˇ*/
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/**
|
||||
* ˇ
|
||||
*/
|
||||
"});
|
||||
// Ensure that if suffix exists on same line after cursor with space it adds new line.
|
||||
cx.set_state(indoc! {"
|
||||
/**ˇ */
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/**
|
||||
* ˇ
|
||||
*/
|
||||
"});
|
||||
// Ensure that if suffix exists on same line after cursor with space it adds new line.
|
||||
cx.set_state(indoc! {"
|
||||
/** ˇ*/
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(
|
||||
indoc! {"
|
||||
/**s
|
||||
* ˇ
|
||||
*/
|
||||
"}
|
||||
.replace("s", " ") // s is used as space placeholder to prevent format on save
|
||||
.as_str(),
|
||||
);
|
||||
// Ensure that delimiter space is preserved when newline on already
|
||||
// spaced delimiter.
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(
|
||||
indoc! {"
|
||||
/**s
|
||||
*s
|
||||
* ˇ
|
||||
*/
|
||||
"}
|
||||
.replace("s", " ") // s is used as space placeholder to prevent format on save
|
||||
.as_str(),
|
||||
);
|
||||
// Ensure that delimiter space is preserved when space is not
|
||||
// on existing delimiter.
|
||||
cx.set_state(indoc! {"
|
||||
/**
|
||||
*ˇ
|
||||
*/
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/**
|
||||
*
|
||||
* ˇ
|
||||
*/
|
||||
"});
|
||||
// Ensure that if suffix exists on same line after cursor it
|
||||
// doesn't add extra new line if prefix is not on same line.
|
||||
cx.set_state(indoc! {"
|
||||
/**
|
||||
ˇ*/
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/**
|
||||
|
||||
ˇ*/
|
||||
"});
|
||||
// Ensure that it detects suffix after existing prefix.
|
||||
cx.set_state(indoc! {"
|
||||
/**ˇ/
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/**
|
||||
ˇ/
|
||||
"});
|
||||
// Ensure that if suffix exists on same line before
|
||||
// cursor it does not add comment prefix.
|
||||
cx.set_state(indoc! {"
|
||||
/** */ˇ
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/** */
|
||||
ˇ
|
||||
"});
|
||||
// Ensure that if suffix exists on same line before
|
||||
// cursor it does not add comment prefix.
|
||||
cx.set_state(indoc! {"
|
||||
/**
|
||||
*
|
||||
*/ˇ
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/**
|
||||
*
|
||||
*/
|
||||
ˇ
|
||||
"});
|
||||
}
|
||||
// Ensure that comment continuations can be disabled.
|
||||
update_test_language_settings(cx, |settings| {
|
||||
settings.defaults.extend_comment_on_newline = Some(false);
|
||||
});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state(indoc! {"
|
||||
/**ˇ
|
||||
"});
|
||||
cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
/**
|
||||
ˇ
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_insert_with_old_selections(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
@@ -6393,6 +6393,7 @@ pub(crate) struct LineWithInvisibles {
|
||||
font_size: Pixels,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum LineFragment {
|
||||
Text(ShapedLine),
|
||||
Element {
|
||||
|
||||
@@ -33,7 +33,6 @@ serde_json.workspace = true
|
||||
task.workspace = true
|
||||
toml.workspace = true
|
||||
util.workspace = true
|
||||
wasi-preview1-component-adapter-provider.workspace = true
|
||||
wasm-encoder.workspace = true
|
||||
wasmparser.workspace = true
|
||||
wit-component.workspace = true
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use futures::AsyncReadExt;
|
||||
use futures::io::BufReader;
|
||||
use heck::ToSnakeCase;
|
||||
use http_client::{self, AsyncBody, HttpClient};
|
||||
@@ -14,7 +15,6 @@ use std::{
|
||||
process::Stdio,
|
||||
sync::Arc,
|
||||
};
|
||||
use wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER;
|
||||
use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _};
|
||||
use wasmparser::Parser;
|
||||
use wit_component::ComponentEncoder;
|
||||
@@ -26,6 +26,7 @@ use wit_component::ComponentEncoder;
|
||||
/// Once Rust 1.78 is released, there will be a `wasm32-wasip2` target available, so we will
|
||||
/// not need the adapter anymore.
|
||||
const RUST_TARGET: &str = "wasm32-wasip1";
|
||||
const WASI_ADAPTER_URL: &str = "https://github.com/bytecodealliance/wasmtime/releases/download/v18.0.2/wasi_snapshot_preview1.reactor.wasm";
|
||||
|
||||
/// Compiling Tree-sitter parsers from C to WASM requires Clang 17, and a WASM build of libc
|
||||
/// and clang's runtime library. The `wasi-sdk` provides these binaries.
|
||||
@@ -136,6 +137,7 @@ impl ExtensionBuilder {
|
||||
options: CompileExtensionOptions,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
self.install_rust_wasm_target_if_needed()?;
|
||||
let adapter_bytes = self.install_wasi_preview1_adapter_if_needed().await?;
|
||||
|
||||
let cargo_toml_content = fs::read_to_string(extension_dir.join("Cargo.toml"))?;
|
||||
let cargo_toml: CargoToml = toml::from_str(&cargo_toml_content)?;
|
||||
@@ -184,10 +186,7 @@ impl ExtensionBuilder {
|
||||
|
||||
let mut encoder = ComponentEncoder::default()
|
||||
.module(&wasm_bytes)?
|
||||
.adapter(
|
||||
"wasi_snapshot_preview1",
|
||||
WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER,
|
||||
)
|
||||
.adapter("wasi_snapshot_preview1", &adapter_bytes)
|
||||
.context("failed to load adapter module")?
|
||||
.validate(true);
|
||||
|
||||
@@ -396,6 +395,38 @@ impl ExtensionBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn install_wasi_preview1_adapter_if_needed(&self) -> Result<Vec<u8>> {
|
||||
let cache_path = self.cache_dir.join("wasi_snapshot_preview1.reactor.wasm");
|
||||
if let Ok(content) = fs::read(&cache_path) {
|
||||
if Parser::is_core_wasm(&content) {
|
||||
return Ok(content);
|
||||
}
|
||||
}
|
||||
|
||||
fs::remove_file(&cache_path).ok();
|
||||
|
||||
log::info!(
|
||||
"downloading wasi adapter module to {}",
|
||||
cache_path.display()
|
||||
);
|
||||
let mut response = self
|
||||
.http
|
||||
.get(WASI_ADAPTER_URL, AsyncBody::default(), true)
|
||||
.await?;
|
||||
|
||||
let mut content = Vec::new();
|
||||
let mut body = BufReader::new(response.body_mut());
|
||||
body.read_to_end(&mut content).await?;
|
||||
|
||||
fs::write(&cache_path, &content)
|
||||
.with_context(|| format!("failed to save file {}", cache_path.display()))?;
|
||||
|
||||
if !Parser::is_core_wasm(&content) {
|
||||
bail!("downloaded wasi adapter is invalid");
|
||||
}
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
async fn install_wasi_sdk_if_needed(&self) -> Result<PathBuf> {
|
||||
let url = if let Some(asset_name) = WASI_SDK_ASSET_NAME {
|
||||
format!("{WASI_SDK_URL}/{asset_name}")
|
||||
|
||||
@@ -324,8 +324,7 @@ fn update_conflict_highlighting(
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
log::debug!("update conflict highlighting for {conflict:?}");
|
||||
let theme = cx.theme().clone();
|
||||
let colors = theme.colors();
|
||||
|
||||
let outer_start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, conflict.range.start)
|
||||
.unwrap();
|
||||
@@ -345,11 +344,9 @@ fn update_conflict_highlighting(
|
||||
.anchor_in_excerpt(excerpt_id, conflict.theirs.end)
|
||||
.unwrap();
|
||||
|
||||
let ours_background = colors.version_control_conflict_ours_background;
|
||||
let ours_marker = colors.version_control_conflict_ours_marker_background;
|
||||
let theirs_background = colors.version_control_conflict_theirs_background;
|
||||
let theirs_marker = colors.version_control_conflict_theirs_marker_background;
|
||||
let divider_background = colors.version_control_conflict_divider_background;
|
||||
let ours_background = cx.theme().colors().version_control_conflict_marker_ours;
|
||||
let theirs_background = cx.theme().colors().version_control_conflict_marker_theirs;
|
||||
let divider_background = cx.theme().colors().version_control_conflict_marker_border;
|
||||
|
||||
let options = RowHighlightOptions {
|
||||
include_gutter: false,
|
||||
@@ -357,14 +354,14 @@ fn update_conflict_highlighting(
|
||||
};
|
||||
|
||||
// Prevent diff hunk highlighting within the entire conflict region.
|
||||
editor.highlight_rows::<ConflictsOuter>(
|
||||
outer_start..outer_end,
|
||||
divider_background,
|
||||
editor.highlight_rows::<ConflictsOuter>(outer_start..outer_end, theirs_background, options, cx);
|
||||
editor.highlight_rows::<ConflictsOurs>(our_start..our_end, ours_background, options, cx);
|
||||
editor.highlight_rows::<ConflictsOursMarker>(
|
||||
outer_start..our_start,
|
||||
ours_background,
|
||||
options,
|
||||
cx,
|
||||
);
|
||||
editor.highlight_rows::<ConflictsOurs>(our_start..our_end, ours_background, options, cx);
|
||||
editor.highlight_rows::<ConflictsOursMarker>(outer_start..our_start, ours_marker, options, cx);
|
||||
editor.highlight_rows::<ConflictsTheirs>(
|
||||
their_start..their_end,
|
||||
theirs_background,
|
||||
@@ -373,7 +370,7 @@ fn update_conflict_highlighting(
|
||||
);
|
||||
editor.highlight_rows::<ConflictsTheirsMarker>(
|
||||
their_end..outer_end,
|
||||
theirs_marker,
|
||||
theirs_background,
|
||||
options,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -257,10 +257,6 @@ path = "examples/image/image.rs"
|
||||
name = "input"
|
||||
path = "examples/input.rs"
|
||||
|
||||
[[example]]
|
||||
name = "on_window_close_quit"
|
||||
path = "examples/on_window_close_quit.rs"
|
||||
|
||||
[[example]]
|
||||
name = "opacity"
|
||||
path = "examples/opacity.rs"
|
||||
@@ -281,10 +277,6 @@ path = "examples/shadow.rs"
|
||||
name = "svg"
|
||||
path = "examples/svg/svg.rs"
|
||||
|
||||
[[example]]
|
||||
name = "text"
|
||||
path = "examples/text.rs"
|
||||
|
||||
[[example]]
|
||||
name = "text_wrapper"
|
||||
path = "examples/text_wrapper.rs"
|
||||
@@ -296,3 +288,7 @@ path = "examples/uniform_list.rs"
|
||||
[[example]]
|
||||
name = "window_shadow"
|
||||
path = "examples/window_shadow.rs"
|
||||
|
||||
[[example]]
|
||||
name = "on_window_close_quit"
|
||||
path = "examples/on_window_close_quit.rs"
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use gpui::{
|
||||
App, Application, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, button,
|
||||
div, prelude::*, px, rgb, size,
|
||||
App, Application, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div,
|
||||
prelude::*, px, rgb, size,
|
||||
};
|
||||
|
||||
struct HelloWorld {
|
||||
text: SharedString,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Render for HelloWorld {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
@@ -24,14 +23,6 @@ impl Render for HelloWorld {
|
||||
.text_xl()
|
||||
.text_color(rgb(0xffffff))
|
||||
.child(format!("Hello, {}!", &self.text))
|
||||
.child(
|
||||
button("counter-button")
|
||||
.on_click(Box::new(cx.listener(move |this, _, _, cx| {
|
||||
this.count += 1;
|
||||
cx.notify();
|
||||
})))
|
||||
.child(div().child(format!("Count: {}", &self.count))),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
@@ -106,7 +97,6 @@ fn main() {
|
||||
|_, cx| {
|
||||
cx.new(|_| HelloWorld {
|
||||
text: "World".into(),
|
||||
count: 0,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
AbsoluteLength, App, Application, Context, DefiniteLength, ElementId, Global, Hsla, Menu,
|
||||
SharedString, TextStyle, TitlebarOptions, Window, WindowBounds, WindowOptions, bounds,
|
||||
colors::DefaultColors, div, point, prelude::*, px, relative, rgb, size,
|
||||
};
|
||||
use std::iter;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextContext {
|
||||
font_size: f32,
|
||||
line_height: f32,
|
||||
type_scale: f32,
|
||||
}
|
||||
|
||||
impl Default for TextContext {
|
||||
fn default() -> Self {
|
||||
TextContext {
|
||||
font_size: 16.0,
|
||||
line_height: 1.3,
|
||||
type_scale: 1.33,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextContext {
|
||||
pub fn get_global(cx: &App) -> &Arc<TextContext> {
|
||||
&cx.global::<GlobalTextContext>().0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GlobalTextContext(pub Arc<TextContext>);
|
||||
|
||||
impl Deref for GlobalTextContext {
|
||||
type Target = Arc<TextContext>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for GlobalTextContext {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Global for GlobalTextContext {}
|
||||
|
||||
pub trait ActiveTextContext {
|
||||
fn text_context(&self) -> &Arc<TextContext>;
|
||||
}
|
||||
|
||||
impl ActiveTextContext for App {
|
||||
fn text_context(&self) -> &Arc<TextContext> {
|
||||
&self.global::<GlobalTextContext>().0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct SpecimenTheme {
|
||||
pub bg: Hsla,
|
||||
pub fg: Hsla,
|
||||
}
|
||||
|
||||
impl Default for SpecimenTheme {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bg: gpui::white(),
|
||||
fg: gpui::black(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecimenTheme {
|
||||
pub fn invert(&self) -> Self {
|
||||
Self {
|
||||
bg: self.fg,
|
||||
fg: self.bg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, IntoElement)]
|
||||
struct Specimen {
|
||||
id: ElementId,
|
||||
scale: f32,
|
||||
text_style: Option<TextStyle>,
|
||||
string: SharedString,
|
||||
invert: bool,
|
||||
}
|
||||
|
||||
impl Specimen {
|
||||
pub fn new(id: usize) -> Self {
|
||||
let string = SharedString::new_static("The quick brown fox jumps over the lazy dog");
|
||||
let id_string = format!("specimen-{}", id);
|
||||
let id = ElementId::Name(id_string.into());
|
||||
Self {
|
||||
id,
|
||||
scale: 1.0,
|
||||
text_style: None,
|
||||
string,
|
||||
invert: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invert(mut self) -> Self {
|
||||
self.invert = !self.invert;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn scale(mut self, scale: f32) -> Self {
|
||||
self.scale = scale;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Specimen {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let rem_size = window.rem_size();
|
||||
let scale = self.scale;
|
||||
let global_style = cx.text_context();
|
||||
|
||||
let style_override = self.text_style;
|
||||
|
||||
let mut font_size = global_style.font_size;
|
||||
let mut line_height = global_style.line_height;
|
||||
|
||||
if let Some(style_override) = style_override {
|
||||
font_size = style_override.font_size.to_pixels(rem_size).0;
|
||||
line_height = match style_override.line_height {
|
||||
DefiniteLength::Absolute(absolute_len) => match absolute_len {
|
||||
AbsoluteLength::Rems(absolute_len) => absolute_len.to_pixels(rem_size).0,
|
||||
AbsoluteLength::Pixels(absolute_len) => absolute_len.0,
|
||||
},
|
||||
DefiniteLength::Fraction(value) => value,
|
||||
};
|
||||
}
|
||||
|
||||
let mut theme = SpecimenTheme::default();
|
||||
|
||||
if self.invert {
|
||||
theme = theme.invert();
|
||||
}
|
||||
|
||||
div()
|
||||
.id(self.id)
|
||||
.bg(theme.bg)
|
||||
.text_color(theme.fg)
|
||||
.text_size(px(font_size * scale))
|
||||
.line_height(relative(line_height))
|
||||
.p(px(10.0))
|
||||
.child(self.string.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, IntoElement)]
|
||||
struct CharacterGrid {
|
||||
scale: f32,
|
||||
invert: bool,
|
||||
text_style: Option<TextStyle>,
|
||||
}
|
||||
|
||||
impl CharacterGrid {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scale: 1.0,
|
||||
invert: false,
|
||||
text_style: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale(mut self, scale: f32) -> Self {
|
||||
self.scale = scale;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for CharacterGrid {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let mut theme = SpecimenTheme::default();
|
||||
|
||||
if self.invert {
|
||||
theme = theme.invert();
|
||||
}
|
||||
|
||||
let characters = vec![
|
||||
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G",
|
||||
"H", "I", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
|
||||
"Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "p", "q",
|
||||
"r", "s", "t", "u", "v", "w", "x", "y", "z", "ẞ", "ſ", "ß", "ð", "Þ", "þ", "α", "β",
|
||||
"Γ", "γ", "Δ", "δ", "η", "θ", "ι", "κ", "Λ", "λ", "μ", "ν", "ξ", "π", "τ", "υ", "φ",
|
||||
"χ", "ψ", "∂", "а", "в", "Ж", "ж", "З", "з", "К", "к", "л", "м", "Н", "н", "Р", "р",
|
||||
"У", "у", "ф", "ч", "ь", "ы", "Э", "э", "Я", "я", "ij", "öẋ", ".,", "⣝⣑", "~", "*",
|
||||
"_", "^", "`", "'", "(", "{", "«", "#", "&", "@", "$", "¢", "%", "|", "?", "¶", "µ",
|
||||
"❮", "<=", "!=", "==", "--", "++", "=>", "->",
|
||||
];
|
||||
|
||||
let columns = 11;
|
||||
let rows = characters.len().div_ceil(columns);
|
||||
|
||||
let grid_rows = (0..rows).map(|row_idx| {
|
||||
let start_idx = row_idx * columns;
|
||||
let end_idx = (start_idx + columns).min(characters.len());
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.children((start_idx..end_idx).map(|i| {
|
||||
div()
|
||||
.text_center()
|
||||
.size(px(62.))
|
||||
.bg(theme.bg)
|
||||
.text_color(theme.fg)
|
||||
.text_size(px(24.0))
|
||||
.line_height(relative(1.0))
|
||||
.child(characters[i])
|
||||
}))
|
||||
.when(end_idx - start_idx < columns, |d| {
|
||||
d.children(
|
||||
iter::repeat_with(|| div().flex_1()).take(columns - (end_idx - start_idx)),
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
div().p_4().gap_2().flex().flex_col().children(grid_rows)
|
||||
}
|
||||
}
|
||||
|
||||
struct TextExample {
|
||||
next_id: usize,
|
||||
}
|
||||
|
||||
impl TextExample {
|
||||
fn next_id(&mut self) -> usize {
|
||||
self.next_id += 1;
|
||||
self.next_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextExample {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let tcx = cx.text_context();
|
||||
let colors = cx.default_colors().clone();
|
||||
|
||||
let type_scale = tcx.type_scale;
|
||||
|
||||
let step_down_2 = 1.0 / (type_scale * type_scale);
|
||||
let step_down_1 = 1.0 / type_scale;
|
||||
let base = 1.0;
|
||||
let step_up_1 = base * type_scale;
|
||||
let step_up_2 = step_up_1 * type_scale;
|
||||
let step_up_3 = step_up_2 * type_scale;
|
||||
let step_up_4 = step_up_3 * type_scale;
|
||||
let step_up_5 = step_up_4 * type_scale;
|
||||
let step_up_6 = step_up_5 * type_scale;
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.child(
|
||||
div()
|
||||
.id("text-example")
|
||||
.overflow_y_scroll()
|
||||
.overflow_x_hidden()
|
||||
.bg(rgb(0xffffff))
|
||||
.size_full()
|
||||
.child(div().child(CharacterGrid::new().scale(base)))
|
||||
.child(
|
||||
div()
|
||||
.child(Specimen::new(self.next_id()).scale(step_down_2))
|
||||
.child(Specimen::new(self.next_id()).scale(step_down_2).invert())
|
||||
.child(Specimen::new(self.next_id()).scale(step_down_1))
|
||||
.child(Specimen::new(self.next_id()).scale(step_down_1).invert())
|
||||
.child(Specimen::new(self.next_id()).scale(base))
|
||||
.child(Specimen::new(self.next_id()).scale(base).invert())
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_1))
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_1).invert())
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_2))
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_2).invert())
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_3))
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_3).invert())
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_4))
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_4).invert())
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_5))
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_5).invert())
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_6))
|
||||
.child(Specimen::new(self.next_id()).scale(step_up_6).invert()),
|
||||
),
|
||||
)
|
||||
.child(div().w(px(240.)).h_full().bg(colors.container))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
cx.set_menus(vec![Menu {
|
||||
name: "GPUI Typography".into(),
|
||||
items: vec![],
|
||||
}]);
|
||||
|
||||
cx.init_colors();
|
||||
cx.set_global(GlobalTextContext(Arc::new(TextContext::default())));
|
||||
|
||||
let window = cx
|
||||
.open_window(
|
||||
WindowOptions {
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: Some("GPUI Typography".into()),
|
||||
..Default::default()
|
||||
}),
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds(
|
||||
point(px(0.0), px(0.0)),
|
||||
size(px(920.), px(720.)),
|
||||
))),
|
||||
..Default::default()
|
||||
},
|
||||
|_window, cx| cx.new(|_cx| TextExample { next_id: 0 }),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
window
|
||||
.update(cx, |_view, _window, cx| {
|
||||
cx.activate(true);
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
@@ -38,9 +38,7 @@ use crate::{
|
||||
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptHandle, PromptLevel,
|
||||
Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString,
|
||||
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
|
||||
WindowHandle, WindowId, WindowInvalidator,
|
||||
colors::{Colors, GlobalColors},
|
||||
current_platform, hash, init_app_menus,
|
||||
WindowHandle, WindowId, WindowInvalidator, current_platform, hash, init_app_menus,
|
||||
};
|
||||
|
||||
mod async_context;
|
||||
@@ -1658,13 +1656,6 @@ impl App {
|
||||
_ = window.drop_image(image);
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes gpui's default colors for the application.
|
||||
///
|
||||
/// These colors can be accessed through `cx.default_colors()`.
|
||||
pub fn init_colors(&mut self) {
|
||||
self.set_global(GlobalColors(Arc::new(Colors::default())));
|
||||
}
|
||||
}
|
||||
|
||||
impl AppContext for App {
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
use crate::{App, Global, Rgba, Window, WindowAppearance, rgb};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The default set of colors for gpui.
|
||||
///
|
||||
/// These are used for styling base components, examples and more.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Colors {
|
||||
/// Text color
|
||||
pub text: Rgba,
|
||||
/// Selected text color
|
||||
pub selected_text: Rgba,
|
||||
/// Background color
|
||||
pub background: Rgba,
|
||||
/// Disabled color
|
||||
pub disabled: Rgba,
|
||||
/// Selected color
|
||||
pub selected: Rgba,
|
||||
/// Border color
|
||||
pub border: Rgba,
|
||||
/// Separator color
|
||||
pub separator: Rgba,
|
||||
/// Container color
|
||||
pub container: Rgba,
|
||||
}
|
||||
|
||||
impl Default for Colors {
|
||||
fn default() -> Self {
|
||||
Self::light()
|
||||
}
|
||||
}
|
||||
|
||||
impl Colors {
|
||||
/// Returns the default colors for the given window appearance.
|
||||
pub fn for_appearance(window: &Window) -> Self {
|
||||
match window.appearance() {
|
||||
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::light(),
|
||||
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::dark(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default dark colors.
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
text: rgb(0xffffff),
|
||||
selected_text: rgb(0xffffff),
|
||||
disabled: rgb(0x565656),
|
||||
selected: rgb(0x2457ca),
|
||||
background: rgb(0x222222),
|
||||
border: rgb(0x000000),
|
||||
separator: rgb(0xd9d9d9),
|
||||
container: rgb(0x262626),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default light colors.
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
text: rgb(0x252525),
|
||||
selected_text: rgb(0xffffff),
|
||||
background: rgb(0xffffff),
|
||||
disabled: rgb(0xb0b0b0),
|
||||
selected: rgb(0x2a63d9),
|
||||
border: rgb(0xd9d9d9),
|
||||
separator: rgb(0xe6e6e6),
|
||||
container: rgb(0xf4f5f5),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get [Colors] from the global state
|
||||
pub fn get_global(cx: &App) -> &Arc<Colors> {
|
||||
&cx.global::<GlobalColors>().0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get [Colors] from the global state
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GlobalColors(pub Arc<Colors>);
|
||||
|
||||
impl Deref for GlobalColors {
|
||||
type Target = Arc<Colors>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Global for GlobalColors {}
|
||||
|
||||
/// Implement this trait to allow global [Color] access via `cx.default_colors()`.
|
||||
pub trait DefaultColors {
|
||||
/// Returns the default [`gpui::Colors`]
|
||||
fn default_colors(&self) -> &Arc<Colors>;
|
||||
}
|
||||
|
||||
impl DefaultColors for App {
|
||||
fn default_colors(&self) -> &Arc<Colors> {
|
||||
&self.global::<GlobalColors>().0
|
||||
}
|
||||
}
|
||||
|
||||
/// The appearance of the base GPUI colors, used to style GPUI elements
|
||||
///
|
||||
/// Varies based on the system's current [`WindowAppearance`].
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DefaultAppearance {
|
||||
/// Use the set of colors for light appearances.
|
||||
#[default]
|
||||
Light,
|
||||
/// Use the set of colors for dark appearances.
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl From<WindowAppearance> for DefaultAppearance {
|
||||
fn from(appearance: WindowAppearance) -> Self {
|
||||
match appearance {
|
||||
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
|
||||
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
#![allow(missing_docs)]
|
||||
use super::{FocusableElement, InteractiveElement, Interactivity, StatefulInteractiveElement};
|
||||
use crate::{
|
||||
AbsoluteLength, AnyElement, App, BorderStyle, ClickEvent, CornersRefinement, Edges,
|
||||
EdgesRefinement, Element, ElementId, GlobalElementId, Hitbox, IntoElement, LayoutId,
|
||||
ParentElement, SharedString, StyleRefinement, Styled, TextStyleRefinement, Window,
|
||||
colors::Colors, px,
|
||||
};
|
||||
use refineable::Refineable;
|
||||
use smallvec::SmallVec;
|
||||
use taffy::FlexDirection;
|
||||
use util::default;
|
||||
|
||||
pub fn button(id: impl Into<ElementId>) -> Button {
|
||||
Button {
|
||||
id: id.into(),
|
||||
interactivity: Interactivity::default(),
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Button {
|
||||
id: ElementId,
|
||||
interactivity: Interactivity,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl Element for Button {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
// Get a LayoutId, an identifier Taffy uses to indicate a unique layout element
|
||||
let layout_id =
|
||||
self.interactivity
|
||||
.request_layout(global_id, window, cx, |style, window, cx| {
|
||||
let mut child_layout_ids = Vec::new();
|
||||
for child in &mut self.children {
|
||||
let child_layout_id = child.request_layout(window, cx);
|
||||
child_layout_ids.push(child_layout_id);
|
||||
}
|
||||
window.request_layout(style, child_layout_ids, cx)
|
||||
});
|
||||
|
||||
// Initialize the layout state
|
||||
let layout_state = ();
|
||||
|
||||
(layout_id, layout_state)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&crate::GlobalElementId>,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
if let Some(handle) = self.interactivity.scroll_anchor.as_ref() {
|
||||
*handle.last_origin.borrow_mut() = bounds.origin - window.element_offset();
|
||||
}
|
||||
let content_size = bounds.size;
|
||||
|
||||
// Prepaint children
|
||||
for child in &mut self.children {
|
||||
child.prepaint(window, cx);
|
||||
}
|
||||
|
||||
self.interactivity.prepaint(
|
||||
global_id,
|
||||
bounds,
|
||||
content_size,
|
||||
window,
|
||||
cx,
|
||||
|_style, _scroll_offset, hitbox, _window, _cx| hitbox,
|
||||
)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&crate::GlobalElementId>,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let colors = Colors::for_appearance(window);
|
||||
let text_style = self.text_style().clone();
|
||||
let abs_px = |number: f32| AbsoluteLength::Pixels(px(number));
|
||||
|
||||
let mut text_style = if let Some(style) = text_style {
|
||||
style.clone()
|
||||
} else {
|
||||
TextStyleRefinement::default()
|
||||
};
|
||||
|
||||
let new_style = StyleRefinement {
|
||||
background: Some(colors.container.into()),
|
||||
text: Some(TextStyleRefinement {
|
||||
color: Some(colors.text.into()),
|
||||
font_size: Some(px(13.).into()),
|
||||
..text_style
|
||||
}),
|
||||
border_color: Some(colors.border.into()),
|
||||
border_style: Some(BorderStyle::Solid),
|
||||
border_widths: EdgesRefinement {
|
||||
top: Some(abs_px(1.)),
|
||||
right: Some(abs_px(1.)),
|
||||
bottom: Some(abs_px(1.)),
|
||||
left: Some(abs_px(1.)),
|
||||
},
|
||||
corner_radii: CornersRefinement {
|
||||
top_left: Some(abs_px(4.)),
|
||||
top_right: Some(abs_px(4.)),
|
||||
bottom_left: Some(abs_px(4.)),
|
||||
bottom_right: Some(abs_px(4.)),
|
||||
},
|
||||
flex_direction: Some(FlexDirection::Row),
|
||||
flex_grow: Some(0.),
|
||||
..StyleRefinement::default()
|
||||
};
|
||||
|
||||
let refined = self.style().refine(&new_style);
|
||||
|
||||
self.interactivity.paint(
|
||||
global_id,
|
||||
bounds,
|
||||
hitbox.as_ref(),
|
||||
window,
|
||||
cx,
|
||||
|style, window, cx| {
|
||||
for child in &mut self.children {
|
||||
child.paint(window, cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for Button {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Button {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.interactivity.base_style
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for Button {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
&mut self.interactivity
|
||||
}
|
||||
}
|
||||
impl StatefulInteractiveElement for Button {}
|
||||
impl FocusableElement for Button {}
|
||||
|
||||
impl ParentElement for Button {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
callback: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
||||
) -> Self {
|
||||
self.interactivity
|
||||
.on_click(move |event, window, cx| callback(event, window, cx));
|
||||
self
|
||||
}
|
||||
}
|
||||
115
crates/gpui/src/elements/common.rs
Normal file
115
crates/gpui/src/elements/common.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use crate::{Hsla, Rgba, WindowAppearance, rgb};
|
||||
|
||||
/// The appearance of the base GPUI colors, used to style GPUI elements
|
||||
///
|
||||
/// Varies based on the system's current [`WindowAppearance`].
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DefaultThemeAppearance {
|
||||
/// Use the set of colors for light appearances.
|
||||
#[default]
|
||||
Light,
|
||||
/// Use the set of colors for dark appearances.
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl From<WindowAppearance> for DefaultThemeAppearance {
|
||||
fn from(appearance: WindowAppearance) -> Self {
|
||||
match appearance {
|
||||
WindowAppearance::Light | WindowAppearance::VibrantLight => Self::Light,
|
||||
WindowAppearance::Dark | WindowAppearance::VibrantDark => Self::Dark,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default colors for the given appearance.
|
||||
pub fn colors(appearance: DefaultThemeAppearance) -> DefaultColors {
|
||||
match appearance {
|
||||
DefaultThemeAppearance::Light => DefaultColors::light(),
|
||||
DefaultThemeAppearance::Dark => DefaultColors::dark(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of colors.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct DefaultColors {
|
||||
text: Rgba,
|
||||
selected_text: Rgba,
|
||||
background: Rgba,
|
||||
disabled: Rgba,
|
||||
selected: Rgba,
|
||||
border: Rgba,
|
||||
separator: Rgba,
|
||||
container: Rgba,
|
||||
}
|
||||
|
||||
impl DefaultColors {
|
||||
/// Returns the default dark colors.
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
text: rgb(0xffffff),
|
||||
selected_text: rgb(0xffffff),
|
||||
disabled: rgb(0x565656),
|
||||
selected: rgb(0x2457ca),
|
||||
background: rgb(0x222222),
|
||||
border: rgb(0x000000),
|
||||
separator: rgb(0xd9d9d9),
|
||||
container: rgb(0x262626),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the default light colors.
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
text: rgb(0x252525),
|
||||
selected_text: rgb(0xffffff),
|
||||
background: rgb(0xffffff),
|
||||
disabled: rgb(0xb0b0b0),
|
||||
selected: rgb(0x2a63d9),
|
||||
border: rgb(0xd9d9d9),
|
||||
separator: rgb(0xe6e6e6),
|
||||
container: rgb(0xf4f5f5),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A default GPUI color.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
|
||||
pub enum DefaultColor {
|
||||
/// Text color
|
||||
Text,
|
||||
/// Selected text color
|
||||
SelectedText,
|
||||
/// Background color
|
||||
Background,
|
||||
/// Disabled color
|
||||
Disabled,
|
||||
/// Selected color
|
||||
Selected,
|
||||
/// Border color
|
||||
Border,
|
||||
/// Separator color
|
||||
Separator,
|
||||
/// Container color
|
||||
Container,
|
||||
}
|
||||
|
||||
impl DefaultColor {
|
||||
/// Returns the RGBA color for the given color type.
|
||||
pub fn color(&self, colors: &DefaultColors) -> Rgba {
|
||||
match self {
|
||||
DefaultColor::Text => colors.text,
|
||||
DefaultColor::SelectedText => colors.selected_text,
|
||||
DefaultColor::Background => colors.background,
|
||||
DefaultColor::Disabled => colors.disabled,
|
||||
DefaultColor::Selected => colors.selected,
|
||||
DefaultColor::Border => colors.border,
|
||||
DefaultColor::Separator => colors.separator,
|
||||
DefaultColor::Container => colors.container,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the HSLA color for the given color type.
|
||||
pub fn hsla(&self, colors: &DefaultColors) -> Hsla {
|
||||
self.color(colors).into()
|
||||
}
|
||||
}
|
||||
@@ -2877,8 +2877,8 @@ where
|
||||
/// Contrary to [ScrollHandle::scroll_to_item], an anchored element does not have to be an immediate child of the parent.
|
||||
#[derive(Clone)]
|
||||
pub struct ScrollAnchor {
|
||||
pub(super) handle: ScrollHandle,
|
||||
pub(super) last_origin: Rc<RefCell<Point<Pixels>>>,
|
||||
handle: ScrollHandle,
|
||||
last_origin: Rc<RefCell<Point<Pixels>>>,
|
||||
}
|
||||
|
||||
impl ScrollAnchor {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod anchored;
|
||||
mod animation;
|
||||
mod button;
|
||||
mod canvas;
|
||||
mod common;
|
||||
mod deferred;
|
||||
mod div;
|
||||
mod image_cache;
|
||||
@@ -14,8 +14,8 @@ mod uniform_list;
|
||||
|
||||
pub use anchored::*;
|
||||
pub use animation::*;
|
||||
pub use button::*;
|
||||
pub use canvas::*;
|
||||
pub use common::*;
|
||||
pub use deferred::*;
|
||||
pub use div::*;
|
||||
pub use image_cache::*;
|
||||
|
||||
@@ -73,8 +73,6 @@ mod asset_cache;
|
||||
mod assets;
|
||||
mod bounds_tree;
|
||||
mod color;
|
||||
/// The default colors used by GPUI.
|
||||
pub mod colors;
|
||||
mod element;
|
||||
mod elements;
|
||||
mod executor;
|
||||
|
||||
@@ -426,8 +426,8 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
|
||||
fn app_path(&self) -> Result<PathBuf> {
|
||||
// get the path of the executable of the current process
|
||||
let app_path = env::current_exe()?;
|
||||
return Ok(app_path);
|
||||
let exe_path = env::current_exe()?;
|
||||
Ok(exe_path)
|
||||
}
|
||||
|
||||
fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) {
|
||||
|
||||
@@ -983,7 +983,7 @@ impl Clipboard {
|
||||
// format that the contents can be converted to
|
||||
format_atoms[0..IMAGE_FORMAT_COUNT].copy_from_slice(&image_format_atoms);
|
||||
format_atoms[IMAGE_FORMAT_COUNT..].copy_from_slice(&text_format_atoms);
|
||||
debug_assert!(!format_atoms.contains(&atom_none));
|
||||
debug_assert!(!format_atoms.iter().any(|&a| a == atom_none));
|
||||
|
||||
let result = self.inner.read(&format_atoms, selection)?;
|
||||
|
||||
|
||||
@@ -137,7 +137,10 @@ fn add_recent_folders(
|
||||
let tasks: IObjectCollection =
|
||||
CoCreateInstance(&EnumerableObjectCollection, None, CLSCTX_INPROC_SERVER)?;
|
||||
|
||||
for folder_path in entries.iter().filter(|path| !removed.contains(path)) {
|
||||
for folder_path in entries
|
||||
.iter()
|
||||
.filter(|path| !is_item_in_array(path, removed))
|
||||
{
|
||||
let argument = HSTRING::from(
|
||||
folder_path
|
||||
.iter()
|
||||
@@ -178,6 +181,11 @@ fn add_recent_folders(
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_item_in_array(item: &SmallVec<[PathBuf; 2]>, removed: &Vec<SmallVec<[PathBuf; 2]>>) -> bool {
|
||||
removed.iter().any(|removed_item| removed_item == item)
|
||||
}
|
||||
|
||||
fn create_shell_link(
|
||||
argument: HSTRING,
|
||||
description: HSTRING,
|
||||
|
||||
@@ -293,35 +293,37 @@ fn handle_mouse_move_msg(
|
||||
start_tracking_mouse(handle, &state_ptr, TME_LEAVE);
|
||||
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
let Some(mut func) = lock.callbacks.input.take() else {
|
||||
return Some(1);
|
||||
};
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
|
||||
let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
|
||||
flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
|
||||
flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
|
||||
flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
|
||||
flags if flags.contains(MK_XBUTTON1) => {
|
||||
Some(MouseButton::Navigate(NavigationDirection::Back))
|
||||
}
|
||||
flags if flags.contains(MK_XBUTTON2) => {
|
||||
Some(MouseButton::Navigate(NavigationDirection::Forward))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let x = lparam.signed_loword() as f32;
|
||||
let y = lparam.signed_hiword() as f32;
|
||||
let input = PlatformInput::MouseMove(MouseMoveEvent {
|
||||
position: logical_point(x, y, scale_factor),
|
||||
pressed_button,
|
||||
modifiers: current_modifiers(),
|
||||
});
|
||||
let handled = !func(input).propagate;
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
|
||||
if handled { Some(0) } else { Some(1) }
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
|
||||
flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
|
||||
flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
|
||||
flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
|
||||
flags if flags.contains(MK_XBUTTON1) => {
|
||||
Some(MouseButton::Navigate(NavigationDirection::Back))
|
||||
}
|
||||
flags if flags.contains(MK_XBUTTON2) => {
|
||||
Some(MouseButton::Navigate(NavigationDirection::Forward))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let x = lparam.signed_loword() as f32;
|
||||
let y = lparam.signed_hiword() as f32;
|
||||
let event = MouseMoveEvent {
|
||||
position: logical_point(x, y, scale_factor),
|
||||
pressed_button,
|
||||
modifiers: current_modifiers(),
|
||||
};
|
||||
let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
Some(1)
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(callback);
|
||||
return result;
|
||||
}
|
||||
Some(1)
|
||||
}
|
||||
|
||||
fn handle_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
|
||||
@@ -437,10 +439,14 @@ fn handle_keyup_msg(
|
||||
};
|
||||
drop(lock);
|
||||
|
||||
let handled = !func(input).propagate;
|
||||
let result = if func(input).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
Some(1)
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
|
||||
if handled { Some(0) } else { Some(1) }
|
||||
result
|
||||
}
|
||||
|
||||
fn handle_char_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
|
||||
@@ -473,27 +479,32 @@ fn handle_mouse_down_msg(
|
||||
) -> Option<isize> {
|
||||
unsafe { SetCapture(handle) };
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
let Some(mut func) = lock.callbacks.input.take() else {
|
||||
return Some(1);
|
||||
};
|
||||
let x = lparam.signed_loword();
|
||||
let y = lparam.signed_hiword();
|
||||
let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
|
||||
let click_count = lock.click_state.update(button, physical_point);
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let x = lparam.signed_loword() as f32;
|
||||
let y = lparam.signed_hiword() as f32;
|
||||
let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
|
||||
let click_count = lock.click_state.update(button, physical_point);
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
|
||||
let input = PlatformInput::MouseDown(MouseDownEvent {
|
||||
button,
|
||||
position: logical_point(x as f32, y as f32, scale_factor),
|
||||
modifiers: current_modifiers(),
|
||||
click_count,
|
||||
first_mouse: false,
|
||||
});
|
||||
let handled = !func(input).propagate;
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
let event = MouseDownEvent {
|
||||
button,
|
||||
position: logical_point(x, y, scale_factor),
|
||||
modifiers: current_modifiers(),
|
||||
click_count,
|
||||
first_mouse: false,
|
||||
};
|
||||
let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
Some(1)
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(callback);
|
||||
|
||||
if handled { Some(0) } else { Some(1) }
|
||||
result
|
||||
} else {
|
||||
Some(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_up_msg(
|
||||
@@ -504,25 +515,30 @@ fn handle_mouse_up_msg(
|
||||
) -> Option<isize> {
|
||||
unsafe { ReleaseCapture().log_err() };
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
let Some(mut func) = lock.callbacks.input.take() else {
|
||||
return Some(1);
|
||||
};
|
||||
let x = lparam.signed_loword() as f32;
|
||||
let y = lparam.signed_hiword() as f32;
|
||||
let click_count = lock.click_state.current_count;
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let x = lparam.signed_loword() as f32;
|
||||
let y = lparam.signed_hiword() as f32;
|
||||
let click_count = lock.click_state.current_count;
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
|
||||
let input = PlatformInput::MouseUp(MouseUpEvent {
|
||||
button,
|
||||
position: logical_point(x, y, scale_factor),
|
||||
modifiers: current_modifiers(),
|
||||
click_count,
|
||||
});
|
||||
let handled = !func(input).propagate;
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
let event = MouseUpEvent {
|
||||
button,
|
||||
position: logical_point(x, y, scale_factor),
|
||||
modifiers: current_modifiers(),
|
||||
click_count,
|
||||
};
|
||||
let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
Some(1)
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(callback);
|
||||
|
||||
if handled { Some(0) } else { Some(1) }
|
||||
result
|
||||
} else {
|
||||
Some(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_xbutton_msg(
|
||||
@@ -548,42 +564,46 @@ fn handle_mouse_wheel_msg(
|
||||
) -> Option<isize> {
|
||||
let modifiers = current_modifiers();
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
let Some(mut func) = lock.callbacks.input.take() else {
|
||||
return Some(1);
|
||||
};
|
||||
let scale_factor = lock.scale_factor;
|
||||
let wheel_scroll_amount = match modifiers.shift {
|
||||
true => lock.system_settings.mouse_wheel_settings.wheel_scroll_chars,
|
||||
false => lock.system_settings.mouse_wheel_settings.wheel_scroll_lines,
|
||||
};
|
||||
drop(lock);
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let scale_factor = lock.scale_factor;
|
||||
let wheel_scroll_amount = match modifiers.shift {
|
||||
true => lock.system_settings.mouse_wheel_settings.wheel_scroll_chars,
|
||||
false => lock.system_settings.mouse_wheel_settings.wheel_scroll_lines,
|
||||
};
|
||||
drop(lock);
|
||||
let wheel_distance =
|
||||
(wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_amount as f32;
|
||||
let mut cursor_point = POINT {
|
||||
x: lparam.signed_loword().into(),
|
||||
y: lparam.signed_hiword().into(),
|
||||
};
|
||||
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
|
||||
let event = ScrollWheelEvent {
|
||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||
delta: ScrollDelta::Lines(match modifiers.shift {
|
||||
true => Point {
|
||||
x: wheel_distance,
|
||||
y: 0.0,
|
||||
},
|
||||
false => Point {
|
||||
y: wheel_distance,
|
||||
x: 0.0,
|
||||
},
|
||||
}),
|
||||
modifiers: current_modifiers(),
|
||||
touch_phase: TouchPhase::Moved,
|
||||
};
|
||||
let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
Some(1)
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(callback);
|
||||
|
||||
let wheel_distance =
|
||||
(wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_amount as f32;
|
||||
let mut cursor_point = POINT {
|
||||
x: lparam.signed_loword().into(),
|
||||
y: lparam.signed_hiword().into(),
|
||||
};
|
||||
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
|
||||
let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
|
||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||
delta: ScrollDelta::Lines(match modifiers.shift {
|
||||
true => Point {
|
||||
x: wheel_distance,
|
||||
y: 0.0,
|
||||
},
|
||||
false => Point {
|
||||
y: wheel_distance,
|
||||
x: 0.0,
|
||||
},
|
||||
}),
|
||||
modifiers,
|
||||
touch_phase: TouchPhase::Moved,
|
||||
});
|
||||
let handled = !func(input).propagate;
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
|
||||
if handled { Some(0) } else { Some(1) }
|
||||
result
|
||||
} else {
|
||||
Some(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_horizontal_wheel_msg(
|
||||
@@ -593,33 +613,37 @@ fn handle_mouse_horizontal_wheel_msg(
|
||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||
) -> Option<isize> {
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
let Some(mut func) = lock.callbacks.input.take() else {
|
||||
return Some(1);
|
||||
};
|
||||
let scale_factor = lock.scale_factor;
|
||||
let wheel_scroll_chars = lock.system_settings.mouse_wheel_settings.wheel_scroll_chars;
|
||||
drop(lock);
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let scale_factor = lock.scale_factor;
|
||||
let wheel_scroll_chars = lock.system_settings.mouse_wheel_settings.wheel_scroll_chars;
|
||||
drop(lock);
|
||||
let wheel_distance =
|
||||
(-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
|
||||
let mut cursor_point = POINT {
|
||||
x: lparam.signed_loword().into(),
|
||||
y: lparam.signed_hiword().into(),
|
||||
};
|
||||
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
|
||||
let event = ScrollWheelEvent {
|
||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||
delta: ScrollDelta::Lines(Point {
|
||||
x: wheel_distance,
|
||||
y: 0.0,
|
||||
}),
|
||||
modifiers: current_modifiers(),
|
||||
touch_phase: TouchPhase::Moved,
|
||||
};
|
||||
let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
Some(1)
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(callback);
|
||||
|
||||
let wheel_distance =
|
||||
(-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
|
||||
let mut cursor_point = POINT {
|
||||
x: lparam.signed_loword().into(),
|
||||
y: lparam.signed_hiword().into(),
|
||||
};
|
||||
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
|
||||
let event = PlatformInput::ScrollWheel(ScrollWheelEvent {
|
||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||
delta: ScrollDelta::Lines(Point {
|
||||
x: wheel_distance,
|
||||
y: 0.0,
|
||||
}),
|
||||
modifiers: current_modifiers(),
|
||||
touch_phase: TouchPhase::Moved,
|
||||
});
|
||||
let handled = !func(event).propagate;
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
|
||||
if handled { Some(0) } else { Some(1) }
|
||||
result
|
||||
} else {
|
||||
Some(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn retrieve_caret_position(state_ptr: &Rc<WindowsWindowStatePtr>) -> Option<POINT> {
|
||||
@@ -784,10 +808,10 @@ fn handle_activate_msg(
|
||||
.executor
|
||||
.spawn(async move {
|
||||
let mut lock = this.state.borrow_mut();
|
||||
if let Some(mut func) = lock.callbacks.active_status_change.take() {
|
||||
if let Some(mut cb) = lock.callbacks.active_status_change.take() {
|
||||
drop(lock);
|
||||
func(activated);
|
||||
this.state.borrow_mut().callbacks.active_status_change = Some(func);
|
||||
cb(activated);
|
||||
this.state.borrow_mut().callbacks.active_status_change = Some(cb);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -854,7 +878,7 @@ fn handle_display_change_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>)
|
||||
// Because WM_DPICHANGED, WM_MOVE, WM_SIZE will come first, window reposition and resize
|
||||
// are handled there.
|
||||
// So we only care about if monitor is disconnected.
|
||||
let previous_monitor = state_ptr.state.borrow().display;
|
||||
let previous_monitor = state_ptr.as_ref().state.borrow().display;
|
||||
if WindowsDisplay::is_connected(previous_monitor.handle) {
|
||||
// we are fine, other display changed
|
||||
return None;
|
||||
@@ -872,7 +896,7 @@ fn handle_display_change_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>)
|
||||
return None;
|
||||
}
|
||||
let new_display = WindowsDisplay::new_with_handle(new_monitor);
|
||||
state_ptr.state.borrow_mut().display = new_display;
|
||||
state_ptr.as_ref().state.borrow_mut().display = new_display;
|
||||
Some(0)
|
||||
}
|
||||
|
||||
@@ -956,24 +980,30 @@ fn handle_nc_mouse_move_msg(
|
||||
start_tracking_mouse(handle, &state_ptr, TME_LEAVE | TME_NONCLIENT);
|
||||
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
let mut func = lock.callbacks.input.take()?;
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
let mut cursor_point = POINT {
|
||||
x: lparam.signed_loword().into(),
|
||||
y: lparam.signed_hiword().into(),
|
||||
};
|
||||
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
|
||||
let event = MouseMoveEvent {
|
||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||
pressed_button: None,
|
||||
modifiers: current_modifiers(),
|
||||
};
|
||||
let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
Some(1)
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(callback);
|
||||
|
||||
let mut cursor_point = POINT {
|
||||
x: lparam.signed_loword().into(),
|
||||
y: lparam.signed_hiword().into(),
|
||||
};
|
||||
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
|
||||
let input = PlatformInput::MouseMove(MouseMoveEvent {
|
||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||
pressed_button: None,
|
||||
modifiers: current_modifiers(),
|
||||
});
|
||||
let handled = !func(input).propagate;
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
|
||||
if handled { Some(0) } else { None }
|
||||
result
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_nc_mouse_down_msg(
|
||||
@@ -988,7 +1018,7 @@ fn handle_nc_mouse_down_msg(
|
||||
}
|
||||
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
if let Some(mut func) = lock.callbacks.input.take() {
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let scale_factor = lock.scale_factor;
|
||||
let mut cursor_point = POINT {
|
||||
x: lparam.signed_loword().into(),
|
||||
@@ -998,19 +1028,22 @@ fn handle_nc_mouse_down_msg(
|
||||
let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
|
||||
let click_count = lock.click_state.update(button, physical_point);
|
||||
drop(lock);
|
||||
|
||||
let input = PlatformInput::MouseDown(MouseDownEvent {
|
||||
let event = MouseDownEvent {
|
||||
button,
|
||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||
modifiers: current_modifiers(),
|
||||
click_count,
|
||||
first_mouse: false,
|
||||
});
|
||||
let handled = !func(input).propagate;
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
};
|
||||
let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(callback);
|
||||
|
||||
if handled {
|
||||
return Some(0);
|
||||
if result.is_some() {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
drop(lock);
|
||||
@@ -1042,26 +1075,28 @@ fn handle_nc_mouse_up_msg(
|
||||
}
|
||||
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
if let Some(mut func) = lock.callbacks.input.take() {
|
||||
if let Some(mut callback) = lock.callbacks.input.take() {
|
||||
let scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
|
||||
let mut cursor_point = POINT {
|
||||
x: lparam.signed_loword().into(),
|
||||
y: lparam.signed_hiword().into(),
|
||||
};
|
||||
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
|
||||
let input = PlatformInput::MouseUp(MouseUpEvent {
|
||||
let event = MouseUpEvent {
|
||||
button,
|
||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||
modifiers: current_modifiers(),
|
||||
click_count: 1,
|
||||
});
|
||||
let handled = !func(input).propagate;
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
|
||||
if handled {
|
||||
return Some(0);
|
||||
};
|
||||
let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(callback);
|
||||
if result.is_some() {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
drop(lock);
|
||||
@@ -1069,27 +1104,35 @@ fn handle_nc_mouse_up_msg(
|
||||
|
||||
let last_pressed = state_ptr.state.borrow_mut().nc_button_pressed.take();
|
||||
if button == MouseButton::Left && last_pressed.is_some() {
|
||||
let handled = match (wparam.0 as u32, last_pressed.unwrap()) {
|
||||
(HTMINBUTTON, HTMINBUTTON) => {
|
||||
unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() };
|
||||
true
|
||||
}
|
||||
(HTMAXBUTTON, HTMAXBUTTON) => {
|
||||
if state_ptr.state.borrow().is_maximized() {
|
||||
unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() };
|
||||
} else {
|
||||
unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() };
|
||||
let last_button = last_pressed.unwrap();
|
||||
let mut handled = false;
|
||||
match wparam.0 as u32 {
|
||||
HTMINBUTTON => {
|
||||
if last_button == HTMINBUTTON {
|
||||
unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() };
|
||||
handled = true;
|
||||
}
|
||||
true
|
||||
}
|
||||
(HTCLOSE, HTCLOSE) => {
|
||||
unsafe {
|
||||
PostMessageW(Some(handle), WM_CLOSE, WPARAM::default(), LPARAM::default())
|
||||
.log_err()
|
||||
};
|
||||
true
|
||||
HTMAXBUTTON => {
|
||||
if last_button == HTMAXBUTTON {
|
||||
if state_ptr.state.borrow().is_maximized() {
|
||||
unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() };
|
||||
} else {
|
||||
unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() };
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
HTCLOSE => {
|
||||
if last_button == HTCLOSE {
|
||||
unsafe {
|
||||
PostMessageW(Some(handle), WM_CLOSE, WPARAM::default(), LPARAM::default())
|
||||
.log_err()
|
||||
};
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
if handled {
|
||||
return Some(0);
|
||||
|
||||
@@ -755,10 +755,6 @@ pub struct LanguageConfig {
|
||||
/// A list of preferred debuggers for this language.
|
||||
#[serde(default)]
|
||||
pub debuggers: IndexSet<SharedString>,
|
||||
/// Whether to treat documentation comment of this language differently by
|
||||
/// auto adding prefix on new line, adjusting the indenting , etc.
|
||||
#[serde(default)]
|
||||
pub documentation: Option<DocumentationConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default, JsonSchema)]
|
||||
@@ -809,19 +805,6 @@ pub struct JsxTagAutoCloseConfig {
|
||||
pub erroneous_close_tag_name_node_name: Option<String>,
|
||||
}
|
||||
|
||||
/// The configuration for documentation block for this language.
|
||||
#[derive(Clone, Deserialize, JsonSchema)]
|
||||
pub struct DocumentationConfig {
|
||||
/// A start tag of documentation block.
|
||||
pub start: Arc<str>,
|
||||
/// A end tag of documentation block.
|
||||
pub end: Arc<str>,
|
||||
/// A character to add as a prefix when a new line is added to a documentation block.
|
||||
pub prefix: Arc<str>,
|
||||
/// A indent to add for prefix and end line upon new line.
|
||||
pub tab_size: NonZeroU32,
|
||||
}
|
||||
|
||||
/// Represents a language for the given range. Some languages (e.g. HTML)
|
||||
/// interleave several languages together, thus a single buffer might actually contain
|
||||
/// several nested scopes.
|
||||
@@ -900,7 +883,6 @@ impl Default for LanguageConfig {
|
||||
completion_query_characters: Default::default(),
|
||||
debuggers: Default::default(),
|
||||
significant_indentation: Default::default(),
|
||||
documentation: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1820,14 +1802,6 @@ impl LanguageScope {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns config to documentation block for this language.
|
||||
///
|
||||
/// Used for documentation styles that require a leading character on each line,
|
||||
/// such as the asterisk in JSDoc, Javadoc, etc.
|
||||
pub fn documentation(&self) -> Option<&DocumentationConfig> {
|
||||
self.language.config.documentation.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a list of bracket pairs for a given language with an additional
|
||||
/// piece of information about whether the particular bracket pair is currently active for a given language.
|
||||
pub fn brackets(&self) -> impl Iterator<Item = (&BracketPair, bool)> {
|
||||
@@ -1859,9 +1833,9 @@ impl LanguageScope {
|
||||
pub fn language_allowed(&self, name: &LanguageServerName) -> bool {
|
||||
let config = &self.language.config;
|
||||
let opt_in_servers = &config.scope_opt_in_language_servers;
|
||||
if opt_in_servers.contains(name) {
|
||||
if opt_in_servers.iter().any(|o| *o == *name) {
|
||||
if let Some(over) = self.config_override() {
|
||||
over.opt_into_language_servers.contains(name)
|
||||
over.opt_into_language_servers.iter().any(|o| *o == *name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
use gpui::{App, Context, Entity, EventEmitter, Global, prelude::*};
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
use util::maybe;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
@@ -27,36 +27,11 @@ pub struct LanguageModelRegistry {
|
||||
inline_alternatives: Vec<Arc<dyn LanguageModel>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SelectedModel {
|
||||
pub provider: LanguageModelProviderId,
|
||||
pub model: LanguageModelId,
|
||||
}
|
||||
|
||||
impl FromStr for SelectedModel {
|
||||
type Err = String;
|
||||
|
||||
/// Parse string identifiers like `provider_id/model_id` into a `SelectedModel`
|
||||
fn from_str(id: &str) -> Result<SelectedModel, Self::Err> {
|
||||
let parts: Vec<&str> = id.split('/').collect();
|
||||
let [provider_id, model_id] = parts.as_slice() else {
|
||||
return Err(format!(
|
||||
"Invalid model identifier format: `{}`. Expected `provider_id/model_id`",
|
||||
id
|
||||
));
|
||||
};
|
||||
|
||||
if provider_id.is_empty() || model_id.is_empty() {
|
||||
return Err(format!("Provider and model ids can't be empty: `{}`", id));
|
||||
}
|
||||
|
||||
Ok(SelectedModel {
|
||||
provider: LanguageModelProviderId(provider_id.to_string().into()),
|
||||
model: LanguageModelId(model_id.to_string().into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConfiguredModel {
|
||||
pub provider: Arc<dyn LanguageModelProvider>,
|
||||
|
||||
@@ -12,4 +12,3 @@ brackets = [
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
debuggers = ["CodeLLDB", "GDB"]
|
||||
documentation = { start = "/*", end = "*/", prefix = "* ", tab_size = 1 }
|
||||
|
||||
@@ -12,4 +12,3 @@ brackets = [
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
debuggers = ["CodeLLDB", "GDB"]
|
||||
documentation = { start = "/*", end = "*/", prefix = "* ", tab_size = 1 }
|
||||
|
||||
@@ -15,4 +15,3 @@ brackets = [
|
||||
tab_size = 4
|
||||
hard_tabs = true
|
||||
debuggers = ["Delve"]
|
||||
documentation = { start = "/*", end = "*/", prefix = "* ", tab_size = 1 }
|
||||
|
||||
@@ -20,7 +20,6 @@ tab_size = 2
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"]
|
||||
prettier_parser_name = "babel"
|
||||
debuggers = ["JavaScript"]
|
||||
documentation = { start = "/**", end = "*/", prefix = "* ", tab_size = 1 }
|
||||
|
||||
[jsx_tag_auto_close]
|
||||
open_tag_node_name = "jsx_opening_element"
|
||||
|
||||
@@ -2,7 +2,6 @@ use anyhow::Context as _;
|
||||
use gpui::{App, UpdateGlobal};
|
||||
use json::json_task_context;
|
||||
use node_runtime::NodeRuntime;
|
||||
use python::PyprojectTomlManifestProvider;
|
||||
use rust::CargoManifestProvider;
|
||||
use rust_embed::RustEmbed;
|
||||
use settings::SettingsStore;
|
||||
@@ -303,13 +302,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
let manifest_providers: [Arc<dyn ManifestProvider>; 2] = [
|
||||
Arc::from(CargoManifestProvider),
|
||||
Arc::from(PyprojectTomlManifestProvider),
|
||||
];
|
||||
for provider in manifest_providers {
|
||||
project::ManifestProviders::global(cx).register(provider);
|
||||
}
|
||||
project::ManifestProviders::global(cx).register(Arc::from(CargoManifestProvider));
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
@@ -4,13 +4,13 @@ use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use gpui::{App, Task};
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use language::LanguageName;
|
||||
use language::LanguageToolchainStore;
|
||||
use language::Toolchain;
|
||||
use language::ToolchainList;
|
||||
use language::ToolchainLister;
|
||||
use language::language_settings::language_settings;
|
||||
use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
|
||||
use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};
|
||||
use lsp::LanguageServerBinary;
|
||||
use lsp::LanguageServerName;
|
||||
use node_runtime::NodeRuntime;
|
||||
@@ -38,32 +38,6 @@ use std::{
|
||||
use task::{TaskTemplate, TaskTemplates, VariableName};
|
||||
use util::ResultExt;
|
||||
|
||||
pub(crate) struct PyprojectTomlManifestProvider;
|
||||
|
||||
impl ManifestProvider for PyprojectTomlManifestProvider {
|
||||
fn name(&self) -> ManifestName {
|
||||
SharedString::new_static("pyproject.toml").into()
|
||||
}
|
||||
|
||||
fn search(
|
||||
&self,
|
||||
ManifestQuery {
|
||||
path,
|
||||
depth,
|
||||
delegate,
|
||||
}: ManifestQuery,
|
||||
) -> Option<Arc<Path>> {
|
||||
for path in path.ancestors().take(depth) {
|
||||
let p = path.join("pyproject.toml");
|
||||
if delegate.exists(&p, Some(false)) {
|
||||
return Some(path.into());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";
|
||||
const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js";
|
||||
|
||||
@@ -327,9 +301,6 @@ impl LspAdapter for PythonLspAdapter {
|
||||
user_settings
|
||||
})
|
||||
}
|
||||
fn manifest_name(&self) -> Option<ManifestName> {
|
||||
Some(SharedString::new_static("pyproject.toml").into())
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
@@ -1171,9 +1142,6 @@ impl LspAdapter for PyLspAdapter {
|
||||
user_settings
|
||||
})
|
||||
}
|
||||
fn manifest_name(&self) -> Option<ManifestName> {
|
||||
Some(SharedString::new_static("pyproject.toml").into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -686,7 +686,6 @@ impl ContextProvider for RustContextProvider {
|
||||
RUST_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
"--".into(),
|
||||
"--nocapture".into(),
|
||||
"--include-ignored".into(),
|
||||
RUST_TEST_NAME_TASK_VARIABLE.template_value(),
|
||||
],
|
||||
tags: vec!["rust-test".to_owned()],
|
||||
@@ -707,7 +706,6 @@ impl ContextProvider for RustContextProvider {
|
||||
RUST_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
"--".into(),
|
||||
"--nocapture".into(),
|
||||
"--include-ignored".into(),
|
||||
RUST_DOC_TEST_NAME_TASK_VARIABLE.template_value(),
|
||||
],
|
||||
tags: vec!["rust-doc-test".to_owned()],
|
||||
|
||||
@@ -16,4 +16,3 @@ brackets = [
|
||||
]
|
||||
collapsed_placeholder = " /* ... */ "
|
||||
debuggers = ["CodeLLDB", "GDB"]
|
||||
documentation = { start = "/*", end = "*/", prefix = "* ", tab_size = 1 }
|
||||
|
||||
@@ -18,7 +18,6 @@ scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-
|
||||
prettier_parser_name = "typescript"
|
||||
tab_size = 2
|
||||
debuggers = ["JavaScript"]
|
||||
documentation = { start = "/**", end = "*/", prefix = "* ", tab_size = 1 }
|
||||
|
||||
[jsx_tag_auto_close]
|
||||
open_tag_node_name = "jsx_opening_element"
|
||||
|
||||
@@ -18,7 +18,6 @@ word_characters = ["#", "$"]
|
||||
prettier_parser_name = "typescript"
|
||||
tab_size = 2
|
||||
debuggers = ["JavaScript"]
|
||||
documentation = { start = "/**", end = "*/", prefix = "* ", tab_size = 1 }
|
||||
|
||||
[overrides.string]
|
||||
completion_query_characters = ["."]
|
||||
|
||||
@@ -1620,7 +1620,7 @@ impl OutlinePanel {
|
||||
.get(&external_file.buffer_id)
|
||||
.into_iter()
|
||||
.flat_map(|excerpts| {
|
||||
excerpts.keys().map(|excerpt_id| {
|
||||
excerpts.iter().map(|(excerpt_id, _)| {
|
||||
CollapsedEntry::Excerpt(
|
||||
external_file.buffer_id,
|
||||
*excerpt_id,
|
||||
@@ -1641,7 +1641,7 @@ impl OutlinePanel {
|
||||
entries.extend(
|
||||
self.excerpts.get(&file.buffer_id).into_iter().flat_map(
|
||||
|excerpts| {
|
||||
excerpts.keys().map(|excerpt_id| {
|
||||
excerpts.iter().map(|(excerpt_id, _)| {
|
||||
CollapsedEntry::Excerpt(file.buffer_id, *excerpt_id)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -64,6 +64,7 @@ pub enum DapStoreEvent {
|
||||
RemoteHasInitialized,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum DapStoreMode {
|
||||
Local(LocalDapStore),
|
||||
Ssh(SshDapStore),
|
||||
|
||||
@@ -3415,6 +3415,7 @@ pub struct RemoteLspStore {
|
||||
upstream_project_id: u64,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub(crate) enum LspStoreMode {
|
||||
Local(LocalLspStore), // ssh host and collab host
|
||||
Remote(RemoteLspStore), // collab guest
|
||||
@@ -8805,10 +8806,9 @@ impl LspStore {
|
||||
})
|
||||
});
|
||||
|
||||
let is_unnecessary = diagnostic
|
||||
.tags
|
||||
.as_ref()
|
||||
.map_or(false, |tags| tags.contains(&DiagnosticTag::UNNECESSARY));
|
||||
let is_unnecessary = diagnostic.tags.as_ref().map_or(false, |tags| {
|
||||
tags.iter().any(|tag| *tag == DiagnosticTag::UNNECESSARY)
|
||||
});
|
||||
|
||||
if is_supporting {
|
||||
supporting_diagnostics.insert(
|
||||
|
||||
@@ -98,7 +98,7 @@ impl<Label: Ord + Clone> RootPathTrie<Label> {
|
||||
};
|
||||
}
|
||||
if !current.labels.is_empty() {
|
||||
let _ = (callback)(¤t.worktree_relative_path, ¤t.labels);
|
||||
(callback)(¤t.worktree_relative_path, ¤t.labels);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3653,7 +3653,7 @@ impl Project {
|
||||
let mut buffer_count = 0;
|
||||
let mut limit_reached = false;
|
||||
let query = Arc::new(query);
|
||||
let chunks = matching_buffers_rx.ready_chunks(64);
|
||||
let mut chunks = matching_buffers_rx.ready_chunks(64);
|
||||
|
||||
// Now that we know what paths match the query, we will load at most
|
||||
// 64 buffers at a time to avoid overwhelming the main thread. For each
|
||||
@@ -4392,7 +4392,7 @@ impl Project {
|
||||
envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::LanguageServerPromptResponse> {
|
||||
let (tx, rx) = smol::channel::bounded(1);
|
||||
let (tx, mut rx) = smol::channel::bounded(1);
|
||||
let actions: Vec<_> = envelope
|
||||
.payload
|
||||
.actions
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::{
|
||||
worktree_store::WorktreeStore,
|
||||
};
|
||||
|
||||
// platform-dependent warning
|
||||
#[allow(clippy::large_enum_variant)] // platform-dependent warning
|
||||
pub enum TaskStore {
|
||||
Functional(StoreState),
|
||||
Noop,
|
||||
|
||||
@@ -24,7 +24,7 @@ pub struct Terminals {
|
||||
}
|
||||
|
||||
/// Terminals are opened either for the users shell, or to run a task.
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum TerminalKind {
|
||||
/// Run a shell at the given path (or $HOME if None)
|
||||
|
||||
@@ -876,7 +876,7 @@ impl WorktreeStore {
|
||||
|
||||
async fn filter_paths(
|
||||
fs: &Arc<dyn Fs>,
|
||||
input: Receiver<MatchingEntry>,
|
||||
mut input: Receiver<MatchingEntry>,
|
||||
query: &SearchQuery,
|
||||
) -> Result<()> {
|
||||
let mut input = pin!(input);
|
||||
|
||||
@@ -2854,9 +2854,12 @@ impl ProjectPanel {
|
||||
}
|
||||
let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id {
|
||||
entry.id == new_entry_id || {
|
||||
self.ancestors
|
||||
.get(&entry.id)
|
||||
.map_or(false, |entries| entries.ancestors.contains(&new_entry_id))
|
||||
self.ancestors.get(&entry.id).map_or(false, |entries| {
|
||||
entries
|
||||
.ancestors
|
||||
.iter()
|
||||
.any(|entry_id| *entry_id == new_entry_id)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
false
|
||||
@@ -3366,7 +3369,10 @@ impl ProjectPanel {
|
||||
.ancestors
|
||||
.get(&entry.id)
|
||||
.is_some_and(|auto_folded_dirs| {
|
||||
auto_folded_dirs.ancestors.contains(&edit_state.entry_id)
|
||||
auto_folded_dirs
|
||||
.ancestors
|
||||
.iter()
|
||||
.any(|entry_id| *entry_id == edit_state.entry_id)
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -925,7 +925,7 @@ impl SshRemoteClient {
|
||||
|
||||
if missed_heartbeats != 0 {
|
||||
missed_heartbeats = 0;
|
||||
let _ =this.update(cx, |this, mut cx| {
|
||||
this.update(cx, |this, mut cx| {
|
||||
this.handle_heartbeat_result(missed_heartbeats, &mut cx)
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ pub fn run(
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum SessionSupport {
|
||||
ActiveSession(Entity<Session>),
|
||||
Inactive(KernelSpecification),
|
||||
|
||||
@@ -20,6 +20,7 @@ pub struct MessageStream<S> {
|
||||
encoding_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
Envelope(Envelope),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use gpui::{
|
||||
AnyElement, App, Div, SharedString, Window, colors::DefaultColors, div, prelude::*, px, rems,
|
||||
AnyElement, App, DefaultColor, DefaultColors, Div, SharedString, Window, div, prelude::*, px,
|
||||
rems,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use smallvec::SmallVec;
|
||||
@@ -7,7 +8,9 @@ use smallvec::SmallVec;
|
||||
pub struct Story {}
|
||||
|
||||
impl Story {
|
||||
pub fn container(cx: &App) -> gpui::Stateful<Div> {
|
||||
pub fn container() -> gpui::Stateful<Div> {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div()
|
||||
.id("story_container")
|
||||
.overflow_y_scroll()
|
||||
@@ -15,66 +18,84 @@ impl Story {
|
||||
.min_h_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.text_color(cx.default_colors().text)
|
||||
.bg(cx.default_colors().background)
|
||||
.text_color(DefaultColor::Text.hsla(&colors))
|
||||
.bg(DefaultColor::Background.hsla(&colors))
|
||||
}
|
||||
|
||||
pub fn title(title: impl Into<SharedString>, cx: &App) -> impl Element {
|
||||
pub fn title(title: impl Into<SharedString>) -> impl Element {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.default_colors().text)
|
||||
.text_color(DefaultColor::Text.hsla(&colors))
|
||||
.child(title.into())
|
||||
}
|
||||
|
||||
pub fn title_for<T>(cx: &App) -> impl Element {
|
||||
Self::title(std::any::type_name::<T>(), cx)
|
||||
pub fn title_for<T>() -> impl Element {
|
||||
Self::title(std::any::type_name::<T>())
|
||||
}
|
||||
|
||||
pub fn section(cx: &App) -> Div {
|
||||
pub fn section() -> Div {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div()
|
||||
.p_4()
|
||||
.m_4()
|
||||
.border_1()
|
||||
.border_color(cx.default_colors().separator)
|
||||
.border_color(DefaultColor::Separator.hsla(&colors))
|
||||
}
|
||||
|
||||
pub fn section_title(cx: &App) -> Div {
|
||||
div().text_lg().text_color(cx.default_colors().text)
|
||||
pub fn section_title() -> Div {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div().text_lg().text_color(DefaultColor::Text.hsla(&colors))
|
||||
}
|
||||
|
||||
pub fn group(cx: &App) -> Div {
|
||||
div().my_2().bg(cx.default_colors().container)
|
||||
pub fn group() -> Div {
|
||||
let colors = DefaultColors::light();
|
||||
div().my_2().bg(DefaultColor::Container.hsla(&colors))
|
||||
}
|
||||
|
||||
pub fn code_block(code: impl Into<SharedString>, cx: &App) -> Div {
|
||||
pub fn code_block(code: impl Into<SharedString>) -> Div {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.p_2()
|
||||
.max_w(rems(36.))
|
||||
.bg(cx.default_colors().container)
|
||||
.bg(DefaultColor::Container.hsla(&colors))
|
||||
.rounded_sm()
|
||||
.text_sm()
|
||||
.text_color(cx.default_colors().text)
|
||||
.text_color(DefaultColor::Text.hsla(&colors))
|
||||
.overflow_hidden()
|
||||
.child(code.into())
|
||||
}
|
||||
|
||||
pub fn divider(cx: &App) -> Div {
|
||||
div().my_2().h(px(1.)).bg(cx.default_colors().separator)
|
||||
pub fn divider() -> Div {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div()
|
||||
.my_2()
|
||||
.h(px(1.))
|
||||
.bg(DefaultColor::Separator.hsla(&colors))
|
||||
}
|
||||
|
||||
pub fn description(description: impl Into<SharedString>, cx: &App) -> impl Element {
|
||||
pub fn description(description: impl Into<SharedString>) -> impl Element {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.default_colors().text)
|
||||
.text_color(DefaultColor::Text.hsla(&colors))
|
||||
.min_w_96()
|
||||
.child(description.into())
|
||||
}
|
||||
|
||||
pub fn label(label: impl Into<SharedString>, cx: &App) -> impl Element {
|
||||
pub fn label(label: impl Into<SharedString>) -> impl Element {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.default_colors().text)
|
||||
.text_color(DefaultColor::Text.hsla(&colors))
|
||||
.child(label.into())
|
||||
}
|
||||
|
||||
@@ -114,8 +135,8 @@ impl StoryItem {
|
||||
}
|
||||
|
||||
impl RenderOnce for StoryItem {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let colors = cx.default_colors();
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let colors = DefaultColors::light();
|
||||
|
||||
div()
|
||||
.my_2()
|
||||
@@ -127,20 +148,20 @@ impl RenderOnce for StoryItem {
|
||||
.px_2()
|
||||
.w_1_2()
|
||||
.min_h_px()
|
||||
.child(Story::label(self.label, cx))
|
||||
.child(Story::label(self.label))
|
||||
.child(
|
||||
div()
|
||||
.rounded_sm()
|
||||
.bg(colors.background)
|
||||
.bg(DefaultColor::Background.hsla(&colors))
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.border_color(DefaultColor::Border.hsla(&colors))
|
||||
.py_1()
|
||||
.px_2()
|
||||
.overflow_hidden()
|
||||
.child(self.item),
|
||||
)
|
||||
.when_some(self.description, |this, description| {
|
||||
this.child(Story::description(description, cx))
|
||||
this.child(Story::description(description))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -150,8 +171,8 @@ impl RenderOnce for StoryItem {
|
||||
.w_1_2()
|
||||
.min_h_px()
|
||||
.when_some(self.usage, |this, usage| {
|
||||
this.child(Story::label("Example Usage", cx))
|
||||
.child(Story::code_block(usage, cx))
|
||||
this.child(Story::label("Example Usage"))
|
||||
.child(Story::code_block(usage))
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -184,21 +205,21 @@ impl StorySection {
|
||||
}
|
||||
|
||||
impl RenderOnce for StorySection {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let children: SmallVec<[AnyElement; 2]> = SmallVec::from_iter(Itertools::intersperse_with(
|
||||
self.children.into_iter(),
|
||||
|| Story::divider(cx).into_any_element(),
|
||||
|| Story::divider().into_any_element(),
|
||||
));
|
||||
|
||||
Story::section(cx)
|
||||
Story::section()
|
||||
// Section title
|
||||
.py_2()
|
||||
// Section description
|
||||
.when_some(self.description.clone(), |section, description| {
|
||||
section.child(Story::description(description, cx))
|
||||
section.child(Story::description(description))
|
||||
})
|
||||
.child(div().flex().flex_col().gap_2().children(children))
|
||||
.child(Story::divider(cx))
|
||||
.child(Story::divider())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod auto_height_editor;
|
||||
mod cursor;
|
||||
mod default_colors;
|
||||
mod focus;
|
||||
mod kitchen_sink;
|
||||
mod overflow_scroll;
|
||||
@@ -11,6 +12,7 @@ mod with_rem_size;
|
||||
|
||||
pub use auto_height_editor::*;
|
||||
pub use cursor::*;
|
||||
pub use default_colors::*;
|
||||
pub use focus::*;
|
||||
pub use kitchen_sink::*;
|
||||
pub use overflow_scroll::*;
|
||||
|
||||
@@ -5,7 +5,7 @@ use ui::prelude::*;
|
||||
pub struct CursorStory;
|
||||
|
||||
impl Render for CursorStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let all_cursors: [(&str, Box<dyn Fn(Stateful<Div>) -> Stateful<Div>>); 19] = [
|
||||
(
|
||||
"cursor_default",
|
||||
@@ -85,10 +85,10 @@ impl Render for CursorStory {
|
||||
),
|
||||
];
|
||||
|
||||
Story::container(cx)
|
||||
Story::container()
|
||||
.flex()
|
||||
.gap_1()
|
||||
.child(Story::title("cursor", cx))
|
||||
.child(Story::title("cursor"))
|
||||
.children(all_cursors.map(|(name, apply_cursor)| {
|
||||
div().gap_1().flex().text_color(gpui::white()).child(
|
||||
div()
|
||||
@@ -102,7 +102,7 @@ impl Render for CursorStory {
|
||||
.bg(gpui::red())
|
||||
.active(|style| style.bg(gpui::green()))
|
||||
.text_sm()
|
||||
.child(Story::label(name, cx)),
|
||||
.child(Story::label(name)),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
89
crates/storybook/src/stories/default_colors.rs
Normal file
89
crates/storybook/src/stories/default_colors.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use gpui::{
|
||||
App, Context, DefaultColor, DefaultThemeAppearance, Entity, Hsla, Render, Window, colors, div,
|
||||
prelude::*,
|
||||
};
|
||||
use story::Story;
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{ActiveTheme, h_flex};
|
||||
|
||||
pub struct DefaultColorsStory;
|
||||
|
||||
impl DefaultColorsStory {
|
||||
pub fn model(cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|_| Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DefaultColorsStory {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let appearances = [DefaultThemeAppearance::Light, DefaultThemeAppearance::Dark];
|
||||
|
||||
Story::container()
|
||||
.child(Story::title("Default Colors"))
|
||||
.children(appearances.iter().map(|&appearance| {
|
||||
let colors = colors(appearance);
|
||||
let color_types = DefaultColor::iter()
|
||||
.map(|color| {
|
||||
let name = format!("{:?}", color);
|
||||
let rgba = color.hsla(&colors);
|
||||
(name, rgba)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_4()
|
||||
.p_4()
|
||||
.child(Story::label(format!("{:?} Appearance", appearance)))
|
||||
.children(color_types.iter().map(|(name, color)| {
|
||||
let color: Hsla = *color;
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.w_12()
|
||||
.h_12()
|
||||
.bg(color)
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border),
|
||||
)
|
||||
.child(Story::label(format!("{}: {:?}", name, color.clone())))
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.bg(DefaultColor::Background.hsla(&colors))
|
||||
.h_8()
|
||||
.p_2()
|
||||
.text_sm()
|
||||
.text_color(DefaultColor::Text.hsla(&colors))
|
||||
.child("Default Text"),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.bg(DefaultColor::Container.hsla(&colors))
|
||||
.h_8()
|
||||
.p_2()
|
||||
.text_sm()
|
||||
.text_color(DefaultColor::Text.hsla(&colors))
|
||||
.child("Text on Container"),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.bg(DefaultColor::Selected.hsla(&colors))
|
||||
.h_8()
|
||||
.p_2()
|
||||
.text_sm()
|
||||
.text_color(DefaultColor::SelectedText.hsla(&colors))
|
||||
.child("Selected Text"),
|
||||
),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::fmt::format;
|
||||
|
||||
use gpui::{
|
||||
DefaultColor, DefaultThemeAppearance, Hsla, Render, colors, div, prelude::*, uniform_list,
|
||||
colors, div, prelude::*, uniform_list, DefaultColor, DefaultThemeAppearance, Hsla, Render,
|
||||
};
|
||||
use story::Story;
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{
|
||||
AbsoluteLength, ActiveTheme, Color, DefiniteLength, Label, LabelCommon, h_flex, px, v_flex,
|
||||
h_flex, px, v_flex, AbsoluteLength, ActiveTheme, Color, DefiniteLength, Label, LabelCommon,
|
||||
};
|
||||
|
||||
const LENGTH: usize = 100;
|
||||
@@ -34,7 +34,7 @@ impl IndentGuidesStory {
|
||||
|
||||
impl Render for IndentGuidesStory {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
Story::container()
|
||||
.child(Story::title("Indent guides"))
|
||||
.child(
|
||||
v_flex().size_full().child(
|
||||
|
||||
@@ -19,11 +19,11 @@ impl Render for KitchenSinkStory {
|
||||
.map(|selector| selector.story(window, cx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Story::container(cx)
|
||||
Story::container()
|
||||
.id("kitchen-sink")
|
||||
.overflow_y_scroll()
|
||||
.child(Story::title("Kitchen Sink", cx))
|
||||
.child(Story::label("Components", cx))
|
||||
.child(Story::title("Kitchen Sink"))
|
||||
.child(Story::label("Components"))
|
||||
.child(div().flex().flex_col().children(component_stories))
|
||||
// Add a bit of space at the bottom of the kitchen sink so elements
|
||||
// don't end up squished right up against the bottom of the screen.
|
||||
|
||||
@@ -6,10 +6,10 @@ use ui::prelude::*;
|
||||
pub struct OverflowScrollStory;
|
||||
|
||||
impl Render for OverflowScrollStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
.child(Story::title("Overflow Scroll", cx))
|
||||
.child(Story::label("`overflow_x_scroll`", cx))
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container()
|
||||
.child(Story::title("Overflow Scroll"))
|
||||
.child(Story::label("`overflow_x_scroll`"))
|
||||
.child(
|
||||
h_flex()
|
||||
.id("overflow_x_scroll")
|
||||
@@ -22,7 +22,7 @@ impl Render for OverflowScrollStory {
|
||||
.child(SharedString::from(format!("Child {}", i + 1)))
|
||||
})),
|
||||
)
|
||||
.child(Story::label("`overflow_y_scroll`", cx))
|
||||
.child(Story::label("`overflow_y_scroll`"))
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
|
||||
@@ -14,9 +14,9 @@ impl TextStory {
|
||||
}
|
||||
|
||||
impl Render for TextStory {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
.child(Story::title("Text", cx))
|
||||
fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container()
|
||||
.child(Story::title("Text"))
|
||||
.children(vec![
|
||||
StorySection::new()
|
||||
.child(
|
||||
|
||||
@@ -6,8 +6,8 @@ use ui::prelude::*;
|
||||
pub struct ViewportUnitsStory;
|
||||
|
||||
impl Render for ViewportUnitsStory {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx).child(
|
||||
fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container().child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
|
||||
@@ -6,8 +6,8 @@ use ui::{prelude::*, utils::WithRemSize};
|
||||
pub struct WithRemSizeStory;
|
||||
|
||||
impl Render for WithRemSizeStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx).child(
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container().child(
|
||||
Example::new(16., gpui::red())
|
||||
.child(
|
||||
Example::new(24., gpui::green())
|
||||
|
||||
@@ -17,6 +17,7 @@ pub enum ComponentStory {
|
||||
CollabNotification,
|
||||
ContextMenu,
|
||||
Cursor,
|
||||
DefaultColors,
|
||||
Focus,
|
||||
IconButton,
|
||||
Keybinding,
|
||||
@@ -46,6 +47,7 @@ impl ComponentStory {
|
||||
.into(),
|
||||
Self::ContextMenu => cx.new(|_| ui::ContextMenuStory).into(),
|
||||
Self::Cursor => cx.new(|_| crate::stories::CursorStory).into(),
|
||||
Self::DefaultColors => DefaultColorsStory::model(cx).into(),
|
||||
Self::Focus => FocusStory::model(window, cx).into(),
|
||||
Self::IconButton => cx.new(|_| ui::IconButtonStory).into(),
|
||||
Self::Keybinding => cx.new(|_| ui::KeybindingStory).into(),
|
||||
|
||||
@@ -186,6 +186,7 @@ impl From<AttachRequest> for DebugRequest {
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum BuildTaskDefinition {
|
||||
ByName(SharedString),
|
||||
Template {
|
||||
|
||||
@@ -144,11 +144,9 @@ impl ThemeColors {
|
||||
version_control_renamed: MODIFIED_COLOR,
|
||||
version_control_conflict: orange().light().step_12(),
|
||||
version_control_ignored: gray().light().step_12(),
|
||||
version_control_conflict_ours_background: green().light().step_10().alpha(0.5),
|
||||
version_control_conflict_theirs_background: blue().light().step_10().alpha(0.5),
|
||||
version_control_conflict_ours_marker_background: green().light().step_10().alpha(0.7),
|
||||
version_control_conflict_theirs_marker_background: blue().light().step_10().alpha(0.7),
|
||||
version_control_conflict_divider_background: Hsla::default(),
|
||||
version_control_conflict_marker_ours: green().light().step_10().alpha(0.5),
|
||||
version_control_conflict_marker_theirs: blue().light().step_10().alpha(0.5),
|
||||
version_control_conflict_marker_border: Hsla::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,11 +263,9 @@ impl ThemeColors {
|
||||
version_control_renamed: MODIFIED_COLOR,
|
||||
version_control_conflict: orange().dark().step_12(),
|
||||
version_control_ignored: gray().dark().step_12(),
|
||||
version_control_conflict_ours_background: green().dark().step_10().alpha(0.5),
|
||||
version_control_conflict_theirs_background: blue().dark().step_10().alpha(0.5),
|
||||
version_control_conflict_ours_marker_background: green().dark().step_10().alpha(0.7),
|
||||
version_control_conflict_theirs_marker_background: blue().dark().step_10().alpha(0.7),
|
||||
version_control_conflict_divider_background: Hsla::default(),
|
||||
version_control_conflict_marker_ours: green().dark().step_10().alpha(0.5),
|
||||
version_control_conflict_marker_theirs: blue().dark().step_10().alpha(0.5),
|
||||
version_control_conflict_marker_border: Hsla::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,23 +207,9 @@ pub(crate) fn zed_default_dark() -> Theme {
|
||||
version_control_renamed: MODIFIED_COLOR,
|
||||
version_control_conflict: crate::orange().light().step_12(),
|
||||
version_control_ignored: crate::gray().light().step_12(),
|
||||
version_control_conflict_ours_background: crate::green()
|
||||
.light()
|
||||
.step_12()
|
||||
.alpha(0.5),
|
||||
version_control_conflict_theirs_background: crate::blue()
|
||||
.light()
|
||||
.step_12()
|
||||
.alpha(0.5),
|
||||
version_control_conflict_ours_marker_background: crate::green()
|
||||
.light()
|
||||
.step_12()
|
||||
.alpha(0.7),
|
||||
version_control_conflict_theirs_marker_background: crate::blue()
|
||||
.light()
|
||||
.step_12()
|
||||
.alpha(0.7),
|
||||
version_control_conflict_divider_background: Hsla::default(),
|
||||
version_control_conflict_marker_ours: crate::green().light().step_12().alpha(0.5),
|
||||
version_control_conflict_marker_theirs: crate::blue().light().step_12().alpha(0.5),
|
||||
version_control_conflict_marker_border: Hsla::default(),
|
||||
},
|
||||
status: StatusColors {
|
||||
conflict: yellow,
|
||||
|
||||
@@ -592,24 +592,16 @@ pub struct ThemeColorsContent {
|
||||
pub version_control_ignored: Option<String>,
|
||||
|
||||
/// Background color for row highlights of "ours" regions in merge conflicts.
|
||||
#[serde(rename = "version_control.conflict.ours_background")]
|
||||
pub version_control_conflict_ours_background: Option<String>,
|
||||
#[serde(rename = "version_control.conflict_marker.ours")]
|
||||
pub version_control_conflict_marker_ours: Option<String>,
|
||||
|
||||
/// Background color for row highlights of "theirs" regions in merge conflicts.
|
||||
#[serde(rename = "version_control.conflict.theirs_background")]
|
||||
pub version_control_conflict_theirs_background: Option<String>,
|
||||
|
||||
/// Background color for row highlights of "ours" conflict markers in merge conflicts.
|
||||
#[serde(rename = "version_control.conflict.ours_marker_background")]
|
||||
pub version_control_conflict_ours_marker_background: Option<String>,
|
||||
|
||||
/// Background color for row highlights of "theirs" conflict markers in merge conflicts.
|
||||
#[serde(rename = "version_control.conflict.theirs_marker_background")]
|
||||
pub version_control_conflict_theirs_marker_background: Option<String>,
|
||||
#[serde(rename = "version_control.conflict_marker.theirs")]
|
||||
pub version_control_conflict_marker_theirs: Option<String>,
|
||||
|
||||
/// Background color for row highlights of the "ours"/"theirs" divider in merge conflicts.
|
||||
#[serde(rename = "version_control.conflict.divider_background")]
|
||||
pub version_control_conflict_divider_background: Option<String>,
|
||||
#[serde(rename = "version_control.conflict_marker.border")]
|
||||
pub version_control_conflict_marker_border: Option<String>,
|
||||
}
|
||||
|
||||
impl ThemeColorsContent {
|
||||
@@ -1067,24 +1059,16 @@ impl ThemeColorsContent {
|
||||
.and_then(|color| try_parse_color(color).ok())
|
||||
// Fall back to `conflict`, for backwards compatibility.
|
||||
.or(status_colors.ignored),
|
||||
version_control_conflict_ours_background: self
|
||||
.version_control_conflict_ours_background
|
||||
version_control_conflict_marker_ours: self
|
||||
.version_control_conflict_marker_ours
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok()),
|
||||
version_control_conflict_theirs_background: self
|
||||
.version_control_conflict_theirs_background
|
||||
version_control_conflict_marker_theirs: self
|
||||
.version_control_conflict_marker_theirs
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok()),
|
||||
version_control_conflict_ours_marker_background: self
|
||||
.version_control_conflict_ours_marker_background
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok()),
|
||||
version_control_conflict_theirs_marker_background: self
|
||||
.version_control_conflict_theirs_marker_background
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok()),
|
||||
version_control_conflict_divider_background: self
|
||||
.version_control_conflict_divider_background
|
||||
version_control_conflict_marker_border: self
|
||||
.version_control_conflict_marker_border
|
||||
.as_ref()
|
||||
.and_then(|color| try_parse_color(color).ok()),
|
||||
}
|
||||
|
||||
@@ -265,12 +265,10 @@ pub struct ThemeColors {
|
||||
pub version_control_ignored: Hsla,
|
||||
|
||||
/// Represents the "ours" region of a merge conflict.
|
||||
pub version_control_conflict_ours_background: Hsla,
|
||||
pub version_control_conflict_marker_ours: Hsla,
|
||||
/// Represents the "theirs" region of a merge conflict.
|
||||
pub version_control_conflict_theirs_background: Hsla,
|
||||
pub version_control_conflict_ours_marker_background: Hsla,
|
||||
pub version_control_conflict_theirs_marker_background: Hsla,
|
||||
pub version_control_conflict_divider_background: Hsla,
|
||||
pub version_control_conflict_marker_theirs: Hsla,
|
||||
pub version_control_conflict_marker_border: Hsla,
|
||||
}
|
||||
|
||||
#[derive(EnumIter, Debug, Clone, Copy, AsRefStr)]
|
||||
|
||||
@@ -18,9 +18,9 @@ impl ApplicationMenuStory {
|
||||
}
|
||||
|
||||
impl Render for ApplicationMenuStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<ApplicationMenu>(cx))
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container()
|
||||
.child(Story::title_for::<ApplicationMenu>())
|
||||
.child(StorySection::new().child(StoryItem::new(
|
||||
"Application Menu",
|
||||
h_flex().child(self.menu.clone()),
|
||||
|
||||
@@ -26,8 +26,8 @@ fn build_menu(
|
||||
pub struct ContextMenuStory;
|
||||
|
||||
impl Render for ContextMenuStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container()
|
||||
.on_action(|_: &PrintCurrentDate, _, _| {
|
||||
println!("printing unix time!");
|
||||
if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() {
|
||||
|
||||
@@ -7,9 +7,9 @@ use crate::prelude::*;
|
||||
pub struct DisclosureStory;
|
||||
|
||||
impl Render for DisclosureStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<Disclosure>(cx))
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container()
|
||||
.child(Story::title_for::<Disclosure>())
|
||||
.child(Story::label("Toggled"))
|
||||
.child(Disclosure::new("toggled", true))
|
||||
.child(Story::label("Not Toggled"))
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{IconButtonShape, Tooltip, prelude::*};
|
||||
pub struct IconButtonStory;
|
||||
|
||||
impl Render for IconButtonStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let default_button = StoryItem::new(
|
||||
"Default",
|
||||
IconButton::new("default_icon_button", IconName::Hash),
|
||||
@@ -113,8 +113,8 @@ impl Render for IconButtonStory {
|
||||
selected_with_tooltip_button,
|
||||
];
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<IconButton>(cx))
|
||||
Story::container()
|
||||
.child(Story::title_for::<IconButton>())
|
||||
.child(StorySection::new().children(buttons))
|
||||
.child(
|
||||
StorySection::new().child(StoryItem::new(
|
||||
|
||||
@@ -15,11 +15,11 @@ impl Render for KeybindingStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let all_modifier_permutations = ["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
|
||||
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<KeyBinding>(cx))
|
||||
.child(Story::label("Single Key", cx))
|
||||
Story::container()
|
||||
.child(Story::title_for::<KeyBinding>())
|
||||
.child(Story::label("Single Key"))
|
||||
.child(KeyBinding::new(binding("Z"), cx))
|
||||
.child(Story::label("Single Key with Modifier", cx))
|
||||
.child(Story::label("Single Key with Modifier"))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
@@ -29,7 +29,7 @@ impl Render for KeybindingStory {
|
||||
.child(KeyBinding::new(binding("cmd-c"), cx))
|
||||
.child(KeyBinding::new(binding("shift-c"), cx)),
|
||||
)
|
||||
.child(Story::label("Single Key with Modifier (Permuted)", cx))
|
||||
.child(Story::label("Single Key with Modifier (Permuted)"))
|
||||
.child(
|
||||
div().flex().flex_col().children(
|
||||
all_modifier_permutations
|
||||
@@ -46,33 +46,33 @@ impl Render for KeybindingStory {
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Single Key with All Modifiers", cx))
|
||||
.child(Story::label("Single Key with All Modifiers"))
|
||||
.child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx))
|
||||
.child(Story::label("Chord", cx))
|
||||
.child(Story::label("Chord"))
|
||||
.child(KeyBinding::new(binding("a z"), cx))
|
||||
.child(Story::label("Chord with Modifier", cx))
|
||||
.child(Story::label("Chord with Modifier"))
|
||||
.child(KeyBinding::new(binding("ctrl-a shift-z"), cx))
|
||||
.child(KeyBinding::new(binding("fn-s"), cx))
|
||||
.child(Story::label("Single Key with All Modifiers (Linux)", cx))
|
||||
.child(Story::label("Single Key with All Modifiers (Linux)"))
|
||||
.child(
|
||||
KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx)
|
||||
.platform_style(PlatformStyle::Linux),
|
||||
)
|
||||
.child(Story::label("Chord (Linux)", cx))
|
||||
.child(Story::label("Chord (Linux)"))
|
||||
.child(KeyBinding::new(binding("a z"), cx).platform_style(PlatformStyle::Linux))
|
||||
.child(Story::label("Chord with Modifier (Linux)", cx))
|
||||
.child(Story::label("Chord with Modifier (Linux)"))
|
||||
.child(
|
||||
KeyBinding::new(binding("ctrl-a shift-z"), cx).platform_style(PlatformStyle::Linux),
|
||||
)
|
||||
.child(KeyBinding::new(binding("fn-s"), cx).platform_style(PlatformStyle::Linux))
|
||||
.child(Story::label("Single Key with All Modifiers (Windows)", cx))
|
||||
.child(Story::label("Single Key with All Modifiers (Windows)"))
|
||||
.child(
|
||||
KeyBinding::new(binding("ctrl-alt-cmd-shift-z"), cx)
|
||||
.platform_style(PlatformStyle::Windows),
|
||||
)
|
||||
.child(Story::label("Chord (Windows)", cx))
|
||||
.child(Story::label("Chord (Windows)"))
|
||||
.child(KeyBinding::new(binding("a z"), cx).platform_style(PlatformStyle::Windows))
|
||||
.child(Story::label("Chord with Modifier (Windows)", cx))
|
||||
.child(Story::label("Chord with Modifier (Windows)"))
|
||||
.child(
|
||||
KeyBinding::new(binding("ctrl-a shift-z"), cx)
|
||||
.platform_style(PlatformStyle::Windows),
|
||||
|
||||
@@ -7,17 +7,17 @@ use crate::{ListHeader, ListSeparator, ListSubHeader, prelude::*};
|
||||
pub struct ListStory;
|
||||
|
||||
impl Render for ListStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<List>(cx))
|
||||
.child(Story::label("Default", cx))
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container()
|
||||
.child(Story::title_for::<List>())
|
||||
.child(Story::label("Default"))
|
||||
.child(
|
||||
List::new()
|
||||
.child(ListItem::new("apple").child("Apple"))
|
||||
.child(ListItem::new("banana").child("Banana"))
|
||||
.child(ListItem::new("cherry").child("Cherry")),
|
||||
)
|
||||
.child(Story::label("With sections", cx))
|
||||
.child(Story::label("With sections"))
|
||||
.child(
|
||||
List::new()
|
||||
.header(ListHeader::new("Produce"))
|
||||
|
||||
@@ -7,20 +7,20 @@ use crate::{IconName, ListHeader};
|
||||
pub struct ListHeaderStory;
|
||||
|
||||
impl Render for ListHeaderStory {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<ListHeader>(cx))
|
||||
.child(Story::label("Default", cx))
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container()
|
||||
.child(Story::title_for::<ListHeader>())
|
||||
.child(Story::label("Default"))
|
||||
.child(ListHeader::new("Section 1"))
|
||||
.child(Story::label("With left icon", cx))
|
||||
.child(Story::label("With left icon"))
|
||||
.child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell)))
|
||||
.child(Story::label("With left icon and meta", cx))
|
||||
.child(Story::label("With left icon and meta"))
|
||||
.child(
|
||||
ListHeader::new("Section 3")
|
||||
.start_slot(Icon::new(IconName::BellOff))
|
||||
.end_slot(IconButton::new("action_1", IconName::Bolt)),
|
||||
)
|
||||
.child(Story::label("With multiple meta", cx))
|
||||
.child(Story::label("With multiple meta"))
|
||||
.child(
|
||||
ListHeader::new("Section 4")
|
||||
.end_slot(IconButton::new("action_1", IconName::Bolt))
|
||||
|
||||
@@ -10,12 +10,12 @@ pub struct ListItemStory;
|
||||
|
||||
impl Render for ListItemStory {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
Story::container(cx)
|
||||
Story::container()
|
||||
.bg(cx.theme().colors().background)
|
||||
.child(Story::title_for::<ListItem>(cx))
|
||||
.child(Story::label("Default", cx))
|
||||
.child(Story::title_for::<ListItem>())
|
||||
.child(Story::label("Default"))
|
||||
.child(ListItem::new("hello_world").child("Hello, world!"))
|
||||
.child(Story::label("Inset", cx))
|
||||
.child(Story::label("Inset"))
|
||||
.child(
|
||||
ListItem::new("inset_list_item")
|
||||
.inset(true)
|
||||
@@ -31,7 +31,7 @@ impl Render for ListItemStory {
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(Story::label("With start slot icon", cx))
|
||||
.child(Story::label("With start slot icon"))
|
||||
.child(
|
||||
ListItem::new("with start slot_icon")
|
||||
.child("Hello, world!")
|
||||
@@ -41,7 +41,7 @@ impl Render for ListItemStory {
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(Story::label("With start slot avatar", cx))
|
||||
.child(Story::label("With start slot avatar"))
|
||||
.child(
|
||||
ListItem::new("with_start slot avatar")
|
||||
.child("Hello, world!")
|
||||
@@ -49,7 +49,7 @@ impl Render for ListItemStory {
|
||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||
)),
|
||||
)
|
||||
.child(Story::label("With end slot", cx))
|
||||
.child(Story::label("With end slot"))
|
||||
.child(
|
||||
ListItem::new("with_left_avatar")
|
||||
.child("Hello, world!")
|
||||
@@ -57,7 +57,7 @@ impl Render for ListItemStory {
|
||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||
)),
|
||||
)
|
||||
.child(Story::label("With end hover slot", cx))
|
||||
.child(Story::label("With end hover slot"))
|
||||
.child(
|
||||
ListItem::new("with_end_hover_slot")
|
||||
.child("Hello, world!")
|
||||
@@ -84,13 +84,13 @@ impl Render for ListItemStory {
|
||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||
)),
|
||||
)
|
||||
.child(Story::label("With `on_click`", cx))
|
||||
.child(Story::label("With `on_click`"))
|
||||
.child(ListItem::new("with_on_click").child("Click me").on_click(
|
||||
|_event, _window, _cx| {
|
||||
println!("Clicked!");
|
||||
},
|
||||
))
|
||||
.child(Story::label("With `on_secondary_mouse_down`", cx))
|
||||
.child(Story::label("With `on_secondary_mouse_down`"))
|
||||
.child(
|
||||
ListItem::new("with_on_secondary_mouse_down")
|
||||
.child("Right click me")
|
||||
@@ -98,10 +98,7 @@ impl Render for ListItemStory {
|
||||
println!("Right mouse down!");
|
||||
}),
|
||||
)
|
||||
.child(Story::label(
|
||||
"With overflowing content in the `end_slot`",
|
||||
cx,
|
||||
))
|
||||
.child(Story::label("With overflowing content in the `end_slot`"))
|
||||
.child(
|
||||
ListItem::new("with_overflowing_content_in_end_slot")
|
||||
.child("An excerpt")
|
||||
@@ -109,7 +106,6 @@ impl Render for ListItemStory {
|
||||
)
|
||||
.child(Story::label(
|
||||
"`inset` with overflowing content in the `end_slot`",
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
ListItem::new("inset_with_overflowing_content_in_end_slot")
|
||||
@@ -119,7 +115,6 @@ impl Render for ListItemStory {
|
||||
)
|
||||
.child(Story::label(
|
||||
"`inset` with overflowing content in `children` and `end_slot`",
|
||||
cx,
|
||||
))
|
||||
.child(
|
||||
ListItem::new("inset_with_overflowing_content_in_children_and_end_slot")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user