Compare commits

...

16 Commits

Author SHA1 Message Date
Nathan Sobo
cb692bfcf5 Add pre-generated files for Bazel builds
These files are generated by build.rs scripts:
- gpui: dispatch_sys.rs, shaders.metallib
- media: bindings.rs
- proto: zed.messages.rs

Used by ex2 Bazel build to avoid running build scripts.
2025-12-25 23:25:24 -07:00
Nathan Sobo
c8b363f4b0 gpui: Expose ShapedLine::width() for pen advancement 2025-12-25 07:47:36 -07:00
Nathan Sobo
47df9d22a0 chore: update generated cargo manifests 2025-12-25 07:47:36 -07:00
Nathan Sobo
93ebd240d4 Merge branch 'ex-pointer-capture' into ex 2025-12-23 12:47:02 -07:00
Nathan Sobo
c7d55b243b Add pointer capture API for stable drag handling
Add minimal pointer capture API to gpui::Window:
- capture_pointer(hitbox_id): starts capture for the given hitbox
- release_pointer(): releases capture
- captured_hitbox(): returns the captured hitbox, if any

When captured, HitboxId::is_hovered() returns true for the captured
hitbox regardless of actual hit testing. Capture is automatically
released on MouseUpEvent.

This enables drag operations (like scrollbar thumb dragging) to
continue working even when the pointer moves outside the element's
bounds during the drag.
2025-12-23 12:46:56 -07:00
Nathan Sobo
d49a8e04e6 Add pointer capture API for stable drag handling
Add minimal pointer capture API to gpui::Window:
- capture_pointer(hitbox_id): starts capture for the given hitbox
- release_pointer(): releases capture
- captured_hitbox(): returns the captured hitbox, if any

When captured, HitboxId::is_hovered() returns true for the captured
hitbox regardless of actual hit testing. Capture is automatically
released on MouseUpEvent.

This enables drag operations (like scrollbar thumb dragging) to
continue working even when the pointer moves outside the element's
bounds during the drag.
2025-12-23 12:30:02 -07:00
Nathan Sobo
1f34525634 Merge origin/main into ex-local 2025-12-18 09:07:13 -07:00
Sean Hagstrom
2d071b0cb6 editor: Fix git-hunk toggling for adjacent hunks (#43187)
Closes #42934 

Release Notes:

- Fix toggling adjacent git-diff hunks based on the reported behaviour
in #42934

---------

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
2025-12-18 16:45:55 +01:00
Alvaro Parker
bd2b0de231 gpui: Add modal dialog window kind (#40291)
Closes #ISSUE

A [modal dialog](https://en.wikipedia.org/wiki/Modal_window) window is a
window that demands the user's immediate attention and blocks
interaction with other parts of the application until it's closed.

- On Windows this is done by disabling the parent window when the dialog
window is created and re-enabling the parent window when closed.
- On Wayland this is done using the
[`XdgDialog`](https://wayland.app/protocols/xdg-dialog-v1) protocol,
which hints to the compositor that the dialog should be modal. While
compositors like GNOME and KDE block parent interaction automatically,
the XDG specification does not guarantee this behavior, compositors may
deliver events to the parent window unfiltered. Since the specification
explicitly requires clients to implement event filtering logic
themselves, this PR implements client-side blocking in GPUI to ensure
consistent modal behavior across all Wayland compositors, including
those like Hyprland that don't block parent interaction.
- On X11 this is done by enabling the application window property
[`_NET_WM_STATE_MODAL`](https://specifications.freedesktop.org/wm/latest/ar01s05.html#id-1.6.8)
state.

I'm unable to implement this on MacOS as I lack the experience and the
hardware to test it. If anyone is interested on implementing this let me
know.

|Window|Linux (wayland)| Linux (x11) |MacOS|
|-|-|-|-|
|<video
src="https://github.com/user-attachments/assets/bfd0733a-445d-4b63-ac6b-ebe098a7dc74"></video>|<video
src="https://github.com/user-attachments/assets/024cd6ec-ff81-4250-a5be-5d207a023f8c"></video>|
N/A | <video
src="https://github.com/user-attachments/assets/656e60a5-26b2-4ee2-8368-1fbbe872453c"></video>|

TODO:

- [x] Block parent interaction client-side on X11

Release Notes:

- Added modal dialog window kind on GPUI

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>
Co-authored-by: Anthony Eid <anthony@zed.dev>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-12-18 16:45:06 +01:00
Bennet Bo Fenner
886de8f54b agent_ui: Improve UX when pasting code into message editor (#45254)
Follow up to #42982

Release Notes:

- agent: Allow pasting code without formatting via ctrl/cmd-shift-v.
- agent: Fixed an issue where pasting a single line of code would always
insert an @mention
2025-12-18 16:38:47 +01:00
Ben Brandt
7a783a91cc acp: Update to agent-client-protocol rust sdk v0.9.2 (#45255)
Release Notes:

- N/A
2025-12-18 15:01:20 +00:00
Nathan Sobo
da6c2a172c WIP: local changes needed by ex 2025-12-16 09:36:05 -07:00
Nathan Sobo
f2409f2605 Run cargo fmt 2025-12-14 12:21:46 -07:00
Nathan Sobo
ce1c228e6e Rename TestAppWindow to TestWindow, internal TestWindow to TestPlatformWindow
- Public API: TestWindow<V> - the new typed test window wrapper
- Internal: TestPlatformWindow - the platform-level mock window (pub(crate))
2025-12-14 10:48:11 -07:00
Nathan Sobo
96ddbd4e13 Add TestApp and TestAppWindow for cleaner GPUI testing
TestApp provides a simpler alternative to TestAppContext with:
- Automatic effect flushing after updates
- Clean window creation returning typed TestAppWindow<V>
- Scene inspection via SceneSnapshot
- Input simulation helpers

Also adds:
- Background::as_solid() helper in color.rs
- SceneSnapshot for inspecting rendered quads/glyphs in scene.rs
2025-12-14 10:43:35 -07:00
Nathan Sobo
f224d2a923 Add TestApp and TestAppWindow for cleaner GPUI testing
Adds zed/crates/gpui/src/app/test_app.rs with:

- TestApp: test context that auto-runs until parked after updates
- TestAppWindow<V>: window wrapper with input simulation helpers

Minor improvement over TestAppContext/VisualTestContext - mainly
convenience (auto-parking, owned window handle, cleaner signatures).

Does NOT solve the deeper issues:
- Scene is still pub(crate), can't inspect rendered output
- Editor still needs FocusHandle which needs real GPUI context
- TestEditor duplication in ex still exists

3 tests included demonstrating basic usage.
2025-12-14 10:34:21 -07:00
34 changed files with 10271 additions and 1016 deletions

32
Cargo.lock generated
View File

@@ -226,9 +226,9 @@ dependencies = [
[[package]]
name = "agent-client-protocol"
version = "0.9.0"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ffe7d502c1e451aafc5aff655000f84d09c9af681354ac0012527009b1af13"
checksum = "d3e527d7dfe0f334313d42d1d9318f0a79665f6f21c440d0798f230a77a7ed6c"
dependencies = [
"agent-client-protocol-schema",
"anyhow",
@@ -243,9 +243,9 @@ dependencies = [
[[package]]
name = "agent-client-protocol-schema"
version = "0.10.0"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8af81cc2d5c3f9c04f73db452efd058333735ba9d51c2cf7ef33c9fee038e7e6"
checksum = "6903a00e8ac822f9bacac59a1932754d7387c72ebb7c9c7439ad021505591da4"
dependencies = [
"anyhow",
"derive_more 2.0.1",
@@ -793,7 +793,7 @@ dependencies = [
"url",
"wayland-backend",
"wayland-client",
"wayland-protocols 0.32.9",
"wayland-protocols",
"zbus",
]
@@ -7370,7 +7370,7 @@ dependencies = [
"wayland-backend",
"wayland-client",
"wayland-cursor",
"wayland-protocols 0.31.2",
"wayland-protocols",
"wayland-protocols-plasma",
"wayland-protocols-wlr",
"windows 0.61.3",
@@ -18927,18 +18927,6 @@ dependencies = [
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
dependencies = [
"bitflags 2.9.4",
"wayland-backend",
"wayland-client",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols"
version = "0.32.9"
@@ -18953,14 +18941,14 @@ dependencies = [
[[package]]
name = "wayland-protocols-plasma"
version = "0.2.0"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479"
checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032"
dependencies = [
"bitflags 2.9.4",
"wayland-backend",
"wayland-client",
"wayland-protocols 0.31.2",
"wayland-protocols",
"wayland-scanner",
]
@@ -18973,7 +18961,7 @@ dependencies = [
"bitflags 2.9.4",
"wayland-backend",
"wayland-client",
"wayland-protocols 0.32.9",
"wayland-protocols",
"wayland-scanner",
]

View File

@@ -1,723 +1,11 @@
[workspace]
resolver = "2"
members = [
"crates/acp_tools",
"crates/acp_thread",
"crates/action_log",
"crates/activity_indicator",
"crates/agent",
"crates/agent_servers",
"crates/agent_settings",
"crates/agent_ui",
"crates/agent_ui_v2",
"crates/ai_onboarding",
"crates/anthropic",
"crates/askpass",
"crates/assets",
"crates/assistant_text_thread",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
"crates/audio",
"crates/auto_update",
"crates/auto_update_helper",
"crates/auto_update_ui",
"crates/aws_http_client",
"crates/bedrock",
"crates/breadcrumbs",
"crates/buffer_diff",
"crates/call",
"crates/channel",
"crates/cli",
"crates/client",
"crates/clock",
"crates/cloud_api_client",
"crates/cloud_api_types",
"crates/cloud_llm_client",
"crates/collab",
"crates/collab_ui",
"crates/collections",
"crates/command_palette",
"crates/command_palette_hooks",
"crates/component",
"crates/context_server",
"crates/copilot",
"crates/crashes",
"crates/credentials_provider",
"crates/dap",
"crates/dap_adapters",
"crates/db",
"crates/debug_adapter_extension",
"crates/debugger_tools",
"crates/debugger_ui",
"crates/deepseek",
"crates/denoise",
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/edit_prediction",
"crates/edit_prediction_types",
"crates/edit_prediction_ui",
"crates/edit_prediction_context",
"crates/editor",
"crates/eval",
"crates/eval_utils",
"crates/explorer_command_injector",
"crates/extension",
"crates/extension_api",
"crates/extension_cli",
"crates/extension_host",
"crates/extensions_ui",
"crates/feature_flags",
"crates/feedback",
"crates/file_finder",
"crates/file_icons",
"crates/fs",
"crates/fs_benchmarks",
"crates/fsevent",
"crates/fuzzy",
"crates/git",
"crates/git_hosting_providers",
"crates/git_ui",
"crates/go_to_line",
"crates/google_ai",
"crates/gpui",
"crates/gpui_macros",
"crates/gpui_tokio",
"crates/html_to_markdown",
"crates/http_client",
"crates/http_client_tls",
"crates/icons",
"crates/image_viewer",
"crates/inspector_ui",
"crates/install_cli",
"crates/journal",
"crates/json_schema_store",
"crates/keymap_editor",
"crates/language",
"crates/language_extension",
"crates/language_model",
"crates/language_models",
"crates/language_onboarding",
"crates/language_selector",
"crates/language_tools",
"crates/languages",
"crates/line_ending_selector",
"crates/livekit_api",
"crates/livekit_client",
"crates/lmstudio",
"crates/lsp",
"crates/markdown",
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/migrator",
"crates/mistral",
"crates/miniprofiler_ui",
"crates/multi_buffer",
"crates/nc",
"crates/net",
"crates/node_runtime",
"crates/notifications",
"crates/ollama",
"crates/onboarding",
"crates/open_ai",
"crates/open_router",
"crates/outline",
"crates/outline_panel",
"crates/panel",
"crates/paths",
"crates/picker",
"crates/prettier",
"crates/project",
"crates/project_benchmarks",
"crates/project_panel",
"crates/project_symbols",
"crates/prompt_store",
"crates/proto",
"crates/recent_projects",
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/scheduler",
"crates/remote",
"crates/remote_server",
"crates/repl",
"crates/reqwest_client",
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/rules_library",
"crates/schema_generator",
"crates/search",
"crates/session",
"crates/settings",
"crates/settings_json",
"crates/settings_macros",
"crates/settings_profile_selector",
"crates/settings_ui",
"crates/snippet",
"crates/snippet_provider",
"crates/snippets_ui",
"crates/sqlez",
"crates/sqlez_macros",
"crates/story",
"crates/storybook",
"crates/streaming_diff",
"crates/sum_tree",
"crates/supermaven",
"crates/supermaven_api",
"crates/codestral",
"crates/svg_preview",
"crates/system_specs",
"crates/tab_switcher",
"crates/task",
"crates/tasks_ui",
"crates/telemetry",
"crates/telemetry_events",
"crates/terminal",
"crates/terminal_view",
"crates/text",
"crates/theme",
"crates/theme_extension",
"crates/theme_importer",
"crates/theme_selector",
"crates/time_format",
"crates/title_bar",
"crates/toolchain_selector",
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
"crates/ui_prompt",
"crates/util",
"crates/util_macros",
"crates/vercel",
"crates/vim",
"crates/vim_mode_setting",
"crates/which_key",
"crates/watch",
"crates/web_search",
"crates/web_search_providers",
"crates/workspace",
"crates/worktree",
"crates/x_ai",
"crates/zed",
"crates/zed_actions",
"crates/zed_env_vars",
"crates/edit_prediction_cli",
"crates/zeta_prompt",
"crates/zlog",
"crates/zlog_settings",
"crates/ztracing",
"crates/ztracing_macro",
#
# Extensions
#
"extensions/glsl",
"extensions/html",
"extensions/proto",
"extensions/slash-commands-example",
"extensions/test-extension",
#
# Tooling
#
"tooling/perf",
"tooling/xtask",
]
default-members = ["crates/zed"]
members = ["crates/askpass", "crates/assets", "crates/clock", "crates/collections", "crates/fs", "crates/fsevent", "crates/git", "crates/gpui", "crates/gpui_macros", "crates/http_client", "crates/http_client_tls", "crates/icons", "crates/media", "crates/migrator", "crates/net", "crates/paths", "crates/proto", "crates/refineable", "crates/release_channel", "crates/reqwest_client", "crates/rope", "crates/scheduler", "crates/settings", "crates/settings_json", "crates/settings_macros", "crates/sum_tree", "crates/text", "crates/theme", "crates/util", "crates/util_macros", "crates/zlog", "crates/ztracing", "crates/ztracing_macro", "tooling/perf"]
[workspace.package]
publish = false
edition = "2024"
[workspace.dependencies]
#
# Workspace member crates
#
acp_tools = { path = "crates/acp_tools" }
acp_thread = { path = "crates/acp_thread" }
action_log = { path = "crates/action_log" }
agent = { path = "crates/agent" }
activity_indicator = { path = "crates/activity_indicator" }
agent_ui = { path = "crates/agent_ui" }
agent_ui_v2 = { path = "crates/agent_ui_v2" }
agent_settings = { path = "crates/agent_settings" }
agent_servers = { path = "crates/agent_servers" }
ai_onboarding = { path = "crates/ai_onboarding" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
assistant_text_thread = { path = "crates/assistant_text_thread" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
auto_update_ui = { path = "crates/auto_update_ui" }
aws_http_client = { path = "crates/aws_http_client" }
bedrock = { path = "crates/bedrock" }
breadcrumbs = { path = "crates/breadcrumbs" }
buffer_diff = { path = "crates/buffer_diff" }
call = { path = "crates/call" }
channel = { path = "crates/channel" }
cli = { path = "crates/cli" }
client = { path = "crates/client" }
clock = { path = "crates/clock" }
cloud_api_client = { path = "crates/cloud_api_client" }
cloud_api_types = { path = "crates/cloud_api_types" }
cloud_llm_client = { path = "crates/cloud_llm_client" }
collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections", version = "0.1.0" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
context_server = { path = "crates/context_server" }
copilot = { path = "crates/copilot" }
crashes = { path = "crates/crashes" }
credentials_provider = { path = "crates/credentials_provider" }
crossbeam = "0.8.4"
dap = { path = "crates/dap" }
dap_adapters = { path = "crates/dap_adapters" }
db = { path = "crates/db" }
debug_adapter_extension = { path = "crates/debug_adapter_extension" }
debugger_tools = { path = "crates/debugger_tools" }
debugger_ui = { path = "crates/debugger_ui" }
deepseek = { path = "crates/deepseek" }
derive_refineable = { path = "crates/refineable/derive_refineable" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
eval_utils = { path = "crates/eval_utils" }
extension = { path = "crates/extension" }
extension_host = { path = "crates/extension_host" }
extensions_ui = { path = "crates/extensions_ui" }
feature_flags = { path = "crates/feature_flags" }
feedback = { path = "crates/feedback" }
file_finder = { path = "crates/file_finder" }
file_icons = { path = "crates/file_icons" }
fs = { path = "crates/fs" }
fsevent = { path = "crates/fsevent" }
fuzzy = { path = "crates/fuzzy" }
git = { path = "crates/git" }
git_hosting_providers = { path = "crates/git_hosting_providers" }
git_ui = { path = "crates/git_ui" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false }
gpui_macros = { path = "crates/gpui_macros" }
gpui_tokio = { path = "crates/gpui_tokio" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
http_client_tls = { path = "crates/http_client_tls" }
icons = { path = "crates/icons" }
image_viewer = { path = "crates/image_viewer" }
edit_prediction_types = { path = "crates/edit_prediction_types" }
edit_prediction_ui = { path = "crates/edit_prediction_ui" }
edit_prediction_context = { path = "crates/edit_prediction_context" }
inspector_ui = { path = "crates/inspector_ui" }
install_cli = { path = "crates/install_cli" }
journal = { path = "crates/journal" }
json_schema_store = { path = "crates/json_schema_store" }
keymap_editor = { path = "crates/keymap_editor" }
language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" }
language_models = { path = "crates/language_models" }
language_onboarding = { path = "crates/language_onboarding" }
language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" }
languages = { path = "crates/languages" }
line_ending_selector = { path = "crates/line_ending_selector" }
livekit_api = { path = "crates/livekit_api" }
livekit_client = { path = "crates/livekit_client" }
lmstudio = { path = "crates/lmstudio" }
lsp = { path = "crates/lsp" }
markdown = { path = "crates/markdown" }
markdown_preview = { path = "crates/markdown_preview" }
svg_preview = { path = "crates/svg_preview" }
media = { path = "crates/media" }
menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
mistral = { path = "crates/mistral" }
multi_buffer = { path = "crates/multi_buffer" }
miniprofiler_ui = { path = "crates/miniprofiler_ui" }
nc = { path = "crates/nc" }
net = { path = "crates/net" }
node_runtime = { path = "crates/node_runtime" }
notifications = { path = "crates/notifications" }
ollama = { path = "crates/ollama" }
onboarding = { path = "crates/onboarding" }
open_ai = { path = "crates/open_ai" }
open_router = { path = "crates/open_router", features = ["schemars"] }
outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
panel = { path = "crates/panel" }
paths = { path = "crates/paths" }
perf = { path = "tooling/perf" }
picker = { path = "crates/picker" }
prettier = { path = "crates/prettier" }
settings_profile_selector = { path = "crates/settings_profile_selector" }
project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" }
release_channel = { path = "crates/release_channel" }
remote = { path = "crates/remote" }
remote_server = { path = "crates/remote_server" }
repl = { path = "crates/repl" }
reqwest_client = { path = "crates/reqwest_client" }
rodio = { git = "https://github.com/RustAudio/rodio", rev ="e2074c6c2acf07b57cf717e076bdda7a9ac6e70b", features = ["wav", "playback", "wav_output", "recording"] }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
rules_library = { path = "crates/rules_library" }
search = { path = "crates/search" }
session = { path = "crates/session" }
settings = { path = "crates/settings" }
settings_json = { path = "crates/settings_json" }
settings_macros = { path = "crates/settings_macros" }
settings_ui = { path = "crates/settings_ui" }
snippet = { path = "crates/snippet" }
snippet_provider = { path = "crates/snippet_provider" }
snippets_ui = { path = "crates/snippets_ui" }
sqlez = { path = "crates/sqlez" }
sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
codestral = { path = "crates/codestral" }
system_specs = { path = "crates/system_specs" }
tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
telemetry = { path = "crates/telemetry" }
telemetry_events = { path = "crates/telemetry_events" }
terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
theme = { path = "crates/theme" }
theme_extension = { path = "crates/theme_extension" }
theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
toolchain_selector = { path = "crates/toolchain_selector" }
ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
ui_prompt = { path = "crates/ui_prompt" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vercel = { path = "crates/vercel" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
which_key = { path = "crates/which_key" }
watch = { path = "crates/watch" }
web_search = { path = "crates/web_search" }
web_search_providers = { path = "crates/web_search_providers" }
workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
x_ai = { path = "crates/x_ai" }
zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
zed_env_vars = { path = "crates/zed_env_vars" }
edit_prediction = { path = "crates/edit_prediction" }
zeta_prompt = { path = "crates/zeta_prompt" }
zlog = { path = "crates/zlog" }
zlog_settings = { path = "crates/zlog_settings" }
ztracing = { path = "crates/ztracing" }
ztracing_macro = { path = "crates/ztracing_macro" }
#
# External crates
#
agent-client-protocol = { version = "=0.9.0", features = ["unstable"] }
aho-corasick = "1.1"
alacritty_terminal = "0.25.1-rc1"
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
ashpd = { version = "0.11", default-features = false, features = ["async-std"] }
async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.1"
async-task = "4.7"
async-trait = "0.1"
async-tungstenite = "0.31.0"
async_zip = { version = "0.0.18", features = ["deflate", "deflate64"] }
aws-config = { version = "1.8.10", features = ["behavior-version-latest"] }
aws-credential-types = { version = "1.2.8", features = [
"hardcoded-credentials",
] }
aws-sdk-bedrockruntime = { version = "1.112.0", features = [
"behavior-version-latest",
] }
aws-smithy-runtime-api = { version = "1.9.2", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.4", features = ["http-body-1-x"] }
backtrace = "0.3"
base64 = "0.22"
bincode = "1.2.1"
bitflags = "2.6.0"
blade-graphics = { version = "0.7.0" }
blade-macros = { version = "0.3.0" }
blade-util = { version = "0.3.0" }
brotli = "8.0.2"
bytes = "1.0"
cargo_metadata = "0.19"
cargo_toml = "0.21"
cfg-if = "1.0.3"
chardetng = "0.1"
chrono = { version = "0.4", features = ["serde"] }
ciborium = "0.2"
circular-buffer = "1.0"
clap = { version = "4.4", features = ["derive", "wrap_help"] }
cocoa = "=0.26.0"
cocoa-foundation = "=0.2.0"
convert_case = "0.8.0"
core-foundation = "=0.10.0"
core-foundation-sys = "0.8.6"
core-video = { version = "0.4.3", features = ["metal"] }
cpal = "0.16"
crash-handler = "0.6"
criterion = { version = "0.5", features = ["html_reports"] }
ctor = "0.4.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "1b461b310481d01e02b2603c16d7144b926339f8" }
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
documented = "0.9.1"
dotenvy = "0.15.0"
ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
encoding_rs = "0.8"
exec = "0.3.1"
fancy-regex = "0.16.0"
fork = "0.4.0"
futures = "0.3"
futures-lite = "1.13"
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "09acfdf2bd5c1d6254abefd609c808ff73547b2c" }
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
handlebars = "4.3"
heck = "0.5"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
human_bytes = "0.4.1"
html5ever = "0.27.0"
http = "1.1"
http-body = "1.0"
hyper = "0.14"
ignore = "0.4.22"
image = "0.25.1"
imara-diff = "0.1.8"
indexmap = { version = "2.7.0", features = ["serde"] }
indoc = "2"
inventory = "0.3.19"
itertools = "0.14.0"
json_dotpath = "1.1"
jsonschema = "0.37.0"
jsonwebtoken = "9.3"
jupyter-protocol = "0.10.0"
jupyter-websocket-client = "0.15.0"
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "b71ab4eeb27d9758be8092020a46fe33fbca4e33" }
mach2 = "0.5"
markup5ever_rcdom = "0.3.0"
metal = "0.29"
minidumper = "0.8"
moka = { version = "0.12.10", features = ["sync"] }
naga = { version = "25.0", features = ["wgsl-in"] }
nanoid = "0.4"
nbformat = "0.15.0"
nix = "0.29"
num-format = "0.4.4"
objc = "0.2"
objc2-foundation = { version = "=0.3.1", default-features = false, features = [
"NSArray",
"NSAttributedString",
"NSBundle",
"NSCoder",
"NSData",
"NSDate",
"NSDictionary",
"NSEnumerator",
"NSError",
"NSGeometry",
"NSNotification",
"NSNull",
"NSObjCRuntime",
"NSObject",
"NSProcessInfo",
"NSRange",
"NSRunLoop",
"NSString",
"NSURL",
"NSUndoManager",
"NSValue",
"objc2-core-foundation",
"std"
] }
open = "5.0.0"
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
partial-json-fixer = "0.5.3"
parse_int = "0.9"
pciid-parser = "0.8.0"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
portable-pty = "0.9.0"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
proc-macro2 = "1.0.93"
profiling = "1"
prost = "0.9"
prost-build = "0.9"
prost-types = "0.9"
pulldown-cmark = { version = "0.12.0", default-features = false }
quote = "1.0.9"
rand = "0.9"
rayon = "1.8"
regex = "1.5"
# WARNING: If you change this, you must also publish a new version of zed-reqwest to crates.io
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662463bda39148ba154100dd44d3fba5873a4", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
"multipart",
"rustls-tls-native-roots",
"socks",
"stream",
], package = "zed-reqwest", version = "0.12.15-zed" }
rsa = "0.9.6"
runtimelib = { version = "0.30.0", default-features = false, features = [
"async-dispatcher-runtime", "aws-lc-rs"
] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
# WARNING: If you change this, you must also publish a new version of zed-scap to crates.io
scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
schemars = { version = "1.0", features = ["indexmap2"] }
semver = { version = "1.0", features = ["serde"] }
serde = { version = "1.0.221", features = ["derive", "rc"] }
serde_json = { version = "1.0.144", features = ["preserve_order", "raw_value"] }
serde_json_lenient = { version = "0.2", features = [
"preserve_order",
"raw_value",
] }
serde_path_to_error = "0.1.17"
serde_repr = "0.1"
serde_urlencoded = "0.7"
sha2 = "0.10"
shellexpand = "2.1.0"
shlex = "1.3.0"
simplelog = "0.12.2"
slotmap = "1.0.6"
smallvec = { version = "1.6", features = ["union", "const_new"] }
smol = "2.0"
sqlformat = "0.2"
stacksafe = "0.1"
streaming-iterator = "0.1"
strsim = "0.11"
strum = { version = "0.27.2", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "2.0.101", features = ["full", "extra-traits", "visit-mut"] }
sys-locale = "0.3.1"
sysinfo = "0.37.0"
take-until = "0.2.0"
tempfile = "3.20.0"
thiserror = "2.0.12"
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "2570c4387a8505fb8f1d3f3557454b474f1e8271" }
time = { version = "0.3", features = [
"macros",
"parsing",
"serde",
"serde-well-known",
"formatting",
"local-offset",
] }
tiny_http = "0.8"
tokio = { version = "1" }
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io", "tokio"] }
toml = "0.8"
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
tower-http = "0.4.4"
tree-sitter = { version = "0.26", features = ["wasm"] }
tree-sitter-bash = "0.25.1"
tree-sitter-c = "0.23"
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
tree-sitter-css = "0.23"
tree-sitter-diff = "0.1.0"
tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0"
tree-sitter-gitcommit = { git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9" }
tree-sitter-go = "0.23"
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "2e886870578eeba1927a2dc4bd2e2b3f598c5f9a", package = "tree-sitter-gomod" }
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex", rev = "1dd45142fbb05562e35b2040c6129c9bca346592" }
tree-sitter-html = "0.23"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.24"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = "0.25"
tree-sitter-regex = "0.24"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.24"
tree-sitter-typescript = { git = "https://github.com/zed-industries/tree-sitter-typescript", rev = "e2c53597d6a5d9cf7bbe8dccde576fe1e46c5899" } # https://github.com/tree-sitter/tree-sitter-typescript/pull/347
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
tracing = "0.1.40"
unicase = "2.6"
unicode-script = "0.5.7"
unicode-segmentation = "1.10"
unindent = "0.2.0"
url = "2.2"
urlencoding = "2.1.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
walkdir = "2.5"
wasm-encoder = "0.221"
wasmparser = "0.221"
wasmtime = { version = "33", default-features = false, features = [
"async",
"demangle",
"runtime",
"cranelift",
"component-model",
"incremental-cache",
"parallel-compilation",
] }
wasmtime-wasi = "33"
wax = "0.6"
which = "6.0.0"
windows-core = "0.61"
yawc = "0.2.5"
zeroize = "1.8"
zstd = "0.11"
[workspace.dependencies.windows]
version = "0.61"
@@ -770,12 +58,6 @@ features = [
"Win32_UI_WindowsAndMessaging",
]
[patch.crates-io]
notify = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" }
calloop = { git = "https://github.com/zed-industries/calloop" }
[profile.dev]
split-debuginfo = "unpacked"
# https://github.com/rust-lang/cargo/issues/16104
@@ -897,13 +179,157 @@ large_enum_variant = "allow"
# Boolean expressions can be hard to read, requiring only the minimal form gets in the way
nonminimal_bool = "allow"
[workspace.metadata.cargo-machete]
ignored = [
"bindgen",
"cbindgen",
"prost_build",
[workspace.dependencies]
anyhow = "1.0.86"
ashpd = { version = "0.11", default-features = false, features = ["async-std"] }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-fs = "2.1"
async-tar = "0.5.1"
async-task = "4.7"
async-trait = "0.1"
async_zip = { version = "0.0.18", features = ["deflate", "deflate64"] }
backtrace = "0.3"
bitflags = "2.6.0"
blade-graphics = { version = "0.7.0" }
blade-macros = { version = "0.3.0" }
blade-util = { version = "0.3.0" }
bytes = "1.0"
chrono = { version = "0.4", features = ["serde"] }
circular-buffer = "1.0"
clock = { path = "crates/clock" }
cocoa = "=0.26.0"
cocoa-foundation = "=0.2.0"
collections = { path = "crates/collections", version = "0.1.0" }
convert_case = "0.8.0"
core-foundation = "=0.10.0"
core-foundation-sys = "0.8.6"
core-video = { version = "0.4.3", features = ["metal"] }
criterion = { version = "0.5", features = ["html_reports"] }
ctor = "0.4.0"
derive_more = "0.99.17"
derive_refineable = { path = "crates/refineable/derive_refineable" }
dirs = "4.0"
ec4rs = "1.1"
env_logger = "0.11"
fs = { path = "crates/fs" }
fsevent = { path = "crates/fsevent" }
futures = "0.3"
futures-lite = "1.13"
git = { path = "crates/git" }
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
gpui = { path = "crates/gpui", default-features = false }
gpui_macros = { path = "crates/gpui_macros" }
heck = "0.5"
http = "1.1"
http-body = "1.0"
http_client = { path = "crates/http_client" }
http_client_tls = { path = "crates/http_client_tls" }
icons = { path = "crates/icons" }
ignore = "0.4.22"
image = "0.25.1"
indexmap = { version = "2.7.0", features = ["serde"] }
indoc = "2"
inventory = "0.3.19"
itertools = "0.14.0"
libc = "0.2"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
mach2 = "0.5"
media = { path = "crates/media" }
metal = "0.29"
migrator = { path = "crates/migrator" }
naga = { version = "25.0", features = ["wgsl-in"] }
net = { path = "crates/net" }
nix = "0.29"
objc = "0.2"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
paths = { path = "crates/paths" }
perf = { path = "tooling/perf" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
proc-macro2 = "1.0.93"
profiling = "1"
prost = "0.9"
prost-build = "0.9"
proto = { path = "crates/proto" }
quote = "1.0.9"
rand = "0.9"
rayon = "1.8"
refineable = { path = "crates/refineable" }
regex = "1.5"
release_channel = { path = "crates/release_channel" }
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662463bda39148ba154100dd44d3fba5873a4", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
"multipart",
"rustls-tls-native-roots",
"socks",
"stream",
], package = "zed-reqwest", version = "0.12.15-zed" }
reqwest_client = { path = "crates/reqwest_client" }
rope = { path = "crates/rope" }
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
schemars = { version = "1.0", features = ["indexmap2"] }
semver = { version = "1.0", features = ["serde"] }
serde = { version = "1.0.221", features = ["derive", "rc"] }
serde_json = { version = "1.0.144", features = ["preserve_order", "raw_value"] }
serde_json_lenient = { version = "0.2", features = [
"preserve_order",
"raw_value",
] }
serde_path_to_error = "0.1.17"
serde_repr = "0.1"
serde_urlencoded = "0.7"
settings = { path = "crates/settings" }
settings_json = { path = "crates/settings_json" }
settings_macros = { path = "crates/settings_macros" }
sha2 = "0.10"
shlex = "1.3.0"
slotmap = "1.0.6"
smallvec = { version = "1.6", features = ["union", "const_new"] }
smol = "2.0"
stacksafe = "0.1"
streaming-iterator = "0.1"
strum = { version = "0.27.2", features = ["derive"] }
sum_tree = { path = "crates/sum_tree" }
syn = { version = "2.0.101", features = ["full", "extra-traits", "visit-mut"] }
take-until = "0.2.0"
tempfile = "3.20.0"
text = { path = "crates/text" }
theme = { path = "crates/theme" }
thiserror = "2.0.12"
time = { version = "0.3", features = [
"macros",
"parsing",
"serde",
"component",
"documented",
"sea-orm-macros",
]
"serde-well-known",
"formatting",
"local-offset",
] }
tokio = { version = "1" }
tracing = "0.1.40"
tree-sitter = { version = "0.26", features = ["wasm"] }
tree-sitter-json = "0.24"
unicase = "2.6"
unicode-segmentation = "1.10"
unindent = "0.2.0"
url = "2.2"
urlencoding = "2.1.2"
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
walkdir = "2.5"
which = "6.0.0"
windows-core = "0.61"
zeroize = "1.8"
zlog = { path = "crates/zlog" }
ztracing = { path = "crates/ztracing" }
ztracing_macro = { path = "crates/ztracing_macro" }

909
Cargo.toml.full Normal file
View File

@@ -0,0 +1,909 @@
[workspace]
resolver = "2"
members = [
"crates/acp_tools",
"crates/acp_thread",
"crates/action_log",
"crates/activity_indicator",
"crates/agent",
"crates/agent_servers",
"crates/agent_settings",
"crates/agent_ui",
"crates/agent_ui_v2",
"crates/ai_onboarding",
"crates/anthropic",
"crates/askpass",
"crates/assets",
"crates/assistant_text_thread",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
"crates/audio",
"crates/auto_update",
"crates/auto_update_helper",
"crates/auto_update_ui",
"crates/aws_http_client",
"crates/bedrock",
"crates/breadcrumbs",
"crates/buffer_diff",
"crates/call",
"crates/channel",
"crates/cli",
"crates/client",
"crates/clock",
"crates/cloud_api_client",
"crates/cloud_api_types",
"crates/cloud_llm_client",
"crates/collab",
"crates/collab_ui",
"crates/collections",
"crates/command_palette",
"crates/command_palette_hooks",
"crates/component",
"crates/context_server",
"crates/copilot",
"crates/crashes",
"crates/credentials_provider",
"crates/dap",
"crates/dap_adapters",
"crates/db",
"crates/debug_adapter_extension",
"crates/debugger_tools",
"crates/debugger_ui",
"crates/deepseek",
"crates/denoise",
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/edit_prediction",
"crates/edit_prediction_types",
"crates/edit_prediction_ui",
"crates/edit_prediction_context",
"crates/editor",
"crates/eval",
"crates/eval_utils",
"crates/explorer_command_injector",
"crates/extension",
"crates/extension_api",
"crates/extension_cli",
"crates/extension_host",
"crates/extensions_ui",
"crates/feature_flags",
"crates/feedback",
"crates/file_finder",
"crates/file_icons",
"crates/fs",
"crates/fs_benchmarks",
"crates/fsevent",
"crates/fuzzy",
"crates/git",
"crates/git_hosting_providers",
"crates/git_ui",
"crates/go_to_line",
"crates/google_ai",
"crates/gpui",
"crates/gpui_macros",
"crates/gpui_tokio",
"crates/html_to_markdown",
"crates/http_client",
"crates/http_client_tls",
"crates/icons",
"crates/image_viewer",
"crates/inspector_ui",
"crates/install_cli",
"crates/journal",
"crates/json_schema_store",
"crates/keymap_editor",
"crates/language",
"crates/language_extension",
"crates/language_model",
"crates/language_models",
"crates/language_onboarding",
"crates/language_selector",
"crates/language_tools",
"crates/languages",
"crates/line_ending_selector",
"crates/livekit_api",
"crates/livekit_client",
"crates/lmstudio",
"crates/lsp",
"crates/markdown",
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/migrator",
"crates/mistral",
"crates/miniprofiler_ui",
"crates/multi_buffer",
"crates/nc",
"crates/net",
"crates/node_runtime",
"crates/notifications",
"crates/ollama",
"crates/onboarding",
"crates/open_ai",
"crates/open_router",
"crates/outline",
"crates/outline_panel",
"crates/panel",
"crates/paths",
"crates/picker",
"crates/prettier",
"crates/project",
"crates/project_benchmarks",
"crates/project_panel",
"crates/project_symbols",
"crates/prompt_store",
"crates/proto",
"crates/recent_projects",
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/scheduler",
"crates/remote",
"crates/remote_server",
"crates/repl",
"crates/reqwest_client",
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/rules_library",
"crates/schema_generator",
"crates/search",
"crates/session",
"crates/settings",
"crates/settings_json",
"crates/settings_macros",
"crates/settings_profile_selector",
"crates/settings_ui",
"crates/snippet",
"crates/snippet_provider",
"crates/snippets_ui",
"crates/sqlez",
"crates/sqlez_macros",
"crates/story",
"crates/storybook",
"crates/streaming_diff",
"crates/sum_tree",
"crates/supermaven",
"crates/supermaven_api",
"crates/codestral",
"crates/svg_preview",
"crates/system_specs",
"crates/tab_switcher",
"crates/task",
"crates/tasks_ui",
"crates/telemetry",
"crates/telemetry_events",
"crates/terminal",
"crates/terminal_view",
"crates/text",
"crates/theme",
"crates/theme_extension",
"crates/theme_importer",
"crates/theme_selector",
"crates/time_format",
"crates/title_bar",
"crates/toolchain_selector",
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
"crates/ui_prompt",
"crates/util",
"crates/util_macros",
"crates/vercel",
"crates/vim",
"crates/vim_mode_setting",
"crates/which_key",
"crates/watch",
"crates/web_search",
"crates/web_search_providers",
"crates/workspace",
"crates/worktree",
"crates/x_ai",
"crates/zed",
"crates/zed_actions",
"crates/zed_env_vars",
"crates/edit_prediction_cli",
"crates/zeta_prompt",
"crates/zlog",
"crates/zlog_settings",
"crates/ztracing",
"crates/ztracing_macro",
#
# Extensions
#
"extensions/glsl",
"extensions/html",
"extensions/proto",
"extensions/slash-commands-example",
"extensions/test-extension",
#
# Tooling
#
"tooling/perf",
"tooling/xtask",
]
default-members = ["crates/zed"]
[workspace.package]
publish = false
edition = "2024"
[workspace.dependencies]
#
# Workspace member crates
#
acp_tools = { path = "crates/acp_tools" }
acp_thread = { path = "crates/acp_thread" }
action_log = { path = "crates/action_log" }
agent = { path = "crates/agent" }
activity_indicator = { path = "crates/activity_indicator" }
agent_ui = { path = "crates/agent_ui" }
agent_ui_v2 = { path = "crates/agent_ui_v2" }
agent_settings = { path = "crates/agent_settings" }
agent_servers = { path = "crates/agent_servers" }
ai_onboarding = { path = "crates/ai_onboarding" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
assistant_text_thread = { path = "crates/assistant_text_thread" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
auto_update_ui = { path = "crates/auto_update_ui" }
aws_http_client = { path = "crates/aws_http_client" }
bedrock = { path = "crates/bedrock" }
breadcrumbs = { path = "crates/breadcrumbs" }
buffer_diff = { path = "crates/buffer_diff" }
call = { path = "crates/call" }
channel = { path = "crates/channel" }
cli = { path = "crates/cli" }
client = { path = "crates/client" }
clock = { path = "crates/clock" }
cloud_api_client = { path = "crates/cloud_api_client" }
cloud_api_types = { path = "crates/cloud_api_types" }
cloud_llm_client = { path = "crates/cloud_llm_client" }
collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections", version = "0.1.0" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
context_server = { path = "crates/context_server" }
copilot = { path = "crates/copilot" }
crashes = { path = "crates/crashes" }
credentials_provider = { path = "crates/credentials_provider" }
crossbeam = "0.8.4"
dap = { path = "crates/dap" }
dap_adapters = { path = "crates/dap_adapters" }
db = { path = "crates/db" }
debug_adapter_extension = { path = "crates/debug_adapter_extension" }
debugger_tools = { path = "crates/debugger_tools" }
debugger_ui = { path = "crates/debugger_ui" }
deepseek = { path = "crates/deepseek" }
derive_refineable = { path = "crates/refineable/derive_refineable" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
eval_utils = { path = "crates/eval_utils" }
extension = { path = "crates/extension" }
extension_host = { path = "crates/extension_host" }
extensions_ui = { path = "crates/extensions_ui" }
feature_flags = { path = "crates/feature_flags" }
feedback = { path = "crates/feedback" }
file_finder = { path = "crates/file_finder" }
file_icons = { path = "crates/file_icons" }
fs = { path = "crates/fs" }
fsevent = { path = "crates/fsevent" }
fuzzy = { path = "crates/fuzzy" }
git = { path = "crates/git" }
git_hosting_providers = { path = "crates/git_hosting_providers" }
git_ui = { path = "crates/git_ui" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false }
gpui_macros = { path = "crates/gpui_macros" }
gpui_tokio = { path = "crates/gpui_tokio" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
http_client_tls = { path = "crates/http_client_tls" }
icons = { path = "crates/icons" }
image_viewer = { path = "crates/image_viewer" }
edit_prediction_types = { path = "crates/edit_prediction_types" }
edit_prediction_ui = { path = "crates/edit_prediction_ui" }
edit_prediction_context = { path = "crates/edit_prediction_context" }
inspector_ui = { path = "crates/inspector_ui" }
install_cli = { path = "crates/install_cli" }
journal = { path = "crates/journal" }
json_schema_store = { path = "crates/json_schema_store" }
keymap_editor = { path = "crates/keymap_editor" }
language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" }
language_models = { path = "crates/language_models" }
language_onboarding = { path = "crates/language_onboarding" }
language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" }
languages = { path = "crates/languages" }
line_ending_selector = { path = "crates/line_ending_selector" }
livekit_api = { path = "crates/livekit_api" }
livekit_client = { path = "crates/livekit_client" }
lmstudio = { path = "crates/lmstudio" }
lsp = { path = "crates/lsp" }
markdown = { path = "crates/markdown" }
markdown_preview = { path = "crates/markdown_preview" }
svg_preview = { path = "crates/svg_preview" }
media = { path = "crates/media" }
menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
mistral = { path = "crates/mistral" }
multi_buffer = { path = "crates/multi_buffer" }
miniprofiler_ui = { path = "crates/miniprofiler_ui" }
nc = { path = "crates/nc" }
net = { path = "crates/net" }
node_runtime = { path = "crates/node_runtime" }
notifications = { path = "crates/notifications" }
ollama = { path = "crates/ollama" }
onboarding = { path = "crates/onboarding" }
open_ai = { path = "crates/open_ai" }
open_router = { path = "crates/open_router", features = ["schemars"] }
outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
panel = { path = "crates/panel" }
paths = { path = "crates/paths" }
perf = { path = "tooling/perf" }
picker = { path = "crates/picker" }
prettier = { path = "crates/prettier" }
settings_profile_selector = { path = "crates/settings_profile_selector" }
project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" }
release_channel = { path = "crates/release_channel" }
remote = { path = "crates/remote" }
remote_server = { path = "crates/remote_server" }
repl = { path = "crates/repl" }
reqwest_client = { path = "crates/reqwest_client" }
rodio = { git = "https://github.com/RustAudio/rodio", rev ="e2074c6c2acf07b57cf717e076bdda7a9ac6e70b", features = ["wav", "playback", "wav_output", "recording"] }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
rules_library = { path = "crates/rules_library" }
search = { path = "crates/search" }
session = { path = "crates/session" }
settings = { path = "crates/settings" }
settings_json = { path = "crates/settings_json" }
settings_macros = { path = "crates/settings_macros" }
settings_ui = { path = "crates/settings_ui" }
snippet = { path = "crates/snippet" }
snippet_provider = { path = "crates/snippet_provider" }
snippets_ui = { path = "crates/snippets_ui" }
sqlez = { path = "crates/sqlez" }
sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
codestral = { path = "crates/codestral" }
system_specs = { path = "crates/system_specs" }
tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
telemetry = { path = "crates/telemetry" }
telemetry_events = { path = "crates/telemetry_events" }
terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
theme = { path = "crates/theme" }
theme_extension = { path = "crates/theme_extension" }
theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
toolchain_selector = { path = "crates/toolchain_selector" }
ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
ui_prompt = { path = "crates/ui_prompt" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vercel = { path = "crates/vercel" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
which_key = { path = "crates/which_key" }
watch = { path = "crates/watch" }
web_search = { path = "crates/web_search" }
web_search_providers = { path = "crates/web_search_providers" }
workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
x_ai = { path = "crates/x_ai" }
zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
zed_env_vars = { path = "crates/zed_env_vars" }
edit_prediction = { path = "crates/edit_prediction" }
zeta_prompt = { path = "crates/zeta_prompt" }
zlog = { path = "crates/zlog" }
zlog_settings = { path = "crates/zlog_settings" }
ztracing = { path = "crates/ztracing" }
ztracing_macro = { path = "crates/ztracing_macro" }
#
# External crates
#
agent-client-protocol = { version = "=0.9.2", features = ["unstable"] }
aho-corasick = "1.1"
alacritty_terminal = "0.25.1-rc1"
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
ashpd = { version = "0.11", default-features = false, features = ["async-std"] }
async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.1"
async-task = "4.7"
async-trait = "0.1"
async-tungstenite = "0.31.0"
async_zip = { version = "0.0.18", features = ["deflate", "deflate64"] }
aws-config = { version = "1.8.10", features = ["behavior-version-latest"] }
aws-credential-types = { version = "1.2.8", features = [
"hardcoded-credentials",
] }
aws-sdk-bedrockruntime = { version = "1.112.0", features = [
"behavior-version-latest",
] }
aws-smithy-runtime-api = { version = "1.9.2", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.4", features = ["http-body-1-x"] }
backtrace = "0.3"
base64 = "0.22"
bincode = "1.2.1"
bitflags = "2.6.0"
blade-graphics = { version = "0.7.0" }
blade-macros = { version = "0.3.0" }
blade-util = { version = "0.3.0" }
brotli = "8.0.2"
bytes = "1.0"
cargo_metadata = "0.19"
cargo_toml = "0.21"
cfg-if = "1.0.3"
chardetng = "0.1"
chrono = { version = "0.4", features = ["serde"] }
ciborium = "0.2"
circular-buffer = "1.0"
clap = { version = "4.4", features = ["derive", "wrap_help"] }
cocoa = "=0.26.0"
cocoa-foundation = "=0.2.0"
convert_case = "0.8.0"
core-foundation = "=0.10.0"
core-foundation-sys = "0.8.6"
core-video = { version = "0.4.3", features = ["metal"] }
cpal = "0.16"
crash-handler = "0.6"
criterion = { version = "0.5", features = ["html_reports"] }
ctor = "0.4.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "1b461b310481d01e02b2603c16d7144b926339f8" }
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
documented = "0.9.1"
dotenvy = "0.15.0"
ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
encoding_rs = "0.8"
exec = "0.3.1"
fancy-regex = "0.16.0"
fork = "0.4.0"
futures = "0.3"
futures-lite = "1.13"
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "09acfdf2bd5c1d6254abefd609c808ff73547b2c" }
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
handlebars = "4.3"
heck = "0.5"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
human_bytes = "0.4.1"
html5ever = "0.27.0"
http = "1.1"
http-body = "1.0"
hyper = "0.14"
ignore = "0.4.22"
image = "0.25.1"
imara-diff = "0.1.8"
indexmap = { version = "2.7.0", features = ["serde"] }
indoc = "2"
inventory = "0.3.19"
itertools = "0.14.0"
json_dotpath = "1.1"
jsonschema = "0.37.0"
jsonwebtoken = "9.3"
jupyter-protocol = "0.10.0"
jupyter-websocket-client = "0.15.0"
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "b71ab4eeb27d9758be8092020a46fe33fbca4e33" }
mach2 = "0.5"
markup5ever_rcdom = "0.3.0"
metal = "0.29"
minidumper = "0.8"
moka = { version = "0.12.10", features = ["sync"] }
naga = { version = "25.0", features = ["wgsl-in"] }
nanoid = "0.4"
nbformat = "0.15.0"
nix = "0.29"
num-format = "0.4.4"
objc = "0.2"
objc2-foundation = { version = "=0.3.1", default-features = false, features = [
"NSArray",
"NSAttributedString",
"NSBundle",
"NSCoder",
"NSData",
"NSDate",
"NSDictionary",
"NSEnumerator",
"NSError",
"NSGeometry",
"NSNotification",
"NSNull",
"NSObjCRuntime",
"NSObject",
"NSProcessInfo",
"NSRange",
"NSRunLoop",
"NSString",
"NSURL",
"NSUndoManager",
"NSValue",
"objc2-core-foundation",
"std"
] }
open = "5.0.0"
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
partial-json-fixer = "0.5.3"
parse_int = "0.9"
pciid-parser = "0.8.0"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
portable-pty = "0.9.0"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
proc-macro2 = "1.0.93"
profiling = "1"
prost = "0.9"
prost-build = "0.9"
prost-types = "0.9"
pulldown-cmark = { version = "0.12.0", default-features = false }
quote = "1.0.9"
rand = "0.9"
rayon = "1.8"
regex = "1.5"
# WARNING: If you change this, you must also publish a new version of zed-reqwest to crates.io
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662463bda39148ba154100dd44d3fba5873a4", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
"multipart",
"rustls-tls-native-roots",
"socks",
"stream",
], package = "zed-reqwest", version = "0.12.15-zed" }
rsa = "0.9.6"
runtimelib = { version = "0.30.0", default-features = false, features = [
"async-dispatcher-runtime", "aws-lc-rs"
] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
# WARNING: If you change this, you must also publish a new version of zed-scap to crates.io
scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
schemars = { version = "1.0", features = ["indexmap2"] }
semver = { version = "1.0", features = ["serde"] }
serde = { version = "1.0.221", features = ["derive", "rc"] }
serde_json = { version = "1.0.144", features = ["preserve_order", "raw_value"] }
serde_json_lenient = { version = "0.2", features = [
"preserve_order",
"raw_value",
] }
serde_path_to_error = "0.1.17"
serde_repr = "0.1"
serde_urlencoded = "0.7"
sha2 = "0.10"
shellexpand = "2.1.0"
shlex = "1.3.0"
simplelog = "0.12.2"
slotmap = "1.0.6"
smallvec = { version = "1.6", features = ["union", "const_new"] }
smol = "2.0"
sqlformat = "0.2"
stacksafe = "0.1"
streaming-iterator = "0.1"
strsim = "0.11"
strum = { version = "0.27.2", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "2.0.101", features = ["full", "extra-traits", "visit-mut"] }
sys-locale = "0.3.1"
sysinfo = "0.37.0"
take-until = "0.2.0"
tempfile = "3.20.0"
thiserror = "2.0.12"
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "2570c4387a8505fb8f1d3f3557454b474f1e8271" }
time = { version = "0.3", features = [
"macros",
"parsing",
"serde",
"serde-well-known",
"formatting",
"local-offset",
] }
tiny_http = "0.8"
tokio = { version = "1" }
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
tokio-socks = { version = "0.5.2", default-features = false, features = ["futures-io", "tokio"] }
toml = "0.8"
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
tower-http = "0.4.4"
tree-sitter = { version = "0.26", features = ["wasm"] }
tree-sitter-bash = "0.25.1"
tree-sitter-c = "0.23"
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" }
tree-sitter-css = "0.23"
tree-sitter-diff = "0.1.0"
tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0"
tree-sitter-gitcommit = { git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9" }
tree-sitter-go = "0.23"
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "2e886870578eeba1927a2dc4bd2e2b3f598c5f9a", package = "tree-sitter-gomod" }
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex", rev = "1dd45142fbb05562e35b2040c6129c9bca346592" }
tree-sitter-html = "0.23"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.24"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = "0.25"
tree-sitter-regex = "0.24"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.24"
tree-sitter-typescript = { git = "https://github.com/zed-industries/tree-sitter-typescript", rev = "e2c53597d6a5d9cf7bbe8dccde576fe1e46c5899" } # https://github.com/tree-sitter/tree-sitter-typescript/pull/347
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
tracing = "0.1.40"
unicase = "2.6"
unicode-script = "0.5.7"
unicode-segmentation = "1.10"
unindent = "0.2.0"
url = "2.2"
urlencoding = "2.1.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
walkdir = "2.5"
wasm-encoder = "0.221"
wasmparser = "0.221"
wasmtime = { version = "33", default-features = false, features = [
"async",
"demangle",
"runtime",
"cranelift",
"component-model",
"incremental-cache",
"parallel-compilation",
] }
wasmtime-wasi = "33"
wax = "0.6"
which = "6.0.0"
windows-core = "0.61"
yawc = "0.2.5"
zeroize = "1.8"
zstd = "0.11"
[workspace.dependencies.windows]
version = "0.61"
features = [
"Foundation_Numerics",
"Storage_Search",
"Storage_Streams",
"System_Threading",
"UI_ViewManagement",
"Wdk_System_SystemServices",
"Win32_Globalization",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_Graphics_Direct3D_Fxc",
"Win32_Graphics_DirectComposition",
"Win32_Graphics_DirectWrite",
"Win32_Graphics_Dwm",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Gdi",
"Win32_Graphics_Imaging",
"Win32_Graphics_Hlsl",
"Win32_Networking_WinSock",
"Win32_Security",
"Win32_Security_Credentials",
"Win32_Security_Cryptography",
"Win32_Storage_FileSystem",
"Win32_System_Com",
"Win32_System_Com_StructuredStorage",
"Win32_System_Console",
"Win32_System_DataExchange",
"Win32_System_IO",
"Win32_System_LibraryLoader",
"Win32_System_Memory",
"Win32_System_Ole",
"Win32_System_Performance",
"Win32_System_Pipes",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",
"Win32_System_Threading",
"Win32_System_Variant",
"Win32_System_WinRT",
"Win32_UI_Controls",
"Win32_UI_HiDpi",
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_Shell_PropertiesSystem",
"Win32_UI_WindowsAndMessaging",
]
[patch.crates-io]
notify = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" }
calloop = { git = "https://github.com/zed-industries/calloop" }
[profile.dev]
split-debuginfo = "unpacked"
# https://github.com/rust-lang/cargo/issues/16104
incremental = false
codegen-units = 16
# mirror configuration for crates compiled for the build platform
# (without this cargo will compile ~400 crates twice)
[profile.dev.build-override]
codegen-units = 16
[profile.dev.package]
# proc-macros start
gpui_macros = { opt-level = 3 }
derive_refineable = { opt-level = 3 }
settings_macros = { opt-level = 3 }
sqlez_macros = { opt-level = 3, codegen-units = 1 }
ui_macros = { opt-level = 3 }
util_macros = { opt-level = 3 }
quote = { opt-level = 3 }
syn = { opt-level = 3 }
proc-macro2 = { opt-level = 3 }
# proc-macros end
taffy = { opt-level = 3 }
resvg = { opt-level = 3 }
wasmtime = { opt-level = 3 }
# Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster
activity_indicator = { codegen-units = 1 }
assets = { codegen-units = 1 }
breadcrumbs = { codegen-units = 1 }
collections = { codegen-units = 1 }
command_palette = { codegen-units = 1 }
command_palette_hooks = { codegen-units = 1 }
feature_flags = { codegen-units = 1 }
file_icons = { codegen-units = 1 }
fsevent = { codegen-units = 1 }
image_viewer = { codegen-units = 1 }
edit_prediction_ui = { codegen-units = 1 }
install_cli = { codegen-units = 1 }
journal = { codegen-units = 1 }
json_schema_store = { codegen-units = 1 }
lmstudio = { codegen-units = 1 }
menu = { codegen-units = 1 }
notifications = { codegen-units = 1 }
ollama = { codegen-units = 1 }
outline = { codegen-units = 1 }
paths = { codegen-units = 1 }
prettier = { codegen-units = 1 }
project_symbols = { codegen-units = 1 }
refineable = { codegen-units = 1 }
release_channel = { codegen-units = 1 }
reqwest_client = { codegen-units = 1 }
session = { codegen-units = 1 }
snippet = { codegen-units = 1 }
snippets_ui = { codegen-units = 1 }
story = { codegen-units = 1 }
supermaven_api = { codegen-units = 1 }
telemetry_events = { codegen-units = 1 }
theme_selector = { codegen-units = 1 }
time_format = { codegen-units = 1 }
ui_input = { codegen-units = 1 }
zed_actions = { codegen-units = 1 }
[profile.release]
debug = "limited"
lto = "thin"
codegen-units = 1
[profile.release.package]
zed = { codegen-units = 16 }
[profile.release-fast]
inherits = "release"
debug = "full"
lto = false
codegen-units = 16
[workspace.lints.rust]
unexpected_cfgs = { level = "allow" }
[workspace.lints.clippy]
dbg_macro = "deny"
todo = "deny"
declare_interior_mutable_const = "deny"
redundant_clone = "deny"
disallowed_methods = "deny"
# We currently do not restrict any style rules
# as it slows down shipping code to Zed.
#
# Running ./script/clippy can take several minutes, and so it's
# common to skip that step and let CI do it. Any unexpected failures
# (which also take minutes to discover) thus require switching back
# to an old branch, manual fixing, and re-pushing.
#
# In the future we could improve this by either making sure
# Zed can surface clippy errors in diagnostics (in addition to the
# rust-analyzer errors), or by having CI fix style nits automatically.
style = { level = "allow", priority = -1 }
# Individual rules that have violations in the codebase:
type_complexity = "allow"
let_underscore_future = "allow"
# Motivation: We use `vec![a..b]` a lot when dealing with ranges in text, so
# warning on this rule produces a lot of noise.
single_range_in_vec_init = "allow"
# in Rust it can be very tedious to reduce argument count without
# 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"
# Boolean expressions can be hard to read, requiring only the minimal form gets in the way
nonminimal_bool = "allow"
[workspace.metadata.cargo-machete]
ignored = [
"bindgen",
"cbindgen",
"prost_build",
"serde",
"component",
"documented",
"sea-orm-macros",
]

View File

@@ -227,6 +227,7 @@
"ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPreviousMatch",
"ctrl-k l": "agent::OpenRulesLibrary",
"ctrl-shift-v": "agent::PasteRaw",
},
},
{
@@ -293,6 +294,7 @@
"shift-ctrl-r": "agent::OpenAgentDiff",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll",
"ctrl-shift-v": "agent::PasteRaw",
},
},
{
@@ -304,6 +306,7 @@
"shift-ctrl-r": "agent::OpenAgentDiff",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll",
"ctrl-shift-v": "agent::PasteRaw",
},
},
{

View File

@@ -267,6 +267,7 @@
"cmd-shift-g": "search::SelectPreviousMatch",
"cmd-k l": "agent::OpenRulesLibrary",
"alt-tab": "agent::CycleFavoriteModels",
"cmd-shift-v": "agent::PasteRaw",
},
},
{
@@ -335,6 +336,7 @@
"shift-ctrl-r": "agent::OpenAgentDiff",
"cmd-shift-y": "agent::KeepAll",
"cmd-shift-n": "agent::RejectAll",
"cmd-shift-v": "agent::PasteRaw",
},
},
{
@@ -347,6 +349,7 @@
"shift-ctrl-r": "agent::OpenAgentDiff",
"cmd-shift-y": "agent::KeepAll",
"cmd-shift-n": "agent::RejectAll",
"cmd-shift-v": "agent::PasteRaw",
},
},
{

View File

@@ -227,6 +227,7 @@
"ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPreviousMatch",
"ctrl-k l": "agent::OpenRulesLibrary",
"ctrl-shift-v": "agent::PasteRaw",
},
},
{
@@ -296,6 +297,7 @@
"ctrl-shift-r": "agent::OpenAgentDiff",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll",
"ctrl-shift-v": "agent::PasteRaw",
},
},
{
@@ -308,6 +310,7 @@
"ctrl-shift-r": "agent::OpenAgentDiff",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll",
"ctrl-shift-v": "agent::PasteRaw",
},
},
{

View File

@@ -34,7 +34,7 @@ use theme::ThemeSettings;
use ui::prelude::*;
use util::{ResultExt, debug_panic};
use workspace::{CollaboratorId, Workspace};
use zed_actions::agent::Chat;
use zed_actions::agent::{Chat, PasteRaw};
pub struct MessageEditor {
mention_set: Entity<MentionSet>,
@@ -543,6 +543,9 @@ impl MessageEditor {
}
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
let editor_clipboard_selections = cx
.read_from_clipboard()
.and_then(|item| item.entries().first().cloned())
@@ -553,133 +556,127 @@ impl MessageEditor {
_ => None,
});
let has_file_context = editor_clipboard_selections
.as_ref()
.is_some_and(|selections| {
selections
.iter()
.any(|sel| sel.file_path.is_some() && sel.line_range.is_some())
});
if has_file_context {
if let Some((workspace, selections)) =
self.workspace.upgrade().zip(editor_clipboard_selections)
{
let Some(first_selection) = selections.first() else {
return;
};
if let Some(file_path) = &first_selection.file_path {
// In case someone pastes selections from another window
// with a different project, we don't want to insert the
// crease (containing the absolute path) since the agent
// cannot access files outside the project.
let is_in_project = workspace
.read(cx)
.project()
.read(cx)
.project_path_for_absolute_path(file_path, cx)
.is_some();
if !is_in_project {
return;
}
}
cx.stop_propagation();
let insertion_target = self
.editor
.read(cx)
.selections
.newest_anchor()
.start
.text_anchor;
let project = workspace.read(cx).project().clone();
for selection in selections {
if let (Some(file_path), Some(line_range)) =
(selection.file_path, selection.line_range)
{
let crease_text =
acp_thread::selection_name(Some(file_path.as_ref()), &line_range);
let mention_uri = MentionUri::Selection {
abs_path: Some(file_path.clone()),
line_range: line_range.clone(),
};
let mention_text = mention_uri.as_link().to_string();
let (excerpt_id, text_anchor, content_len) =
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx);
let snapshot = buffer.snapshot(cx);
let (excerpt_id, _, buffer_snapshot) =
snapshot.as_singleton().unwrap();
let text_anchor = insertion_target.bias_left(&buffer_snapshot);
editor.insert(&mention_text, window, cx);
editor.insert(" ", window, cx);
(*excerpt_id, text_anchor, mention_text.len())
});
let Some((crease_id, tx)) = insert_crease_for_mention(
excerpt_id,
text_anchor,
content_len,
crease_text.into(),
mention_uri.icon_path(cx),
None,
self.editor.clone(),
window,
cx,
) else {
continue;
};
drop(tx);
let mention_task = cx
.spawn({
let project = project.clone();
async move |_, cx| {
let project_path = project
.update(cx, |project, cx| {
project.project_path_for_absolute_path(&file_path, cx)
})
.map_err(|e| e.to_string())?
.ok_or_else(|| "project path not found".to_string())?;
let buffer = project
.update(cx, |project, cx| {
project.open_buffer(project_path, cx)
})
.map_err(|e| e.to_string())?
.await
.map_err(|e| e.to_string())?;
buffer
.update(cx, |buffer, cx| {
let start = Point::new(*line_range.start(), 0)
.min(buffer.max_point());
let end = Point::new(*line_range.end() + 1, 0)
.min(buffer.max_point());
let content =
buffer.text_for_range(start..end).collect();
Mention::Text {
content,
tracked_buffers: vec![cx.entity()],
}
})
.map_err(|e| e.to_string())
}
})
.shared();
self.mention_set.update(cx, |mention_set, _cx| {
mention_set.insert_mention(crease_id, mention_uri.clone(), mention_task)
});
}
}
return;
// Insert creases for pasted clipboard selections that:
// 1. Contain exactly one selection
// 2. Have an associated file path
// 3. Span multiple lines (not single-line selections)
// 4. Belong to a file that exists in the current project
let should_insert_creases = util::maybe!({
let selections = editor_clipboard_selections.as_ref()?;
if selections.len() > 1 {
return Some(false);
}
let selection = selections.first()?;
let file_path = selection.file_path.as_ref()?;
let line_range = selection.line_range.as_ref()?;
if line_range.start() == line_range.end() {
return Some(false);
}
Some(
workspace
.read(cx)
.project()
.read(cx)
.project_path_for_absolute_path(file_path, cx)
.is_some(),
)
})
.unwrap_or(false);
if should_insert_creases && let Some(selections) = editor_clipboard_selections {
cx.stop_propagation();
let insertion_target = self
.editor
.read(cx)
.selections
.newest_anchor()
.start
.text_anchor;
let project = workspace.read(cx).project().clone();
for selection in selections {
if let (Some(file_path), Some(line_range)) =
(selection.file_path, selection.line_range)
{
let crease_text =
acp_thread::selection_name(Some(file_path.as_ref()), &line_range);
let mention_uri = MentionUri::Selection {
abs_path: Some(file_path.clone()),
line_range: line_range.clone(),
};
let mention_text = mention_uri.as_link().to_string();
let (excerpt_id, text_anchor, content_len) =
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx);
let snapshot = buffer.snapshot(cx);
let (excerpt_id, _, buffer_snapshot) = snapshot.as_singleton().unwrap();
let text_anchor = insertion_target.bias_left(&buffer_snapshot);
editor.insert(&mention_text, window, cx);
editor.insert(" ", window, cx);
(*excerpt_id, text_anchor, mention_text.len())
});
let Some((crease_id, tx)) = insert_crease_for_mention(
excerpt_id,
text_anchor,
content_len,
crease_text.into(),
mention_uri.icon_path(cx),
None,
self.editor.clone(),
window,
cx,
) else {
continue;
};
drop(tx);
let mention_task = cx
.spawn({
let project = project.clone();
async move |_, cx| {
let project_path = project
.update(cx, |project, cx| {
project.project_path_for_absolute_path(&file_path, cx)
})
.map_err(|e| e.to_string())?
.ok_or_else(|| "project path not found".to_string())?;
let buffer = project
.update(cx, |project, cx| project.open_buffer(project_path, cx))
.map_err(|e| e.to_string())?
.await
.map_err(|e| e.to_string())?;
buffer
.update(cx, |buffer, cx| {
let start = Point::new(*line_range.start(), 0)
.min(buffer.max_point());
let end = Point::new(*line_range.end() + 1, 0)
.min(buffer.max_point());
let content = buffer.text_for_range(start..end).collect();
Mention::Text {
content,
tracked_buffers: vec![cx.entity()],
}
})
.map_err(|e| e.to_string())
}
})
.shared();
self.mention_set.update(cx, |mention_set, _cx| {
mention_set.insert_mention(crease_id, mention_uri.clone(), mention_task)
});
}
}
return;
}
if self.prompt_capabilities.borrow().image
@@ -690,6 +687,13 @@ impl MessageEditor {
}
}
fn paste_raw(&mut self, _: &PasteRaw, window: &mut Window, cx: &mut Context<Self>) {
let editor = self.editor.clone();
window.defer(cx, move |window, cx| {
editor.update(cx, |editor, cx| editor.paste(&Paste, window, cx));
});
}
pub fn insert_dragged_files(
&mut self,
paths: Vec<project::ProjectPath>,
@@ -967,6 +971,7 @@ impl Render for MessageEditor {
.on_action(cx.listener(Self::chat))
.on_action(cx.listener(Self::chat_with_follow))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::paste_raw))
.capture_action(cx.listener(Self::paste))
.flex_1()
.child({

View File

@@ -71,7 +71,7 @@ use workspace::{
pane,
searchable::{SearchEvent, SearchableItem},
};
use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
use zed_actions::agent::{AddSelectionToThread, PasteRaw, ToggleModelSelector};
use crate::CycleFavoriteModels;
@@ -1698,6 +1698,9 @@ impl TextThreadEditor {
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
let editor_clipboard_selections = cx
.read_from_clipboard()
.and_then(|item| item.entries().first().cloned())
@@ -1708,84 +1711,101 @@ impl TextThreadEditor {
_ => None,
});
let has_file_context = editor_clipboard_selections
.as_ref()
.is_some_and(|selections| {
selections
.iter()
.any(|sel| sel.file_path.is_some() && sel.line_range.is_some())
});
// Insert creases for pasted clipboard selections that:
// 1. Contain exactly one selection
// 2. Have an associated file path
// 3. Span multiple lines (not single-line selections)
// 4. Belong to a file that exists in the current project
let should_insert_creases = util::maybe!({
let selections = editor_clipboard_selections.as_ref()?;
if selections.len() > 1 {
return Some(false);
}
let selection = selections.first()?;
let file_path = selection.file_path.as_ref()?;
let line_range = selection.line_range.as_ref()?;
if has_file_context {
if let Some(clipboard_item) = cx.read_from_clipboard() {
if let Some(ClipboardEntry::String(clipboard_text)) =
clipboard_item.entries().first()
{
if let Some(selections) = editor_clipboard_selections {
cx.stop_propagation();
if line_range.start() == line_range.end() {
return Some(false);
}
let text = clipboard_text.text();
self.editor.update(cx, |editor, cx| {
let mut current_offset = 0;
let weak_editor = cx.entity().downgrade();
Some(
workspace
.read(cx)
.project()
.read(cx)
.project_path_for_absolute_path(file_path, cx)
.is_some(),
)
})
.unwrap_or(false);
for selection in selections {
if let (Some(file_path), Some(line_range)) =
(selection.file_path, selection.line_range)
{
let selected_text =
&text[current_offset..current_offset + selection.len];
let fence = assistant_slash_commands::codeblock_fence_for_path(
file_path.to_str(),
Some(line_range.clone()),
);
let formatted_text = format!("{fence}{selected_text}\n```");
if should_insert_creases && let Some(clipboard_item) = cx.read_from_clipboard() {
if let Some(ClipboardEntry::String(clipboard_text)) = clipboard_item.entries().first() {
if let Some(selections) = editor_clipboard_selections {
cx.stop_propagation();
let insert_point = editor
.selections
.newest::<Point>(&editor.display_snapshot(cx))
.head();
let start_row = MultiBufferRow(insert_point.row);
let text = clipboard_text.text();
self.editor.update(cx, |editor, cx| {
let mut current_offset = 0;
let weak_editor = cx.entity().downgrade();
editor.insert(&formatted_text, window, cx);
for selection in selections {
if let (Some(file_path), Some(line_range)) =
(selection.file_path, selection.line_range)
{
let selected_text =
&text[current_offset..current_offset + selection.len];
let fence = assistant_slash_commands::codeblock_fence_for_path(
file_path.to_str(),
Some(line_range.clone()),
);
let formatted_text = format!("{fence}{selected_text}\n```");
let snapshot = editor.buffer().read(cx).snapshot(cx);
let anchor_before = snapshot.anchor_after(insert_point);
let anchor_after = editor
.selections
.newest_anchor()
.head()
.bias_left(&snapshot);
let insert_point = editor
.selections
.newest::<Point>(&editor.display_snapshot(cx))
.head();
let start_row = MultiBufferRow(insert_point.row);
editor.insert("\n", window, cx);
editor.insert(&formatted_text, window, cx);
let crease_text = acp_thread::selection_name(
Some(file_path.as_ref()),
&line_range,
);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let anchor_before = snapshot.anchor_after(insert_point);
let anchor_after = editor
.selections
.newest_anchor()
.head()
.bias_left(&snapshot);
let fold_placeholder = quote_selection_fold_placeholder(
crease_text,
weak_editor.clone(),
);
let crease = Crease::inline(
anchor_before..anchor_after,
fold_placeholder,
render_quote_selection_output_toggle,
|_, _, _, _| Empty.into_any(),
);
editor.insert_creases(vec![crease], cx);
editor.fold_at(start_row, window, cx);
editor.insert("\n", window, cx);
current_offset += selection.len;
if !selection.is_entire_line && current_offset < text.len() {
current_offset += 1;
}
let crease_text = acp_thread::selection_name(
Some(file_path.as_ref()),
&line_range,
);
let fold_placeholder = quote_selection_fold_placeholder(
crease_text,
weak_editor.clone(),
);
let crease = Crease::inline(
anchor_before..anchor_after,
fold_placeholder,
render_quote_selection_output_toggle,
|_, _, _, _| Empty.into_any(),
);
editor.insert_creases(vec![crease], cx);
editor.fold_at(start_row, window, cx);
current_offset += selection.len;
if !selection.is_entire_line && current_offset < text.len() {
current_offset += 1;
}
}
});
return;
}
}
});
return;
}
}
}
@@ -1944,6 +1964,12 @@ impl TextThreadEditor {
}
}
fn paste_raw(&mut self, _: &PasteRaw, window: &mut Window, cx: &mut Context<Self>) {
self.editor.update(cx, |editor, cx| {
editor.paste(&editor::actions::Paste, window, cx);
});
}
fn update_image_blocks(&mut self, cx: &mut Context<Self>) {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
@@ -2627,6 +2653,7 @@ impl Render for TextThreadEditor {
.capture_action(cx.listener(TextThreadEditor::copy))
.capture_action(cx.listener(TextThreadEditor::cut))
.capture_action(cx.listener(TextThreadEditor::paste))
.on_action(cx.listener(TextThreadEditor::paste_raw))
.capture_action(cx.listener(TextThreadEditor::cycle_message_role))
.capture_action(cx.listener(TextThreadEditor::confirm_command))
.on_action(cx.listener(TextThreadEditor::assist))

View File

@@ -20880,6 +20880,36 @@ async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
.to_string(),
);
cx.update_editor(|editor, window, cx| {
editor.move_up(&MoveUp, window, cx);
editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
});
cx.assert_state_with_diff(
indoc! { "
ˇone
- two
three
five
"}
.to_string(),
);
cx.update_editor(|editor, window, cx| {
editor.move_down(&MoveDown, window, cx);
editor.move_down(&MoveDown, window, cx);
editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
});
cx.assert_state_with_diff(
indoc! { "
one
- two
ˇthree
- four
five
"}
.to_string(),
);
cx.set_state(indoc! { "
one
ˇTWO
@@ -20919,6 +20949,66 @@ async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
);
}
#[gpui::test]
async fn test_toggling_adjacent_diff_hunks_2(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let diff_base = r#"
lineA
lineB
lineC
lineD
"#
.unindent();
cx.set_state(
&r#"
ˇlineA1
lineB
lineD
"#
.unindent(),
);
cx.set_head_text(&diff_base);
executor.run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(
r#"
- lineA
+ ˇlineA1
lineB
lineD
"#
.unindent(),
);
cx.update_editor(|editor, window, cx| {
editor.move_down(&MoveDown, window, cx);
editor.move_right(&MoveRight, window, cx);
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(
r#"
- lineA
+ lineA1
lˇineB
- lineC
lineD
"#
.unindent(),
);
}
#[gpui::test]
async fn test_edits_around_expanded_deletion_hunks(
executor: BackgroundExecutor,

View File

@@ -198,14 +198,14 @@ wayland-backend = { version = "0.3.3", features = [
"client_system",
"dlopen",
], optional = true }
wayland-client = { version = "0.31.2", optional = true }
wayland-cursor = { version = "0.31.1", optional = true }
wayland-protocols = { version = "0.31.2", features = [
wayland-client = { version = "0.31.11", optional = true }
wayland-cursor = { version = "0.31.11", optional = true }
wayland-protocols = { version = "0.32.9", features = [
"client",
"staging",
"unstable",
], optional = true }
wayland-protocols-plasma = { version = "0.2.0", features = [
wayland-protocols-plasma = { version = "0.3.9", features = [
"client",
], optional = true }
wayland-protocols-wlr = { version = "0.3.9", features = [

View File

@@ -5,6 +5,7 @@ use gpui::{
struct SubWindow {
custom_titlebar: bool,
is_dialog: bool,
}
fn button(text: &str, on_click: impl Fn(&mut Window, &mut App) + 'static) -> impl IntoElement {
@@ -23,7 +24,10 @@ fn button(text: &str, on_click: impl Fn(&mut Window, &mut App) + 'static) -> imp
}
impl Render for SubWindow {
fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let window_bounds =
WindowBounds::Windowed(Bounds::centered(None, size(px(250.0), px(200.0)), cx));
div()
.flex()
.flex_col()
@@ -52,8 +56,28 @@ impl Render for SubWindow {
.child(
div()
.p_8()
.flex()
.flex_col()
.gap_2()
.child("SubWindow")
.when(self.is_dialog, |div| {
div.child(button("Open Nested Dialog", move |_, cx| {
cx.open_window(
WindowOptions {
window_bounds: Some(window_bounds),
kind: WindowKind::Dialog,
..Default::default()
},
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: true,
})
},
)
.unwrap();
}))
})
.child(button("Close", |window, _| {
window.remove_window();
})),
@@ -86,6 +110,7 @@ impl Render for WindowDemo {
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: false,
})
},
)
@@ -101,6 +126,39 @@ impl Render for WindowDemo {
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: false,
})
},
)
.unwrap();
}))
.child(button("Floating", move |_, cx| {
cx.open_window(
WindowOptions {
window_bounds: Some(window_bounds),
kind: WindowKind::Floating,
..Default::default()
},
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: false,
})
},
)
.unwrap();
}))
.child(button("Dialog", move |_, cx| {
cx.open_window(
WindowOptions {
window_bounds: Some(window_bounds),
kind: WindowKind::Dialog,
..Default::default()
},
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: true,
})
},
)
@@ -116,6 +174,7 @@ impl Render for WindowDemo {
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: true,
is_dialog: false,
})
},
)
@@ -131,6 +190,7 @@ impl Render for WindowDemo {
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: false,
})
},
)
@@ -147,6 +207,7 @@ impl Render for WindowDemo {
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: false,
})
},
)
@@ -162,6 +223,7 @@ impl Render for WindowDemo {
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: false,
})
},
)
@@ -177,6 +239,7 @@ impl Render for WindowDemo {
|_, cx| {
cx.new(|_| SubWindow {
custom_titlebar: false,
is_dialog: false,
})
},
)

View File

@@ -0,0 +1,150 @@
/* automatically generated by rust-bindgen 0.71.1 */
pub const DISPATCH_TIME_NOW: u32 = 0;
pub const DISPATCH_QUEUE_PRIORITY_HIGH: u32 = 2;
pub const DISPATCH_QUEUE_PRIORITY_DEFAULT: u32 = 0;
pub const DISPATCH_QUEUE_PRIORITY_LOW: i32 = -2;
pub type dispatch_function_t =
::std::option::Option<unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void)>;
pub type dispatch_time_t = u64;
unsafe extern "C" {
pub fn dispatch_time(when: dispatch_time_t, delta: i64) -> dispatch_time_t;
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union dispatch_object_t {
pub _os_obj: *mut _os_object_s,
pub _do: *mut dispatch_object_s,
pub _dq: *mut dispatch_queue_s,
pub _dqa: *mut dispatch_queue_attr_s,
pub _dg: *mut dispatch_group_s,
pub _ds: *mut dispatch_source_s,
pub _dch: *mut dispatch_channel_s,
pub _dm: *mut dispatch_mach_s,
pub _dmsg: *mut dispatch_mach_msg_s,
pub _dsema: *mut dispatch_semaphore_s,
pub _ddata: *mut dispatch_data_s,
pub _dchannel: *mut dispatch_io_s,
}
unsafe extern "C" {
pub fn dispatch_set_context(object: dispatch_object_t, context: *mut ::std::os::raw::c_void);
}
unsafe extern "C" {
pub fn dispatch_suspend(object: dispatch_object_t);
}
unsafe extern "C" {
pub fn dispatch_resume(object: dispatch_object_t);
}
pub type dispatch_queue_t = *mut dispatch_queue_s;
pub type dispatch_queue_global_t = dispatch_queue_t;
unsafe extern "C" {
pub fn dispatch_async_f(
queue: dispatch_queue_t,
context: *mut ::std::os::raw::c_void,
work: dispatch_function_t,
);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_queue_s {
pub _address: u8,
}
unsafe extern "C" {
pub static mut _dispatch_main_q: dispatch_queue_s;
}
unsafe extern "C" {
pub fn dispatch_get_global_queue(identifier: isize, flags: usize) -> dispatch_queue_global_t;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_queue_attr_s {
pub _address: u8,
}
unsafe extern "C" {
pub fn dispatch_after_f(
when: dispatch_time_t,
queue: dispatch_queue_t,
context: *mut ::std::os::raw::c_void,
work: dispatch_function_t,
);
}
pub type dispatch_source_t = *mut dispatch_source_s;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_source_type_s {
_unused: [u8; 0],
}
pub type dispatch_source_type_t = *const dispatch_source_type_s;
unsafe extern "C" {
pub static _dispatch_source_type_data_add: dispatch_source_type_s;
}
unsafe extern "C" {
pub fn dispatch_source_create(
type_: dispatch_source_type_t,
handle: usize,
mask: usize,
queue: dispatch_queue_t,
) -> dispatch_source_t;
}
unsafe extern "C" {
pub fn dispatch_source_set_event_handler_f(
source: dispatch_source_t,
handler: dispatch_function_t,
);
}
unsafe extern "C" {
pub fn dispatch_source_cancel(source: dispatch_source_t);
}
unsafe extern "C" {
pub fn dispatch_source_merge_data(source: dispatch_source_t, value: usize);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_data_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct _os_object_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_object_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_group_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_source_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_channel_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_mach_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_mach_msg_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_semaphore_s {
pub _address: u8,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct dispatch_io_s {
pub _address: u8,
}

Binary file not shown.

View File

@@ -28,6 +28,8 @@ pub use entity_map::*;
use http_client::{HttpClient, Url};
use smallvec::SmallVec;
#[cfg(any(test, feature = "test-support"))]
pub use test_app::*;
#[cfg(any(test, feature = "test-support"))]
pub use test_context::*;
use util::{ResultExt, debug_panic};
@@ -51,6 +53,8 @@ mod async_context;
mod context;
mod entity_map;
#[cfg(any(test, feature = "test-support"))]
mod test_app;
#[cfg(any(test, feature = "test-support"))]
mod test_context;
/// The duration for which futures returned from [Context::on_app_quit] can run before the application fully quits.

View File

@@ -0,0 +1,605 @@
//! A clean testing API for GPUI applications.
//!
//! `TestApp` provides a simpler alternative to `TestAppContext` with:
//! - Automatic effect flushing after updates
//! - Clean window creation and inspection
//! - Input simulation helpers
//!
//! # Example
//! ```ignore
//! #[test]
//! fn test_my_view() {
//! let mut app = TestApp::new();
//!
//! let mut window = app.open_window(|window, cx| {
//! MyView::new(window, cx)
//! });
//!
//! window.update(|view, window, cx| {
//! view.do_something(cx);
//! });
//!
//! // Check rendered state
//! assert_eq!(window.title(), Some("Expected Title"));
//! }
//! ```
use crate::{
AnyWindowHandle, App, AppCell, AppContext, AsyncApp, BackgroundExecutor, BorrowAppContext,
Bounds, ClipboardItem, Context, Entity, ForegroundExecutor, Global, InputEvent, Keystroke,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Platform, Point, Render,
SceneSnapshot, Size, Task, TestDispatcher, TestPlatform, TextSystem, Window, WindowBounds,
WindowHandle, WindowOptions, app::GpuiMode,
};
use rand::{SeedableRng, rngs::StdRng};
use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
/// A test application context with a clean API.
///
/// Unlike `TestAppContext`, `TestApp` automatically flushes effects after
/// each update and provides simpler window management.
pub struct TestApp {
app: Rc<AppCell>,
platform: Rc<TestPlatform>,
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
#[allow(dead_code)]
dispatcher: TestDispatcher,
text_system: Arc<TextSystem>,
}
impl TestApp {
/// Create a new test application.
pub fn new() -> Self {
Self::with_seed(0)
}
/// Create a new test application with a specific random seed.
pub fn with_seed(seed: u64) -> Self {
let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed));
let arc_dispatcher = Arc::new(dispatcher.clone());
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
let asset_source = Arc::new(());
let http_client = http_client::FakeHttpClient::with_404_response();
let text_system = Arc::new(TextSystem::new(platform.text_system()));
let mut app = App::new_app(platform.clone(), asset_source, http_client);
app.borrow_mut().mode = GpuiMode::test();
Self {
app,
platform,
background_executor,
foreground_executor,
dispatcher,
text_system,
}
}
/// Run a closure with mutable access to the App context.
/// Automatically runs until parked after the closure completes.
pub fn update<R>(&mut self, f: impl FnOnce(&mut App) -> R) -> R {
let result = {
let mut app = self.app.borrow_mut();
app.update(f)
};
self.run_until_parked();
result
}
/// Run a closure with read-only access to the App context.
pub fn read<R>(&self, f: impl FnOnce(&App) -> R) -> R {
let app = self.app.borrow();
f(&app)
}
/// Create a new entity in the app.
pub fn new_entity<T: 'static>(
&mut self,
build: impl FnOnce(&mut Context<T>) -> T,
) -> Entity<T> {
self.update(|cx| cx.new(build))
}
/// Update an entity.
pub fn update_entity<T: 'static, R>(
&mut self,
entity: &Entity<T>,
f: impl FnOnce(&mut T, &mut Context<T>) -> R,
) -> R {
self.update(|cx| entity.update(cx, f))
}
/// Read an entity.
pub fn read_entity<T: 'static, R>(
&self,
entity: &Entity<T>,
f: impl FnOnce(&T, &App) -> R,
) -> R {
self.read(|cx| f(entity.read(cx), cx))
}
/// Open a test window with the given root view.
pub fn open_window<V: Render + 'static>(
&mut self,
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
) -> TestWindow<V> {
let bounds = self.read(|cx| Bounds::maximized(None, cx));
let handle = self.update(|cx| {
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|window, cx| cx.new(|cx| build_view(window, cx)),
)
.unwrap()
});
TestWindow {
handle,
app: self.app.clone(),
platform: self.platform.clone(),
background_executor: self.background_executor.clone(),
}
}
/// Open a test window with specific options.
pub fn open_window_with_options<V: Render + 'static>(
&mut self,
options: WindowOptions,
build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
) -> TestWindow<V> {
let handle = self.update(|cx| {
cx.open_window(options, |window, cx| cx.new(|cx| build_view(window, cx)))
.unwrap()
});
TestWindow {
handle,
app: self.app.clone(),
platform: self.platform.clone(),
background_executor: self.background_executor.clone(),
}
}
/// Run pending tasks until there's nothing left to do.
pub fn run_until_parked(&self) {
self.background_executor.run_until_parked();
}
/// Advance the simulated clock by the given duration.
pub fn advance_clock(&self, duration: Duration) {
self.background_executor.advance_clock(duration);
}
/// Spawn a future on the foreground executor.
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncApp) -> Fut) -> Task<R>
where
Fut: Future<Output = R> + 'static,
R: 'static,
{
self.foreground_executor.spawn(f(self.to_async()))
}
/// Spawn a future on the background executor.
pub fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
where
R: Send + 'static,
{
self.background_executor.spawn(future)
}
/// Get an async handle to the app.
pub fn to_async(&self) -> AsyncApp {
AsyncApp {
app: Rc::downgrade(&self.app),
background_executor: self.background_executor.clone(),
foreground_executor: self.foreground_executor.clone(),
}
}
/// Get the background executor.
pub fn background_executor(&self) -> &BackgroundExecutor {
&self.background_executor
}
/// Get the foreground executor.
pub fn foreground_executor(&self) -> &ForegroundExecutor {
&self.foreground_executor
}
/// Get the text system.
pub fn text_system(&self) -> &Arc<TextSystem> {
&self.text_system
}
/// Check if a global of the given type exists.
pub fn has_global<G: Global>(&self) -> bool {
self.read(|cx| cx.has_global::<G>())
}
/// Set a global value.
pub fn set_global<G: Global>(&mut self, global: G) {
self.update(|cx| cx.set_global(global));
}
/// Read a global value.
pub fn read_global<G: Global, R>(&self, f: impl FnOnce(&G, &App) -> R) -> R {
self.read(|cx| f(cx.global(), cx))
}
/// Update a global value.
pub fn update_global<G: Global, R>(&mut self, f: impl FnOnce(&mut G, &mut App) -> R) -> R {
self.update(|cx| cx.update_global(f))
}
// Platform simulation methods
/// Write text to the simulated clipboard.
pub fn write_to_clipboard(&self, item: ClipboardItem) {
self.platform.write_to_clipboard(item);
}
/// Read from the simulated clipboard.
pub fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.platform.read_from_clipboard()
}
/// Get URLs that have been opened via `cx.open_url()`.
pub fn opened_url(&self) -> Option<String> {
self.platform.opened_url.borrow().clone()
}
/// Check if a file path prompt is pending.
pub fn did_prompt_for_new_path(&self) -> bool {
self.platform.did_prompt_for_new_path()
}
/// Simulate answering a path selection dialog.
pub fn simulate_new_path_selection(
&self,
select: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
) {
self.platform.simulate_new_path_selection(select);
}
/// Check if a prompt dialog is pending.
pub fn has_pending_prompt(&self) -> bool {
self.platform.has_pending_prompt()
}
/// Simulate answering a prompt dialog.
pub fn simulate_prompt_answer(&self, button: &str) {
self.platform.simulate_prompt_answer(button);
}
/// Get all open windows.
pub fn windows(&self) -> Vec<AnyWindowHandle> {
self.read(|cx| cx.windows())
}
}
impl Default for TestApp {
fn default() -> Self {
Self::new()
}
}
/// A test window with inspection and simulation capabilities.
pub struct TestWindow<V> {
handle: WindowHandle<V>,
app: Rc<AppCell>,
platform: Rc<TestPlatform>,
background_executor: BackgroundExecutor,
}
impl<V: 'static + Render> TestWindow<V> {
/// Get the window handle.
pub fn handle(&self) -> WindowHandle<V> {
self.handle
}
/// Get the root view entity.
pub fn root(&self) -> Entity<V> {
let mut app = self.app.borrow_mut();
let any_handle: AnyWindowHandle = self.handle.into();
app.update_window(any_handle, |root_view, _, _| {
root_view.downcast::<V>().expect("root view type mismatch")
})
.expect("window not found")
}
/// Update the root view.
/// Automatically draws the window after the update to ensure the scene is current.
pub fn update<R>(&mut self, f: impl FnOnce(&mut V, &mut Window, &mut Context<V>) -> R) -> R {
let result = {
let mut app = self.app.borrow_mut();
let any_handle: AnyWindowHandle = self.handle.into();
app.update_window(any_handle, |root_view, window, cx| {
let view = root_view.downcast::<V>().expect("root view type mismatch");
view.update(cx, |view, cx| f(view, window, cx))
})
.expect("window not found")
};
self.background_executor.run_until_parked();
self.draw();
result
}
/// Read the root view.
pub fn read<R>(&self, f: impl FnOnce(&V, &App) -> R) -> R {
let app = self.app.borrow();
let view = self
.app
.borrow()
.windows
.get(self.handle.window_id())
.and_then(|w| w.as_ref())
.and_then(|w| w.root.clone())
.and_then(|r| r.downcast::<V>().ok())
.expect("window or root view not found");
f(view.read(&app), &app)
}
/// Get the window title.
pub fn title(&self) -> Option<String> {
let app = self.app.borrow();
app.read_window(&self.handle, |_, _cx| {
// TODO: expose title through Window API
None
})
.unwrap()
}
/// Simulate a keystroke.
/// Automatically draws the window after the keystroke.
pub fn simulate_keystroke(&mut self, keystroke: &str) {
let keystroke = Keystroke::parse(keystroke).unwrap();
{
let mut app = self.app.borrow_mut();
let any_handle: AnyWindowHandle = self.handle.into();
app.update_window(any_handle, |_, window, cx| {
window.dispatch_keystroke(keystroke, cx);
})
.unwrap();
}
self.background_executor.run_until_parked();
self.draw();
}
/// Simulate multiple keystrokes (space-separated).
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
for keystroke in keystrokes.split(' ') {
self.simulate_keystroke(keystroke);
}
}
/// Simulate typing text.
pub fn simulate_input(&mut self, input: &str) {
for char in input.chars() {
self.simulate_keystroke(&char.to_string());
}
}
/// Simulate a mouse move.
pub fn simulate_mouse_move(&mut self, position: Point<Pixels>) {
self.simulate_event(MouseMoveEvent {
position,
modifiers: Default::default(),
pressed_button: None,
});
}
/// Simulate a mouse down event.
pub fn simulate_mouse_down(&mut self, position: Point<Pixels>, button: MouseButton) {
self.simulate_event(MouseDownEvent {
position,
button,
modifiers: Default::default(),
click_count: 1,
first_mouse: false,
});
}
/// Simulate a mouse up event.
pub fn simulate_mouse_up(&mut self, position: Point<Pixels>, button: MouseButton) {
self.simulate_event(MouseUpEvent {
position,
button,
modifiers: Default::default(),
click_count: 1,
});
}
/// Simulate a click at the given position.
pub fn simulate_click(&mut self, position: Point<Pixels>, button: MouseButton) {
self.simulate_mouse_down(position, button);
self.simulate_mouse_up(position, button);
}
/// Simulate a scroll event.
pub fn simulate_scroll(&mut self, position: Point<Pixels>, delta: Point<Pixels>) {
self.simulate_event(crate::ScrollWheelEvent {
position,
delta: crate::ScrollDelta::Pixels(delta),
modifiers: Default::default(),
touch_phase: crate::TouchPhase::Moved,
});
}
/// Simulate an input event.
/// Automatically draws the window after the event.
pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
let platform_input = event.to_platform_input();
{
let mut app = self.app.borrow_mut();
let any_handle: AnyWindowHandle = self.handle.into();
app.update_window(any_handle, |_, window, cx| {
window.dispatch_event(platform_input, cx);
})
.unwrap();
}
self.background_executor.run_until_parked();
self.draw();
}
/// Simulate resizing the window.
/// Automatically draws the window after the resize.
pub fn simulate_resize(&mut self, size: Size<Pixels>) {
let window_id = self.handle.window_id();
let mut app = self.app.borrow_mut();
if let Some(Some(window)) = app.windows.get_mut(window_id) {
if let Some(test_window) = window.platform_window.as_test() {
test_window.simulate_resize(size);
}
}
drop(app);
self.background_executor.run_until_parked();
self.draw();
}
/// Force a redraw of the window.
pub fn draw(&mut self) {
let mut app = self.app.borrow_mut();
let any_handle: AnyWindowHandle = self.handle.into();
app.update_window(any_handle, |_, window, cx| {
window.draw(cx).clear();
})
.unwrap();
}
/// Get a snapshot of the rendered scene for inspection.
/// The scene is automatically kept up to date after `update()` and `simulate_*()` calls.
pub fn scene_snapshot(&self) -> SceneSnapshot {
let app = self.app.borrow();
let window = app
.windows
.get(self.handle.window_id())
.and_then(|w| w.as_ref())
.expect("window not found");
window.rendered_frame.scene.snapshot()
}
/// Get the named diagnostic quads recorded during imperative paint, without inspecting the
/// rest of the scene snapshot.
///
/// This is useful for tests that want a stable, semantic view of layout/paint geometry without
/// coupling to the low-level quad/glyph output.
pub fn diagnostic_quads(&self) -> Vec<crate::scene::test_scene::DiagnosticQuad> {
self.scene_snapshot().diagnostic_quads
}
}
impl<V> Clone for TestWindow<V> {
fn clone(&self) -> Self {
Self {
handle: self.handle,
app: self.app.clone(),
platform: self.platform.clone(),
background_executor: self.background_executor.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FocusHandle, Focusable, div, prelude::*};
struct Counter {
count: usize,
focus_handle: FocusHandle,
}
impl Counter {
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
Self {
count: 0,
focus_handle,
}
}
fn increment(&mut self, _cx: &mut Context<Self>) {
self.count += 1;
}
}
impl Focusable for Counter {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for Counter {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().child(format!("Count: {}", self.count))
}
}
#[test]
fn test_basic_usage() {
let mut app = TestApp::new();
let mut window = app.open_window(Counter::new);
window.update(|counter, _window, cx| {
counter.increment(cx);
});
window.read(|counter, _| {
assert_eq!(counter.count, 1);
});
}
#[test]
fn test_entity_creation() {
let mut app = TestApp::new();
let entity = app.new_entity(|cx| Counter {
count: 42,
focus_handle: cx.focus_handle(),
});
app.read_entity(&entity, |counter, _| {
assert_eq!(counter.count, 42);
});
app.update_entity(&entity, |counter, _cx| {
counter.count += 1;
});
app.read_entity(&entity, |counter, _| {
assert_eq!(counter.count, 43);
});
}
#[test]
fn test_globals() {
let mut app = TestApp::new();
struct MyGlobal(String);
impl Global for MyGlobal {}
assert!(!app.has_global::<MyGlobal>());
app.set_global(MyGlobal("hello".into()));
assert!(app.has_global::<MyGlobal>());
app.read_global::<MyGlobal, _>(|global, _| {
assert_eq!(global.0, "hello");
});
app.update_global::<MyGlobal, _>(|global, _| {
global.0 = "world".into();
});
app.read_global::<MyGlobal, _>(|global, _| {
assert_eq!(global.0, "world");
});
}
}

View File

@@ -3,9 +3,9 @@ use crate::{
BackgroundExecutor, BorrowAppContext, Bounds, Capslock, ClipboardItem, DrawPhase, Drawable,
Element, Empty, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform,
TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window, WindowBounds,
WindowHandle, WindowOptions, app::GpuiMode,
Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestPlatformWindow,
TestScreenCaptureSource, TextSystem, VisualContext, Window, WindowBounds, WindowHandle,
WindowOptions, app::GpuiMode,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt, channel::oneshot};
@@ -220,7 +220,7 @@ impl TestAppContext {
f(&cx)
}
/// Adds a new window. The Window will always be backed by a `TestWindow` which
/// Adds a new window. The Window will always be backed by a `TestPlatformWindow` which
/// can be retrieved with `self.test_window(handle)`
pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
where
@@ -465,8 +465,8 @@ impl TestAppContext {
.unwrap();
}
/// Returns the `TestWindow` backing the given handle.
pub(crate) fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
/// Returns the `TestPlatformWindow` backing the given handle.
pub(crate) fn test_window(&self, window: AnyWindowHandle) -> TestPlatformWindow {
self.app
.borrow_mut()
.windows

View File

@@ -808,6 +808,15 @@ impl LinearColorStop {
}
impl Background {
/// Returns the solid color if this is a solid background, None otherwise.
pub fn as_solid(&self) -> Option<Hsla> {
if self.tag == BackgroundTag::Solid {
Some(self.solid)
} else {
None
}
}
/// Use specified color space for color interpolation.
///
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method>

View File

@@ -561,7 +561,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn update_ime_position(&self, _bounds: Bounds<Pixels>);
#[cfg(any(test, feature = "test-support"))]
fn as_test(&mut self) -> Option<&mut TestWindow> {
fn as_test(&mut self) -> Option<&mut TestPlatformWindow> {
None
}
}
@@ -1348,6 +1348,10 @@ pub enum WindowKind {
/// docks, notifications or wallpapers.
#[cfg(all(target_os = "linux", feature = "wayland"))]
LayerShell(layer_shell::LayerShellOptions),
/// A window that appears on top of its parent window and blocks interaction with it
/// until the modal window is closed
Dialog,
}
/// The appearance of the window, as defined by the operating system.

View File

@@ -36,12 +36,6 @@ use wayland_client::{
wl_shm_pool, wl_surface,
},
};
use wayland_protocols::wp::cursor_shape::v1::client::{
wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1,
};
use wayland_protocols::wp::fractional_scale::v1::client::{
wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
};
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::{
self, ZwpPrimarySelectionOfferV1,
};
@@ -61,6 +55,14 @@ use wayland_protocols::xdg::decoration::zv1::client::{
zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1,
};
use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
use wayland_protocols::{
wp::cursor_shape::v1::client::{wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1},
xdg::dialog::v1::client::xdg_wm_dialog_v1::{self, XdgWmDialogV1},
};
use wayland_protocols::{
wp::fractional_scale::v1::client::{wp_fractional_scale_manager_v1, wp_fractional_scale_v1},
xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1,
};
use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};
use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
@@ -122,6 +124,7 @@ pub struct Globals {
pub layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>,
pub blur_manager: Option<org_kde_kwin_blur_manager::OrgKdeKwinBlurManager>,
pub text_input_manager: Option<zwp_text_input_manager_v3::ZwpTextInputManagerV3>,
pub dialog: Option<xdg_wm_dialog_v1::XdgWmDialogV1>,
pub executor: ForegroundExecutor,
}
@@ -132,6 +135,7 @@ impl Globals {
qh: QueueHandle<WaylandClientStatePtr>,
seat: wl_seat::WlSeat,
) -> Self {
let dialog_v = XdgWmDialogV1::interface().version;
Globals {
activation: globals.bind(&qh, 1..=1, ()).ok(),
compositor: globals
@@ -160,6 +164,7 @@ impl Globals {
layer_shell: globals.bind(&qh, 1..=5, ()).ok(),
blur_manager: globals.bind(&qh, 1..=1, ()).ok(),
text_input_manager: globals.bind(&qh, 1..=1, ()).ok(),
dialog: globals.bind(&qh, dialog_v..=dialog_v, ()).ok(),
executor,
qh,
}
@@ -729,10 +734,7 @@ impl LinuxClient for WaylandClient {
) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut();
let parent = state
.keyboard_focused_window
.as_ref()
.and_then(|w| w.toplevel());
let parent = state.keyboard_focused_window.clone();
let (window, surface_id) = WaylandWindow::new(
handle,
@@ -751,7 +753,12 @@ impl LinuxClient for WaylandClient {
fn set_cursor_style(&self, style: CursorStyle) {
let mut state = self.0.borrow_mut();
let need_update = state.cursor_style != Some(style);
let need_update = state.cursor_style != Some(style)
&& (state.mouse_focused_window.is_none()
|| state
.mouse_focused_window
.as_ref()
.is_some_and(|w| !w.is_blocked()));
if need_update {
let serial = state.serial_tracker.get(SerialKind::MouseEnter);
@@ -1011,7 +1018,7 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
}
}
fn get_window(
pub(crate) fn get_window(
mut state: &mut RefMut<WaylandClientState>,
surface_id: &ObjectId,
) -> Option<WaylandWindowStatePtr> {
@@ -1654,6 +1661,30 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
if let Some(window) = state.mouse_focused_window.clone() {
if window.is_blocked() {
let default_style = CursorStyle::Arrow;
if state.cursor_style != Some(default_style) {
let serial = state.serial_tracker.get(SerialKind::MouseEnter);
state.cursor_style = Some(default_style);
if let Some(cursor_shape_device) = &state.cursor_shape_device {
cursor_shape_device.set_shape(serial, default_style.to_shape());
} else {
// cursor-shape-v1 isn't supported, set the cursor using a surface.
let wl_pointer = state
.wl_pointer
.clone()
.expect("window is focused by pointer");
let scale = window.primary_output_scale();
state.cursor.set_icon(
&wl_pointer,
serial,
default_style.to_icon_names(),
scale,
);
}
}
}
if state
.keyboard_focused_window
.as_ref()
@@ -2225,3 +2256,27 @@ impl Dispatch<zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, ()>
}
}
}
impl Dispatch<XdgWmDialogV1, ()> for WaylandClientStatePtr {
fn event(
_: &mut Self,
_: &XdgWmDialogV1,
_: <XdgWmDialogV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<XdgDialogV1, ()> for WaylandClientStatePtr {
fn event(
_state: &mut Self,
_proxy: &XdgDialogV1,
_event: <XdgDialogV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}

View File

@@ -7,7 +7,7 @@ use std::{
};
use blade_graphics as gpu;
use collections::HashMap;
use collections::{FxHashSet, HashMap};
use futures::channel::oneshot::Receiver;
use raw_window_handle as rwh;
@@ -20,7 +20,7 @@ use wayland_protocols::xdg::shell::client::xdg_surface;
use wayland_protocols::xdg::shell::client::xdg_toplevel::{self};
use wayland_protocols::{
wp::fractional_scale::v1::client::wp_fractional_scale_v1,
xdg::shell::client::xdg_toplevel::XdgToplevel,
xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1,
};
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1;
@@ -29,7 +29,7 @@ use crate::{
AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
ResizeEdge, Size, Tiling, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance,
WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowParams,
WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowParams, get_window,
layer_shell::LayerShellNotSupportedError, px, size,
};
use crate::{
@@ -87,6 +87,8 @@ struct InProgressConfigure {
pub struct WaylandWindowState {
surface_state: WaylandSurfaceState,
acknowledged_first_configure: bool,
parent: Option<WaylandWindowStatePtr>,
children: FxHashSet<ObjectId>,
pub surface: wl_surface::WlSurface,
app_id: Option<String>,
appearance: WindowAppearance,
@@ -126,7 +128,7 @@ impl WaylandSurfaceState {
surface: &wl_surface::WlSurface,
globals: &Globals,
params: &WindowParams,
parent: Option<XdgToplevel>,
parent: Option<WaylandWindowStatePtr>,
) -> anyhow::Result<Self> {
// For layer_shell windows, create a layer surface instead of an xdg surface
if let WindowKind::LayerShell(options) = &params.kind {
@@ -178,10 +180,28 @@ impl WaylandSurfaceState {
.get_xdg_surface(&surface, &globals.qh, surface.id());
let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id());
if params.kind == WindowKind::Floating {
toplevel.set_parent(parent.as_ref());
let xdg_parent = parent.as_ref().and_then(|w| w.toplevel());
if params.kind == WindowKind::Floating || params.kind == WindowKind::Dialog {
toplevel.set_parent(xdg_parent.as_ref());
}
let dialog = if params.kind == WindowKind::Dialog {
let dialog = globals.dialog.as_ref().map(|dialog| {
let xdg_dialog = dialog.get_xdg_dialog(&toplevel, &globals.qh, ());
xdg_dialog.set_modal();
xdg_dialog
});
if let Some(parent) = parent.as_ref() {
parent.add_child(surface.id());
}
dialog
} else {
None
};
if let Some(size) = params.window_min_size {
toplevel.set_min_size(size.width.0 as i32, size.height.0 as i32);
}
@@ -198,6 +218,7 @@ impl WaylandSurfaceState {
xdg_surface,
toplevel,
decoration,
dialog,
}))
}
}
@@ -206,6 +227,7 @@ pub struct WaylandXdgSurfaceState {
xdg_surface: xdg_surface::XdgSurface,
toplevel: xdg_toplevel::XdgToplevel,
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
dialog: Option<XdgDialogV1>,
}
pub struct WaylandLayerSurfaceState {
@@ -258,7 +280,13 @@ impl WaylandSurfaceState {
xdg_surface,
toplevel,
decoration: _decoration,
dialog,
}) => {
// drop the dialog before toplevel so compositor can explicitly unapply it's effects
if let Some(dialog) = dialog {
dialog.destroy();
}
// The role object (toplevel) must always be destroyed before the xdg_surface.
// See https://wayland.app/protocols/xdg-shell#xdg_surface:request:destroy
toplevel.destroy();
@@ -288,6 +316,7 @@ impl WaylandWindowState {
globals: Globals,
gpu_context: &BladeContext,
options: WindowParams,
parent: Option<WaylandWindowStatePtr>,
) -> anyhow::Result<Self> {
let renderer = {
let raw_window = RawWindow {
@@ -319,6 +348,8 @@ impl WaylandWindowState {
Ok(Self {
surface_state,
acknowledged_first_configure: false,
parent,
children: FxHashSet::default(),
surface,
app_id: None,
blur: None,
@@ -391,6 +422,10 @@ impl Drop for WaylandWindow {
fn drop(&mut self) {
let mut state = self.0.state.borrow_mut();
let surface_id = state.surface.id();
if let Some(parent) = state.parent.as_ref() {
parent.state.borrow_mut().children.remove(&surface_id);
}
let client = state.client.clone();
state.renderer.destroy();
@@ -448,10 +483,10 @@ impl WaylandWindow {
client: WaylandClientStatePtr,
params: WindowParams,
appearance: WindowAppearance,
parent: Option<XdgToplevel>,
parent: Option<WaylandWindowStatePtr>,
) -> anyhow::Result<(Self, ObjectId)> {
let surface = globals.compositor.create_surface(&globals.qh, ());
let surface_state = WaylandSurfaceState::new(&surface, &globals, &params, parent)?;
let surface_state = WaylandSurfaceState::new(&surface, &globals, &params, parent.clone())?;
if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() {
fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id());
@@ -473,6 +508,7 @@ impl WaylandWindow {
globals,
gpu_context,
params,
parent,
)?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())),
});
@@ -501,6 +537,16 @@ impl WaylandWindowStatePtr {
Rc::ptr_eq(&self.state, &other.state)
}
pub fn add_child(&self, child: ObjectId) {
let mut state = self.state.borrow_mut();
state.children.insert(child);
}
pub fn is_blocked(&self) -> bool {
let state = self.state.borrow();
!state.children.is_empty()
}
pub fn frame(&self) {
let mut state = self.state.borrow_mut();
state.surface.frame(&state.globals.qh, state.surface.id());
@@ -818,6 +864,9 @@ impl WaylandWindowStatePtr {
}
pub fn handle_ime(&self, ime: ImeInput) {
if self.is_blocked() {
return;
}
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
drop(state);
@@ -894,6 +943,21 @@ impl WaylandWindowStatePtr {
}
pub fn close(&self) {
let state = self.state.borrow();
let client = state.client.get_client();
#[allow(clippy::mutable_key_type)]
let children = state.children.clone();
drop(state);
for child in children {
let mut client_state = client.borrow_mut();
let window = get_window(&mut client_state, &child);
drop(client_state);
if let Some(child) = window {
child.close();
}
}
let mut callbacks = self.callbacks.borrow_mut();
if let Some(fun) = callbacks.close.take() {
fun()
@@ -901,6 +965,9 @@ impl WaylandWindowStatePtr {
}
pub fn handle_input(&self, input: PlatformInput) {
if self.is_blocked() {
return;
}
if let Some(ref mut fun) = self.callbacks.borrow_mut().input
&& !fun(input.clone()).propagate
{

View File

@@ -222,7 +222,7 @@ pub struct X11ClientState {
pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>);
impl X11ClientStatePtr {
fn get_client(&self) -> Option<X11Client> {
pub fn get_client(&self) -> Option<X11Client> {
self.0.upgrade().map(X11Client)
}
@@ -752,7 +752,7 @@ impl X11Client {
}
}
fn get_window(&self, win: xproto::Window) -> Option<X11WindowStatePtr> {
pub(crate) fn get_window(&self, win: xproto::Window) -> Option<X11WindowStatePtr> {
let state = self.0.borrow();
state
.windows
@@ -789,12 +789,12 @@ impl X11Client {
let [atom, arg1, arg2, arg3, arg4] = event.data.as_data32();
let mut state = self.0.borrow_mut();
if atom == state.atoms.WM_DELETE_WINDOW {
if atom == state.atoms.WM_DELETE_WINDOW && window.should_close() {
// window "x" button clicked by user
if window.should_close() {
// Rest of the close logic is handled in drop_window()
window.close();
}
// Rest of the close logic is handled in drop_window()
drop(state);
window.close();
state = self.0.borrow_mut();
} else if atom == state.atoms._NET_WM_SYNC_REQUEST {
window.state.borrow_mut().last_sync_counter =
Some(x11rb::protocol::sync::Int64 {
@@ -1216,6 +1216,33 @@ impl X11Client {
Event::XinputMotion(event) => {
let window = self.get_window(event.event)?;
let mut state = self.0.borrow_mut();
if window.is_blocked() {
// We want to set the cursor to the default arrow
// when the window is blocked
let style = CursorStyle::Arrow;
let current_style = state
.cursor_styles
.get(&window.x_window)
.unwrap_or(&CursorStyle::Arrow);
if *current_style != style
&& let Some(cursor) = state.get_cursor_icon(style)
{
state.cursor_styles.insert(window.x_window, style);
check_reply(
|| "Failed to set cursor style",
state.xcb_connection.change_window_attributes(
window.x_window,
&ChangeWindowAttributesAux {
cursor: Some(cursor),
..Default::default()
},
),
)
.log_err();
state.xcb_connection.flush().log_err();
};
}
let pressed_button = pressed_button_from_mask(event.button_mask[0]);
let position = point(
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
@@ -1489,7 +1516,7 @@ impl LinuxClient for X11Client {
let parent_window = state
.keyboard_focused_window
.and_then(|focused_window| state.windows.get(&focused_window))
.map(|window| window.window.x_window);
.map(|w| w.window.clone());
let x_window = state
.xcb_connection
.generate_id()
@@ -1544,7 +1571,15 @@ impl LinuxClient for X11Client {
.cursor_styles
.get(&focused_window)
.unwrap_or(&CursorStyle::Arrow);
if *current_style == style {
let window = state
.mouse_focused_window
.and_then(|w| state.windows.get(&w));
let should_change = *current_style != style
&& (window.is_none() || window.is_some_and(|w| !w.is_blocked()));
if !should_change {
return;
}

View File

@@ -11,6 +11,7 @@ use crate::{
};
use blade_graphics as gpu;
use collections::FxHashSet;
use raw_window_handle as rwh;
use util::{ResultExt, maybe};
use x11rb::{
@@ -74,6 +75,7 @@ x11rb::atom_manager! {
_NET_WM_WINDOW_TYPE,
_NET_WM_WINDOW_TYPE_NOTIFICATION,
_NET_WM_WINDOW_TYPE_DIALOG,
_NET_WM_STATE_MODAL,
_NET_WM_SYNC,
_NET_SUPPORTED,
_MOTIF_WM_HINTS,
@@ -249,6 +251,8 @@ pub struct Callbacks {
pub struct X11WindowState {
pub destroyed: bool,
parent: Option<X11WindowStatePtr>,
children: FxHashSet<xproto::Window>,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
atoms: XcbAtoms,
@@ -394,7 +398,7 @@ impl X11WindowState {
atoms: &XcbAtoms,
scale_factor: f32,
appearance: WindowAppearance,
parent_window: Option<xproto::Window>,
parent_window: Option<X11WindowStatePtr>,
) -> anyhow::Result<Self> {
let x_screen_index = params
.display_id
@@ -546,8 +550,8 @@ impl X11WindowState {
)?;
}
if params.kind == WindowKind::Floating {
if let Some(parent_window) = parent_window {
if params.kind == WindowKind::Floating || params.kind == WindowKind::Dialog {
if let Some(parent_window) = parent_window.as_ref().map(|w| w.x_window) {
// WM_TRANSIENT_FOR hint indicating the main application window. For floating windows, we set
// a parent window (WM_TRANSIENT_FOR) such that the window manager knows where to
// place the floating window in relation to the main window.
@@ -563,11 +567,23 @@ impl X11WindowState {
),
)?;
}
}
let parent = if params.kind == WindowKind::Dialog
&& let Some(parent) = parent_window
{
parent.add_child(x_window);
Some(parent)
} else {
None
};
if params.kind == WindowKind::Dialog {
// _NET_WM_WINDOW_TYPE_DIALOG indicates that this is a dialog (floating) window
// https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html
check_reply(
|| "X11 ChangeProperty32 setting window type for floating window failed.",
|| "X11 ChangeProperty32 setting window type for dialog window failed.",
xcb.change_property32(
xproto::PropMode::REPLACE,
x_window,
@@ -576,6 +592,20 @@ impl X11WindowState {
&[atoms._NET_WM_WINDOW_TYPE_DIALOG],
),
)?;
// We set the modal state for dialog windows, so that the window manager
// can handle it appropriately (e.g., prevent interaction with the parent window
// while the dialog is open).
check_reply(
|| "X11 ChangeProperty32 setting modal state for dialog window failed.",
xcb.change_property32(
xproto::PropMode::REPLACE,
x_window,
atoms._NET_WM_STATE,
xproto::AtomEnum::ATOM,
&[atoms._NET_WM_STATE_MODAL],
),
)?;
}
check_reply(
@@ -667,6 +697,8 @@ impl X11WindowState {
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
Ok(Self {
parent,
children: FxHashSet::default(),
client,
executor,
display,
@@ -720,6 +752,11 @@ pub(crate) struct X11Window(pub X11WindowStatePtr);
impl Drop for X11Window {
fn drop(&mut self) {
let mut state = self.0.state.borrow_mut();
if let Some(parent) = state.parent.as_ref() {
parent.state.borrow_mut().children.remove(&self.0.x_window);
}
state.renderer.destroy();
let destroy_x_window = maybe!({
@@ -734,8 +771,6 @@ impl Drop for X11Window {
.log_err();
if destroy_x_window.is_some() {
// Mark window as destroyed so that we can filter out when X11 events
// for it still come in.
state.destroyed = true;
let this_ptr = self.0.clone();
@@ -773,7 +808,7 @@ impl X11Window {
atoms: &XcbAtoms,
scale_factor: f32,
appearance: WindowAppearance,
parent_window: Option<xproto::Window>,
parent_window: Option<X11WindowStatePtr>,
) -> anyhow::Result<Self> {
let ptr = X11WindowStatePtr {
state: Rc::new(RefCell::new(X11WindowState::new(
@@ -979,7 +1014,31 @@ impl X11WindowStatePtr {
Ok(())
}
pub fn add_child(&self, child: xproto::Window) {
let mut state = self.state.borrow_mut();
state.children.insert(child);
}
pub fn is_blocked(&self) -> bool {
let state = self.state.borrow();
!state.children.is_empty()
}
pub fn close(&self) {
let state = self.state.borrow();
let client = state.client.clone();
#[allow(clippy::mutable_key_type)]
let children = state.children.clone();
drop(state);
if let Some(client) = client.get_client() {
for child in children {
if let Some(child_window) = client.get_window(child) {
child_window.close();
}
}
}
let mut callbacks = self.callbacks.borrow_mut();
if let Some(fun) = callbacks.close.take() {
fun()
@@ -994,6 +1053,9 @@ impl X11WindowStatePtr {
}
pub fn handle_input(&self, input: PlatformInput) {
if self.is_blocked() {
return;
}
if let Some(ref mut fun) = self.callbacks.borrow_mut().input
&& !fun(input.clone()).propagate
{
@@ -1016,6 +1078,9 @@ impl X11WindowStatePtr {
}
pub fn handle_ime_commit(&self, text: String) {
if self.is_blocked() {
return;
}
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
drop(state);
@@ -1026,6 +1091,9 @@ impl X11WindowStatePtr {
}
pub fn handle_ime_preedit(&self, text: String) {
if self.is_blocked() {
return;
}
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
drop(state);
@@ -1036,6 +1104,9 @@ impl X11WindowStatePtr {
}
pub fn handle_ime_unmark(&self) {
if self.is_blocked() {
return;
}
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
drop(state);
@@ -1046,6 +1117,9 @@ impl X11WindowStatePtr {
}
pub fn handle_ime_delete(&self) {
if self.is_blocked() {
return;
}
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
drop(state);

View File

@@ -62,9 +62,12 @@ static mut BLURRED_VIEW_CLASS: *const Class = ptr::null();
#[allow(non_upper_case_globals)]
const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
NSWindowStyleMask::from_bits_retain(1 << 7);
// WindowLevel const value ref: https://docs.rs/core-graphics2/0.4.1/src/core_graphics2/window_level.rs.html
#[allow(non_upper_case_globals)]
const NSNormalWindowLevel: NSInteger = 0;
#[allow(non_upper_case_globals)]
const NSFloatingWindowLevel: NSInteger = 3;
#[allow(non_upper_case_globals)]
const NSPopUpWindowLevel: NSInteger = 101;
#[allow(non_upper_case_globals)]
const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
@@ -423,6 +426,8 @@ struct MacWindowState {
select_previous_tab_callback: Option<Box<dyn FnMut()>>,
toggle_tab_bar_callback: Option<Box<dyn FnMut()>>,
activated_least_once: bool,
// The parent window if this window is a sheet (Dialog kind)
sheet_parent: Option<id>,
}
impl MacWindowState {
@@ -622,11 +627,16 @@ impl MacWindow {
}
let native_window: id = match kind {
WindowKind::Normal | WindowKind::Floating => msg_send![WINDOW_CLASS, alloc],
WindowKind::Normal => {
msg_send![WINDOW_CLASS, alloc]
}
WindowKind::PopUp => {
style_mask |= NSWindowStyleMaskNonactivatingPanel;
msg_send![PANEL_CLASS, alloc]
}
WindowKind::Floating | WindowKind::Dialog => {
msg_send![PANEL_CLASS, alloc]
}
};
let display = display_id
@@ -729,6 +739,7 @@ impl MacWindow {
select_previous_tab_callback: None,
toggle_tab_bar_callback: None,
activated_least_once: false,
sheet_parent: None,
})));
(*native_window).set_ivar(
@@ -779,9 +790,18 @@ impl MacWindow {
content_view.addSubview_(native_view.autorelease());
native_window.makeFirstResponder_(native_view);
let app: id = NSApplication::sharedApplication(nil);
let main_window: id = msg_send![app, mainWindow];
let mut sheet_parent = None;
match kind {
WindowKind::Normal | WindowKind::Floating => {
native_window.setLevel_(NSNormalWindowLevel);
if kind == WindowKind::Floating {
// Let the window float keep above normal windows.
native_window.setLevel_(NSFloatingWindowLevel);
} else {
native_window.setLevel_(NSNormalWindowLevel);
}
native_window.setAcceptsMouseMovedEvents_(YES);
if let Some(tabbing_identifier) = tabbing_identifier {
@@ -816,10 +836,23 @@ impl MacWindow {
NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
);
}
WindowKind::Dialog => {
if !main_window.is_null() {
let parent = {
let active_sheet: id = msg_send![main_window, attachedSheet];
if active_sheet.is_null() {
main_window
} else {
active_sheet
}
};
let _: () =
msg_send![parent, beginSheet: native_window completionHandler: nil];
sheet_parent = Some(parent);
}
}
}
let app = NSApplication::sharedApplication(nil);
let main_window: id = msg_send![app, mainWindow];
if allows_automatic_window_tabbing
&& !main_window.is_null()
&& main_window != native_window
@@ -861,7 +894,11 @@ impl MacWindow {
// the window position might be incorrect if the main screen (the screen that contains the window that has focus)
// is different from the primary screen.
NSWindow::setFrameTopLeftPoint_(native_window, window_rect.origin);
window.0.lock().move_traffic_light();
{
let mut window_state = window.0.lock();
window_state.move_traffic_light();
window_state.sheet_parent = sheet_parent;
}
pool.drain();
@@ -938,6 +975,7 @@ impl Drop for MacWindow {
let mut this = self.0.lock();
this.renderer.destroy();
let window = this.native_window;
let sheet_parent = this.sheet_parent.take();
this.display_link.take();
unsafe {
this.native_window.setDelegate_(nil);
@@ -946,6 +984,9 @@ impl Drop for MacWindow {
this.executor
.spawn(async move {
unsafe {
if let Some(parent) = sheet_parent {
let _: () = msg_send![parent, endSheet: window];
}
window.close();
window.autorelease();
}

View File

@@ -3,7 +3,7 @@ use crate::{
DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay,
PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PromptButton,
ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, Task,
TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
TestDisplay, TestPlatformWindow, WindowAppearance, WindowParams, size,
};
use anyhow::Result;
use collections::VecDeque;
@@ -26,7 +26,7 @@ pub(crate) struct TestPlatform {
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
pub(crate) active_window: RefCell<Option<TestWindow>>,
pub(crate) active_window: RefCell<Option<TestPlatformWindow>>,
active_display: Rc<dyn PlatformDisplay>,
active_cursor: Mutex<CursorStyle>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
@@ -196,7 +196,7 @@ impl TestPlatform {
rx
}
pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
pub(crate) fn set_active_window(&self, window: Option<TestPlatformWindow>) {
let executor = self.foreground_executor();
let previous_window = self.active_window.borrow_mut().take();
self.active_window.borrow_mut().clone_from(&window);
@@ -314,7 +314,7 @@ impl Platform for TestPlatform {
handle: AnyWindowHandle,
params: WindowParams,
) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
let window = TestWindow::new(
let window = TestPlatformWindow::new(
handle,
params,
self.weak.clone(),

View File

@@ -12,7 +12,7 @@ use std::{
sync::{self, Arc},
};
pub(crate) struct TestWindowState {
pub(crate) struct TestPlatformWindowState {
pub(crate) bounds: Bounds<Pixels>,
pub(crate) handle: AnyWindowHandle,
display: Rc<dyn PlatformDisplay>,
@@ -32,9 +32,9 @@ pub(crate) struct TestWindowState {
}
#[derive(Clone)]
pub(crate) struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>);
pub(crate) struct TestPlatformWindow(pub(crate) Rc<Mutex<TestPlatformWindowState>>);
impl HasWindowHandle for TestWindow {
impl HasWindowHandle for TestPlatformWindow {
fn window_handle(
&self,
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
@@ -42,7 +42,7 @@ impl HasWindowHandle for TestWindow {
}
}
impl HasDisplayHandle for TestWindow {
impl HasDisplayHandle for TestPlatformWindow {
fn display_handle(
&self,
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
@@ -50,14 +50,14 @@ impl HasDisplayHandle for TestWindow {
}
}
impl TestWindow {
impl TestPlatformWindow {
pub fn new(
handle: AnyWindowHandle,
params: WindowParams,
platform: Weak<TestPlatform>,
display: Rc<dyn PlatformDisplay>,
) -> Self {
Self(Rc::new(Mutex::new(TestWindowState {
Self(Rc::new(Mutex::new(TestPlatformWindowState {
bounds: params.bounds,
display,
platform,
@@ -111,7 +111,7 @@ impl TestWindow {
}
}
impl PlatformWindow for TestWindow {
impl PlatformWindow for TestPlatformWindow {
fn bounds(&self) -> Bounds<Pixels> {
self.0.lock().bounds
}
@@ -272,7 +272,7 @@ impl PlatformWindow for TestWindow {
self.0.lock().sprite_atlas.clone()
}
fn as_test(&mut self) -> Option<&mut TestWindow> {
fn as_test(&mut self) -> Option<&mut TestPlatformWindow> {
Some(self)
}

View File

@@ -270,6 +270,14 @@ impl WindowsWindowInner {
fn handle_destroy_msg(&self, handle: HWND) -> Option<isize> {
let callback = { self.state.callbacks.close.take() };
// Re-enable parent window if this was a modal dialog
if let Some(parent_hwnd) = self.parent_hwnd {
unsafe {
let _ = EnableWindow(parent_hwnd, true);
let _ = SetForegroundWindow(parent_hwnd);
}
}
if let Some(callback) = callback {
callback();
}

View File

@@ -83,6 +83,7 @@ pub(crate) struct WindowsWindowInner {
pub(crate) validation_number: usize,
pub(crate) main_receiver: flume::Receiver<RunnableVariant>,
pub(crate) platform_window_handle: HWND,
pub(crate) parent_hwnd: Option<HWND>,
}
impl WindowsWindowState {
@@ -241,6 +242,7 @@ impl WindowsWindowInner {
main_receiver: context.main_receiver.clone(),
platform_window_handle: context.platform_window_handle,
system_settings: WindowsSystemSettings::new(context.display),
parent_hwnd: context.parent_hwnd,
}))
}
@@ -368,6 +370,7 @@ struct WindowCreateContext {
disable_direct_composition: bool,
directx_devices: DirectXDevices,
invalidate_devices: Arc<AtomicBool>,
parent_hwnd: Option<HWND>,
}
impl WindowsWindow {
@@ -390,6 +393,20 @@ impl WindowsWindow {
invalidate_devices,
} = creation_info;
register_window_class(icon);
let parent_hwnd = if params.kind == WindowKind::Dialog {
let parent_window = unsafe { GetActiveWindow() };
if parent_window.is_invalid() {
None
} else {
// Disable the parent window to make this dialog modal
unsafe {
EnableWindow(parent_window, false).as_bool();
};
Some(parent_window)
}
} else {
None
};
let hide_title_bar = params
.titlebar
.as_ref()
@@ -416,8 +433,14 @@ impl WindowsWindow {
if params.is_minimizable {
dwstyle |= WS_MINIMIZEBOX;
}
let dwexstyle = if params.kind == WindowKind::Dialog {
dwstyle |= WS_POPUP | WS_CAPTION;
WS_EX_DLGMODALFRAME
} else {
WS_EX_APPWINDOW
};
(WS_EX_APPWINDOW, dwstyle)
(dwexstyle, dwstyle)
};
if !disable_direct_composition {
dwexstyle |= WS_EX_NOREDIRECTIONBITMAP;
@@ -449,6 +472,7 @@ impl WindowsWindow {
disable_direct_composition,
directx_devices,
invalidate_devices,
parent_hwnd,
};
let creation_result = unsafe {
CreateWindowExW(
@@ -460,7 +484,7 @@ impl WindowsWindow {
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
None,
parent_hwnd,
None,
Some(hinstance.into()),
Some(&context as *const _ as *const _),

View File

@@ -20,6 +20,126 @@ pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
pub(crate) type DrawOrder = u32;
/// Test-only scene snapshot for inspecting rendered content.
#[cfg(any(test, feature = "test-support"))]
pub mod test_scene {
use crate::{Bounds, Hsla, Point, ScaledPixels, SharedString};
/// A rendered quad (background, border, cursor, selection, etc.)
#[derive(Debug, Clone)]
pub struct RenderedQuad {
/// Bounds in scaled pixels.
pub bounds: Bounds<ScaledPixels>,
/// Background color (if solid).
pub background_color: Option<Hsla>,
/// Border color.
pub border_color: Hsla,
}
/// A named diagnostic quad for tests and debugging of imperative paint logic.
///
/// This is not necessarily a "real" painted quad; it is metadata recorded alongside a scene.
#[derive(Debug, Clone)]
pub struct DiagnosticQuad {
/// A stable name that test code can filter by.
pub name: SharedString,
/// Bounds in scaled pixels.
pub bounds: Bounds<ScaledPixels>,
/// Optional color hint (useful when visualizing).
pub color: Option<Hsla>,
}
/// A rendered text glyph.
#[derive(Debug, Clone)]
pub struct RenderedGlyph {
/// Origin position in scaled pixels.
pub origin: Point<ScaledPixels>,
/// Size in scaled pixels.
pub size: crate::Size<ScaledPixels>,
/// Color of the glyph.
pub color: Hsla,
}
/// Snapshot of scene contents for testing.
#[derive(Debug, Default)]
pub struct SceneSnapshot {
/// All rendered quads.
pub quads: Vec<RenderedQuad>,
/// All rendered text glyphs.
pub glyphs: Vec<RenderedGlyph>,
/// Named diagnostic quads recorded by imperative drawing code for tests/debugging.
pub diagnostic_quads: Vec<DiagnosticQuad>,
/// Number of shadow primitives.
pub shadow_count: usize,
/// Number of path primitives.
pub path_count: usize,
/// Number of underline primitives.
pub underline_count: usize,
/// Number of polychrome sprites (images, emoji).
pub polychrome_sprite_count: usize,
/// Number of surface primitives.
pub surface_count: usize,
}
impl SceneSnapshot {
/// Get unique Y positions of quads, sorted.
pub fn quad_y_positions(&self) -> Vec<f32> {
let mut positions: Vec<f32> = self.quads.iter().map(|q| q.bounds.origin.y.0).collect();
positions.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
positions.dedup();
positions
}
/// Get unique Y positions of glyphs, sorted.
pub fn glyph_y_positions(&self) -> Vec<f32> {
let mut positions: Vec<f32> = self.glyphs.iter().map(|g| g.origin.y.0).collect();
positions.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
positions.dedup();
positions
}
/// Find quads within a Y range.
pub fn quads_in_y_range(&self, min_y: f32, max_y: f32) -> Vec<&RenderedQuad> {
self.quads
.iter()
.filter(|q| {
let y = q.bounds.origin.y.0;
y >= min_y && y < max_y
})
.collect()
}
/// Find glyphs within a Y range.
pub fn glyphs_in_y_range(&self, min_y: f32, max_y: f32) -> Vec<&RenderedGlyph> {
self.glyphs
.iter()
.filter(|g| {
let y = g.origin.y.0;
y >= min_y && y < max_y
})
.collect()
}
/// Debug summary string.
pub fn summary(&self) -> String {
format!(
"quads: {}, glyphs: {}, diagnostic_quads: {}, shadows: {}, paths: {}, underlines: {}, polychrome: {}, surfaces: {}",
self.quads.len(),
self.glyphs.len(),
self.diagnostic_quads.len(),
self.shadow_count,
self.path_count,
self.underline_count,
self.polychrome_sprite_count,
self.surface_count,
)
}
}
}
#[cfg(any(test, feature = "test-support"))]
pub use test_scene::*;
#[derive(Default)]
pub(crate) struct Scene {
pub(crate) paint_operations: Vec<PaintOperation>,
@@ -32,6 +152,8 @@ pub(crate) struct Scene {
pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
pub(crate) surfaces: Vec<PaintSurface>,
#[cfg(any(test, feature = "test-support"))]
pub(crate) diagnostic_quads: Vec<test_scene::DiagnosticQuad>,
}
impl Scene {
@@ -46,6 +168,8 @@ impl Scene {
self.monochrome_sprites.clear();
self.polychrome_sprites.clear();
self.surfaces.clear();
#[cfg(any(test, feature = "test-support"))]
self.diagnostic_quads.clear();
}
pub fn len(&self) -> usize {
@@ -124,6 +248,41 @@ impl Scene {
}
}
/// Create a snapshot of the scene for testing.
#[cfg(any(test, feature = "test-support"))]
pub fn snapshot(&self) -> SceneSnapshot {
let quads = self
.quads
.iter()
.map(|q| RenderedQuad {
bounds: q.bounds,
background_color: q.background.as_solid(),
border_color: q.border_color,
})
.collect();
let glyphs = self
.monochrome_sprites
.iter()
.map(|s| RenderedGlyph {
origin: s.bounds.origin,
size: s.bounds.size,
color: s.color,
})
.collect();
SceneSnapshot {
quads,
glyphs,
diagnostic_quads: self.diagnostic_quads.clone(),
shadow_count: self.shadows.len(),
path_count: self.paths.len(),
underline_count: self.underlines.len(),
polychrome_sprite_count: self.polychrome_sprites.len(),
surface_count: self.surfaces.len(),
}
}
pub fn finish(&mut self) {
self.shadows.sort_by_key(|shadow| shadow.order);
self.quads.sort_by_key(|quad| quad.order);
@@ -134,6 +293,10 @@ impl Scene {
self.polychrome_sprites
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
self.surfaces.sort_by_key(|surface| surface.order);
#[cfg(any(test, feature = "test-support"))]
self.diagnostic_quads
.sort_by(|a, b| a.name.as_ref().cmp(b.name.as_ref()));
}
#[cfg_attr(
@@ -620,7 +783,7 @@ impl Default for TransformationMatrix {
#[repr(C)]
pub(crate) struct MonochromeSprite {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub pad: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
@@ -638,7 +801,7 @@ impl From<MonochromeSprite> for Primitive {
#[repr(C)]
pub(crate) struct PolychromeSprite {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub pad: u32,
pub grayscale: bool,
pub opacity: f32,
pub bounds: Bounds<ScaledPixels>,

View File

@@ -44,6 +44,14 @@ impl ShapedLine {
self.layout.len
}
/// The width of the shaped line in pixels.
///
/// This is the glyph advance width computed by the text shaping system and is useful for
/// incrementally advancing a "pen" when painting multiple fragments on the same row.
pub fn width(&self) -> Pixels {
self.layout.width
}
/// Override the len, useful if you're rendering text a
/// as text b (e.g. rendering invisibles).
pub fn with_len(mut self, len: usize) -> Self {

View File

@@ -506,6 +506,10 @@ impl HitboxId {
///
/// See [`Hitbox::is_hovered`] for details.
pub fn is_hovered(self, window: &Window) -> bool {
// If this hitbox has captured the pointer, it's always considered hovered
if window.captured_hitbox == Some(self) {
return true;
}
let hit_test = &window.mouse_hit_test;
for id in hit_test.ids.iter().take(hit_test.hover_hitbox_count) {
if self == *id {
@@ -760,6 +764,11 @@ impl Frame {
self.tab_stops.clear();
self.focus = None;
#[cfg(any(test, feature = "test-support"))]
{
self.debug_bounds.clear();
}
#[cfg(any(feature = "inspector", debug_assertions))]
{
self.next_inspector_instance_ids.clear();
@@ -887,6 +896,9 @@ pub struct Window {
pub(crate) pending_input_observers: SubscriberSet<(), AnyObserver>,
prompt: Option<RenderablePromptHandle>,
pub(crate) client_inset: Option<Pixels>,
/// The hitbox that has captured the pointer, if any.
/// While captured, mouse events route to this hitbox regardless of hit testing.
captured_hitbox: Option<HitboxId>,
#[cfg(any(feature = "inspector", debug_assertions))]
inspector: Option<Entity<Inspector>>,
}
@@ -1311,6 +1323,7 @@ impl Window {
prompt: None,
client_inset: None,
image_cache_stack: Vec::new(),
captured_hitbox: None,
#[cfg(any(feature = "inspector", debug_assertions))]
inspector: None,
})
@@ -1994,6 +2007,26 @@ impl Window {
self.mouse_position
}
/// Captures the pointer for the given hitbox. While captured, all mouse move and mouse up
/// events will be routed to listeners that check this hitbox's `is_hovered` status,
/// regardless of actual hit testing. This enables drag operations that continue
/// even when the pointer moves outside the element's bounds.
///
/// The capture is automatically released on mouse up.
pub fn capture_pointer(&mut self, hitbox_id: HitboxId) {
self.captured_hitbox = Some(hitbox_id);
}
/// Releases any active pointer capture.
pub fn release_pointer(&mut self) {
self.captured_hitbox = None;
}
/// Returns the hitbox that has captured the pointer, if any.
pub fn captured_hitbox(&self) -> Option<HitboxId> {
self.captured_hitbox
}
/// The current state of the keyboard's modifiers
pub fn modifiers(&self) -> Modifiers {
self.modifiers
@@ -2966,6 +2999,41 @@ impl Window {
});
}
#[cfg(any(test, feature = "test-support"))]
/// Record a named diagnostic quad for test/debug snapshots.
///
/// This is intended for debugging and asserting against imperative painting logic. The
/// recorded quad does not affect rendering; it is captured alongside the rendered scene and
/// exposed via `scene_snapshot()`.
pub fn record_diagnostic_quad(
&mut self,
name: impl Into<SharedString>,
bounds: Bounds<Pixels>,
color: Option<Hsla>,
) {
self.invalidator.debug_assert_paint();
let scale_factor = self.scale_factor();
self.next_frame.scene.diagnostic_quads.push(crate::test_scene::DiagnosticQuad {
name: name.into(),
bounds: bounds.scale(scale_factor),
color,
});
}
#[cfg(not(any(test, feature = "test-support")))]
#[inline]
/// Record a named diagnostic quad for test/debug snapshots.
///
/// This is a no-op unless tests or the `test-support` feature are enabled.
pub fn record_diagnostic_quad(
&mut self,
_name: impl Into<SharedString>,
_bounds: Bounds<Pixels>,
_color: Option<Hsla>,
) {
}
/// Paint the given `Path` into the scene for the next frame at the current z-index.
///
/// This method should only be called as part of the paint phase of element drawing.
@@ -3850,6 +3918,11 @@ impl Window {
self.refresh();
}
}
// Auto-release pointer capture on mouse up
if event.is::<MouseUpEvent>() && self.captured_hitbox.is_some() {
self.captured_hitbox = None;
}
}
fn dispatch_key_event(&mut self, event: &dyn Any, cx: &mut App) {

View File

@@ -0,0 +1,342 @@
/* automatically generated by rust-bindgen 0.71.1 */
pub const kCMTimeMaxTimescale: u32 = 2147483647;
pub type UInt32 = ::std::os::raw::c_uint;
pub type FourCharCode = UInt32;
pub type OSType = FourCharCode;
pub type CFIndex = ::std::os::raw::c_long;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct __CFString {
_unused: [u8; 0],
}
pub type CFStringRef = *const __CFString;
pub type CMItemIndex = CFIndex;
pub type CMTimeValue = i64;
pub type CMTimeScale = i32;
pub type CMTimeEpoch = i64;
pub const CMTimeFlags_kCMTimeFlags_Valid: CMTimeFlags = 1;
pub const CMTimeFlags_kCMTimeFlags_HasBeenRounded: CMTimeFlags = 2;
pub const CMTimeFlags_kCMTimeFlags_PositiveInfinity: CMTimeFlags = 4;
pub const CMTimeFlags_kCMTimeFlags_NegativeInfinity: CMTimeFlags = 8;
pub const CMTimeFlags_kCMTimeFlags_Indefinite: CMTimeFlags = 16;
pub const CMTimeFlags_kCMTimeFlags_ImpliedValueFlagsMask: CMTimeFlags = 28;
pub type CMTimeFlags = u32;
#[repr(C, packed(4))]
#[derive(Debug, Copy, Clone)]
pub struct CMTime {
pub value: CMTimeValue,
pub timescale: CMTimeScale,
pub flags: CMTimeFlags,
pub epoch: CMTimeEpoch,
}
unsafe extern "C" {
pub static kCMTimeInvalid: CMTime;
}
unsafe extern "C" {
pub static kCMTimeIndefinite: CMTime;
}
unsafe extern "C" {
pub static kCMTimePositiveInfinity: CMTime;
}
unsafe extern "C" {
pub static kCMTimeNegativeInfinity: CMTime;
}
unsafe extern "C" {
pub static kCMTimeZero: CMTime;
}
unsafe extern "C" {
pub fn CMTimeMake(value: i64, timescale: i32) -> CMTime;
}
unsafe extern "C" {
pub static kCMTimeValueKey: CFStringRef;
}
unsafe extern "C" {
pub static kCMTimeScaleKey: CFStringRef;
}
unsafe extern "C" {
pub static kCMTimeEpochKey: CFStringRef;
}
unsafe extern "C" {
pub static kCMTimeFlagsKey: CFStringRef;
}
pub type CVReturn = i32;
pub const kCVReturnSuccess: _bindgen_ty_29 = 0;
pub const kCVReturnFirst: _bindgen_ty_29 = -6660;
pub const kCVReturnError: _bindgen_ty_29 = -6660;
pub const kCVReturnInvalidArgument: _bindgen_ty_29 = -6661;
pub const kCVReturnAllocationFailed: _bindgen_ty_29 = -6662;
pub const kCVReturnUnsupported: _bindgen_ty_29 = -6663;
pub const kCVReturnInvalidDisplay: _bindgen_ty_29 = -6670;
pub const kCVReturnDisplayLinkAlreadyRunning: _bindgen_ty_29 = -6671;
pub const kCVReturnDisplayLinkNotRunning: _bindgen_ty_29 = -6672;
pub const kCVReturnDisplayLinkCallbacksNotSet: _bindgen_ty_29 = -6673;
pub const kCVReturnInvalidPixelFormat: _bindgen_ty_29 = -6680;
pub const kCVReturnInvalidSize: _bindgen_ty_29 = -6681;
pub const kCVReturnInvalidPixelBufferAttributes: _bindgen_ty_29 = -6682;
pub const kCVReturnPixelBufferNotOpenGLCompatible: _bindgen_ty_29 = -6683;
pub const kCVReturnPixelBufferNotMetalCompatible: _bindgen_ty_29 = -6684;
pub const kCVReturnWouldExceedAllocationThreshold: _bindgen_ty_29 = -6689;
pub const kCVReturnPoolAllocationFailed: _bindgen_ty_29 = -6690;
pub const kCVReturnInvalidPoolAttributes: _bindgen_ty_29 = -6691;
pub const kCVReturnRetry: _bindgen_ty_29 = -6692;
pub const kCVReturnLast: _bindgen_ty_29 = -6699;
pub type _bindgen_ty_29 = CVReturn;
pub const kCVPixelFormatType_1Monochrome: _bindgen_ty_1462 = 1;
pub const kCVPixelFormatType_2Indexed: _bindgen_ty_1462 = 2;
pub const kCVPixelFormatType_4Indexed: _bindgen_ty_1462 = 4;
pub const kCVPixelFormatType_8Indexed: _bindgen_ty_1462 = 8;
pub const kCVPixelFormatType_1IndexedGray_WhiteIsZero: _bindgen_ty_1462 = 33;
pub const kCVPixelFormatType_2IndexedGray_WhiteIsZero: _bindgen_ty_1462 = 34;
pub const kCVPixelFormatType_4IndexedGray_WhiteIsZero: _bindgen_ty_1462 = 36;
pub const kCVPixelFormatType_8IndexedGray_WhiteIsZero: _bindgen_ty_1462 = 40;
pub const kCVPixelFormatType_16BE555: _bindgen_ty_1462 = 16;
pub const kCVPixelFormatType_16LE555: _bindgen_ty_1462 = 1278555445;
pub const kCVPixelFormatType_16LE5551: _bindgen_ty_1462 = 892679473;
pub const kCVPixelFormatType_16BE565: _bindgen_ty_1462 = 1110783541;
pub const kCVPixelFormatType_16LE565: _bindgen_ty_1462 = 1278555701;
pub const kCVPixelFormatType_24RGB: _bindgen_ty_1462 = 24;
pub const kCVPixelFormatType_24BGR: _bindgen_ty_1462 = 842285639;
pub const kCVPixelFormatType_32ARGB: _bindgen_ty_1462 = 32;
pub const kCVPixelFormatType_32BGRA: _bindgen_ty_1462 = 1111970369;
pub const kCVPixelFormatType_32ABGR: _bindgen_ty_1462 = 1094862674;
pub const kCVPixelFormatType_32RGBA: _bindgen_ty_1462 = 1380401729;
pub const kCVPixelFormatType_64ARGB: _bindgen_ty_1462 = 1647719521;
pub const kCVPixelFormatType_64RGBALE: _bindgen_ty_1462 = 1815491698;
pub const kCVPixelFormatType_48RGB: _bindgen_ty_1462 = 1647589490;
pub const kCVPixelFormatType_32AlphaGray: _bindgen_ty_1462 = 1647522401;
pub const kCVPixelFormatType_16Gray: _bindgen_ty_1462 = 1647392359;
pub const kCVPixelFormatType_30RGB: _bindgen_ty_1462 = 1378955371;
pub const kCVPixelFormatType_30RGB_r210: _bindgen_ty_1462 = 1915892016;
pub const kCVPixelFormatType_422YpCbCr8: _bindgen_ty_1462 = 846624121;
pub const kCVPixelFormatType_4444YpCbCrA8: _bindgen_ty_1462 = 1983131704;
pub const kCVPixelFormatType_4444YpCbCrA8R: _bindgen_ty_1462 = 1916022840;
pub const kCVPixelFormatType_4444AYpCbCr8: _bindgen_ty_1462 = 2033463352;
pub const kCVPixelFormatType_4444AYpCbCr16: _bindgen_ty_1462 = 2033463606;
pub const kCVPixelFormatType_4444AYpCbCrFloat: _bindgen_ty_1462 = 1916036716;
pub const kCVPixelFormatType_444YpCbCr8: _bindgen_ty_1462 = 1983066168;
pub const kCVPixelFormatType_422YpCbCr16: _bindgen_ty_1462 = 1983000886;
pub const kCVPixelFormatType_422YpCbCr10: _bindgen_ty_1462 = 1983000880;
pub const kCVPixelFormatType_444YpCbCr10: _bindgen_ty_1462 = 1983131952;
pub const kCVPixelFormatType_420YpCbCr8Planar: _bindgen_ty_1462 = 2033463856;
pub const kCVPixelFormatType_420YpCbCr8PlanarFullRange: _bindgen_ty_1462 = 1714696752;
pub const kCVPixelFormatType_422YpCbCr_4A_8BiPlanar: _bindgen_ty_1462 = 1630697081;
pub const kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: _bindgen_ty_1462 = 875704438;
pub const kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: _bindgen_ty_1462 = 875704422;
pub const kCVPixelFormatType_422YpCbCr8BiPlanarVideoRange: _bindgen_ty_1462 = 875704950;
pub const kCVPixelFormatType_422YpCbCr8BiPlanarFullRange: _bindgen_ty_1462 = 875704934;
pub const kCVPixelFormatType_444YpCbCr8BiPlanarVideoRange: _bindgen_ty_1462 = 875836534;
pub const kCVPixelFormatType_444YpCbCr8BiPlanarFullRange: _bindgen_ty_1462 = 875836518;
pub const kCVPixelFormatType_422YpCbCr8_yuvs: _bindgen_ty_1462 = 2037741171;
pub const kCVPixelFormatType_422YpCbCr8FullRange: _bindgen_ty_1462 = 2037741158;
pub const kCVPixelFormatType_OneComponent8: _bindgen_ty_1462 = 1278226488;
pub const kCVPixelFormatType_TwoComponent8: _bindgen_ty_1462 = 843264056;
pub const kCVPixelFormatType_30RGBLEPackedWideGamut: _bindgen_ty_1462 = 1999843442;
pub const kCVPixelFormatType_ARGB2101010LEPacked: _bindgen_ty_1462 = 1815162994;
pub const kCVPixelFormatType_40ARGBLEWideGamut: _bindgen_ty_1462 = 1999908961;
pub const kCVPixelFormatType_40ARGBLEWideGamutPremultiplied: _bindgen_ty_1462 = 1999908973;
pub const kCVPixelFormatType_OneComponent10: _bindgen_ty_1462 = 1278226736;
pub const kCVPixelFormatType_OneComponent12: _bindgen_ty_1462 = 1278226738;
pub const kCVPixelFormatType_OneComponent16: _bindgen_ty_1462 = 1278226742;
pub const kCVPixelFormatType_TwoComponent16: _bindgen_ty_1462 = 843264310;
pub const kCVPixelFormatType_OneComponent16Half: _bindgen_ty_1462 = 1278226536;
pub const kCVPixelFormatType_OneComponent32Float: _bindgen_ty_1462 = 1278226534;
pub const kCVPixelFormatType_TwoComponent16Half: _bindgen_ty_1462 = 843264104;
pub const kCVPixelFormatType_TwoComponent32Float: _bindgen_ty_1462 = 843264102;
pub const kCVPixelFormatType_64RGBAHalf: _bindgen_ty_1462 = 1380411457;
pub const kCVPixelFormatType_128RGBAFloat: _bindgen_ty_1462 = 1380410945;
pub const kCVPixelFormatType_14Bayer_GRBG: _bindgen_ty_1462 = 1735549492;
pub const kCVPixelFormatType_14Bayer_RGGB: _bindgen_ty_1462 = 1919379252;
pub const kCVPixelFormatType_14Bayer_BGGR: _bindgen_ty_1462 = 1650943796;
pub const kCVPixelFormatType_14Bayer_GBRG: _bindgen_ty_1462 = 1734505012;
pub const kCVPixelFormatType_DisparityFloat16: _bindgen_ty_1462 = 1751411059;
pub const kCVPixelFormatType_DisparityFloat32: _bindgen_ty_1462 = 1717856627;
pub const kCVPixelFormatType_DepthFloat16: _bindgen_ty_1462 = 1751410032;
pub const kCVPixelFormatType_DepthFloat32: _bindgen_ty_1462 = 1717855600;
pub const kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange: _bindgen_ty_1462 = 2016686640;
pub const kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange: _bindgen_ty_1462 = 2016686642;
pub const kCVPixelFormatType_444YpCbCr10BiPlanarVideoRange: _bindgen_ty_1462 = 2016687156;
pub const kCVPixelFormatType_420YpCbCr10BiPlanarFullRange: _bindgen_ty_1462 = 2019963440;
pub const kCVPixelFormatType_422YpCbCr10BiPlanarFullRange: _bindgen_ty_1462 = 2019963442;
pub const kCVPixelFormatType_444YpCbCr10BiPlanarFullRange: _bindgen_ty_1462 = 2019963956;
pub const kCVPixelFormatType_420YpCbCr8VideoRange_8A_TriPlanar: _bindgen_ty_1462 = 1982882104;
pub const kCVPixelFormatType_16VersatileBayer: _bindgen_ty_1462 = 1651519798;
pub const kCVPixelFormatType_96VersatileBayerPacked12: _bindgen_ty_1462 = 1651798066;
pub const kCVPixelFormatType_64RGBA_DownscaledProResRAW: _bindgen_ty_1462 = 1651521076;
pub const kCVPixelFormatType_422YpCbCr16BiPlanarVideoRange: _bindgen_ty_1462 = 1937125938;
pub const kCVPixelFormatType_444YpCbCr16BiPlanarVideoRange: _bindgen_ty_1462 = 1937126452;
pub const kCVPixelFormatType_444YpCbCr16VideoRange_16A_TriPlanar: _bindgen_ty_1462 = 1932812659;
pub const kCVPixelFormatType_30RGBLE_8A_BiPlanar: _bindgen_ty_1462 = 1647534392;
pub type _bindgen_ty_1462 = OSType;
pub const kCVPixelFormatType_Lossless_32BGRA: _bindgen_ty_1463 = 641877825;
pub const kCVPixelFormatType_Lossless_64RGBAHalf: _bindgen_ty_1463 = 642934849;
pub const kCVPixelFormatType_Lossless_420YpCbCr8BiPlanarVideoRange: _bindgen_ty_1463 = 641234480;
pub const kCVPixelFormatType_Lossless_420YpCbCr8BiPlanarFullRange: _bindgen_ty_1463 = 641230384;
pub const kCVPixelFormatType_Lossless_420YpCbCr10PackedBiPlanarVideoRange: _bindgen_ty_1463 =
645428784;
pub const kCVPixelFormatType_Lossless_422YpCbCr10PackedBiPlanarVideoRange: _bindgen_ty_1463 =
645428786;
pub const kCVPixelFormatType_Lossless_420YpCbCr10PackedBiPlanarFullRange: _bindgen_ty_1463 =
645424688;
pub const kCVPixelFormatType_Lossless_30RGBLE_8A_BiPlanar: _bindgen_ty_1463 = 643969848;
pub const kCVPixelFormatType_Lossless_30RGBLEPackedWideGamut: _bindgen_ty_1463 = 645346162;
pub type _bindgen_ty_1463 = OSType;
pub const kCVPixelFormatType_Lossy_32BGRA: _bindgen_ty_1464 = 759318337;
pub const kCVPixelFormatType_Lossy_420YpCbCr8BiPlanarVideoRange: _bindgen_ty_1464 = 758674992;
pub const kCVPixelFormatType_Lossy_420YpCbCr8BiPlanarFullRange: _bindgen_ty_1464 = 758670896;
pub const kCVPixelFormatType_Lossy_420YpCbCr10PackedBiPlanarVideoRange: _bindgen_ty_1464 =
762869296;
pub const kCVPixelFormatType_Lossy_422YpCbCr10PackedBiPlanarVideoRange: _bindgen_ty_1464 =
762869298;
pub type _bindgen_ty_1464 = OSType;
pub type CMVideoCodecType = FourCharCode;
pub const kCMVideoCodecType_422YpCbCr8: _bindgen_ty_1474 = 846624121;
pub const kCMVideoCodecType_Animation: _bindgen_ty_1474 = 1919706400;
pub const kCMVideoCodecType_Cinepak: _bindgen_ty_1474 = 1668704612;
pub const kCMVideoCodecType_JPEG: _bindgen_ty_1474 = 1785750887;
pub const kCMVideoCodecType_JPEG_OpenDML: _bindgen_ty_1474 = 1684890161;
pub const kCMVideoCodecType_JPEG_XL: _bindgen_ty_1474 = 1786276963;
pub const kCMVideoCodecType_SorensonVideo: _bindgen_ty_1474 = 1398165809;
pub const kCMVideoCodecType_SorensonVideo3: _bindgen_ty_1474 = 1398165811;
pub const kCMVideoCodecType_H263: _bindgen_ty_1474 = 1748121139;
pub const kCMVideoCodecType_H264: _bindgen_ty_1474 = 1635148593;
pub const kCMVideoCodecType_HEVC: _bindgen_ty_1474 = 1752589105;
pub const kCMVideoCodecType_HEVCWithAlpha: _bindgen_ty_1474 = 1836415073;
pub const kCMVideoCodecType_DolbyVisionHEVC: _bindgen_ty_1474 = 1685481521;
pub const kCMVideoCodecType_MPEG4Video: _bindgen_ty_1474 = 1836070006;
pub const kCMVideoCodecType_MPEG2Video: _bindgen_ty_1474 = 1836069494;
pub const kCMVideoCodecType_MPEG1Video: _bindgen_ty_1474 = 1836069238;
pub const kCMVideoCodecType_VP9: _bindgen_ty_1474 = 1987063865;
pub const kCMVideoCodecType_DVCNTSC: _bindgen_ty_1474 = 1685480224;
pub const kCMVideoCodecType_DVCPAL: _bindgen_ty_1474 = 1685480304;
pub const kCMVideoCodecType_DVCProPAL: _bindgen_ty_1474 = 1685483632;
pub const kCMVideoCodecType_DVCPro50NTSC: _bindgen_ty_1474 = 1685468526;
pub const kCMVideoCodecType_DVCPro50PAL: _bindgen_ty_1474 = 1685468528;
pub const kCMVideoCodecType_DVCPROHD720p60: _bindgen_ty_1474 = 1685481584;
pub const kCMVideoCodecType_DVCPROHD720p50: _bindgen_ty_1474 = 1685481585;
pub const kCMVideoCodecType_DVCPROHD1080i60: _bindgen_ty_1474 = 1685481526;
pub const kCMVideoCodecType_DVCPROHD1080i50: _bindgen_ty_1474 = 1685481525;
pub const kCMVideoCodecType_DVCPROHD1080p30: _bindgen_ty_1474 = 1685481523;
pub const kCMVideoCodecType_DVCPROHD1080p25: _bindgen_ty_1474 = 1685481522;
pub const kCMVideoCodecType_AppleProRes4444XQ: _bindgen_ty_1474 = 1634743416;
pub const kCMVideoCodecType_AppleProRes4444: _bindgen_ty_1474 = 1634743400;
pub const kCMVideoCodecType_AppleProRes422HQ: _bindgen_ty_1474 = 1634755432;
pub const kCMVideoCodecType_AppleProRes422: _bindgen_ty_1474 = 1634755438;
pub const kCMVideoCodecType_AppleProRes422LT: _bindgen_ty_1474 = 1634755443;
pub const kCMVideoCodecType_AppleProRes422Proxy: _bindgen_ty_1474 = 1634755439;
pub const kCMVideoCodecType_AppleProResRAW: _bindgen_ty_1474 = 1634759278;
pub const kCMVideoCodecType_AppleProResRAWHQ: _bindgen_ty_1474 = 1634759272;
pub const kCMVideoCodecType_DisparityHEVC: _bindgen_ty_1474 = 1684632424;
pub const kCMVideoCodecType_DepthHEVC: _bindgen_ty_1474 = 1684369512;
pub const kCMVideoCodecType_AV1: _bindgen_ty_1474 = 1635135537;
pub type _bindgen_ty_1474 = CMVideoCodecType;
pub type CMTimeCodeFormatType = FourCharCode;
pub const kCMTimeCodeFormatType_TimeCode32: _bindgen_ty_1483 = 1953325924;
pub const kCMTimeCodeFormatType_TimeCode64: _bindgen_ty_1483 = 1952658996;
pub const kCMTimeCodeFormatType_Counter32: _bindgen_ty_1483 = 1668166450;
pub const kCMTimeCodeFormatType_Counter64: _bindgen_ty_1483 = 1668167220;
pub type _bindgen_ty_1483 = CMTimeCodeFormatType;
pub const kCMTimeCodeFlag_DropFrame: _bindgen_ty_1484 = 1;
pub const kCMTimeCodeFlag_24HourMax: _bindgen_ty_1484 = 2;
pub const kCMTimeCodeFlag_NegTimesOK: _bindgen_ty_1484 = 4;
pub type _bindgen_ty_1484 = u32;
unsafe extern "C" {
pub static kCMTimeCodeFormatDescriptionExtension_SourceReferenceName: CFStringRef;
}
unsafe extern "C" {
pub static kCMTimeCodeFormatDescriptionKey_Value: CFStringRef;
}
unsafe extern "C" {
pub static kCMTimeCodeFormatDescriptionKey_LangCode: CFStringRef;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct CMSampleTimingInfo {
pub duration: CMTime,
pub presentationTimeStamp: CMTime,
pub decodeTimeStamp: CMTime,
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_NotSync: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_PartialSync: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_HasRedundantCoding: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_IsDependedOnByOthers: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_DependsOnOthers: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_EarlierDisplayTimesAllowed: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_DisplayImmediately: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_DoNotDisplay: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_HEVCTemporalLevelInfo: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_HEVCTemporalSubLayerAccess: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_HEVCStepwiseTemporalSubLayerAccess: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_HEVCSyncSampleNALUnitType: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_AudioIndependentSampleDecoderRefreshCount: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_CryptorSubsampleAuxiliaryData: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_HDR10PlusPerFrameData: CFStringRef;
}
unsafe extern "C" {
pub static kCMSampleAttachmentKey_PostDecodeProcessingMetadata: CFStringRef;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct CMTimeRange {
pub start: CMTime,
pub duration: CMTime,
}
unsafe extern "C" {
pub static kCMTimeRangeZero: CMTimeRange;
}
unsafe extern "C" {
pub static kCMTimeRangeInvalid: CMTimeRange;
}
unsafe extern "C" {
pub static kCMTimeRangeStartKey: CFStringRef;
}
unsafe extern "C" {
pub static kCMTimeRangeDurationKey: CFStringRef;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct CMTimeMapping {
pub source: CMTimeRange,
pub target: CMTimeRange,
}
unsafe extern "C" {
pub static kCMTimeMappingInvalid: CMTimeMapping;
}
unsafe extern "C" {
pub static kCMTimeMappingSourceKey: CFStringRef;
}
unsafe extern "C" {
pub static kCMTimeMappingTargetKey: CFStringRef;
}
pub const VTEncodeInfoFlags_kVTEncodeInfo_Asynchronous: VTEncodeInfoFlags = 1;
pub const VTEncodeInfoFlags_kVTEncodeInfo_FrameDropped: VTEncodeInfoFlags = 2;
pub type VTEncodeInfoFlags = UInt32;

View File

@@ -2610,9 +2610,8 @@ impl MultiBuffer {
for range in ranges {
let range = range.to_point(&snapshot);
let start = snapshot.point_to_offset(Point::new(range.start.row, 0));
let end = snapshot.point_to_offset(Point::new(range.end.row + 1, 0));
let start = start.saturating_sub_usize(1);
let end = snapshot.len().min(end + 1usize);
let end = (snapshot.point_to_offset(Point::new(range.end.row + 1, 0)) + 1usize)
.min(snapshot.len());
cursor.seek(&start, Bias::Right);
while let Some(item) = cursor.item() {
if *cursor.start() >= end {

File diff suppressed because it is too large Load Diff

View File

@@ -354,6 +354,8 @@ pub mod agent {
ResetAgentZoom,
/// Toggles the utility/agent pane open/closed state.
ToggleAgentPane,
/// Pastes clipboard content without any formatting.
PasteRaw,
]
);
}