.config/hakari.toml π
@@ -34,7 +34,6 @@ workspace-members = [
"zed_extension_api",
# exclude all extensions
- "zed_emmet",
"zed_glsl",
"zed_html",
"zed_proto",
Antonio Scandurra created
.config/hakari.toml | 1
Cargo.lock | 205
Cargo.toml | 19
assets/icons/ai.svg | 2
assets/icons/arrow_circle.svg | 8
assets/icons/arrow_down.svg | 2
assets/icons/arrow_down10.svg | 2
assets/icons/arrow_down_right.svg | 4
assets/icons/arrow_left.svg | 2
assets/icons/arrow_right.svg | 2
assets/icons/arrow_right_left.svg | 8
assets/icons/arrow_up.svg | 2
assets/icons/arrow_up_right.svg | 4
assets/icons/audio_off.svg | 10
assets/icons/audio_on.svg | 6
assets/icons/backspace.svg | 6
assets/icons/bell.svg | 4
assets/icons/bell_dot.svg | 4
assets/icons/bell_off.svg | 6
assets/icons/bell_ring.svg | 8
assets/icons/binary.svg | 2
assets/icons/blocks.svg | 0
assets/icons/bolt_outlined.svg | 2
assets/icons/book.svg | 2
assets/icons/book_copy.svg | 2
assets/icons/chat.svg | 4
assets/icons/check.svg | 2
assets/icons/check_circle.svg | 4
assets/icons/check_double.svg | 4
assets/icons/chevron_down.svg | 2
assets/icons/chevron_left.svg | 2
assets/icons/chevron_right.svg | 2
assets/icons/chevron_up.svg | 2
assets/icons/chevron_up_down.svg | 4
assets/icons/circle_help.svg | 6
assets/icons/close.svg | 2
assets/icons/cloud_download.svg | 2
assets/icons/code.svg | 2
assets/icons/cog.svg | 2
assets/icons/command.svg | 0
assets/icons/control.svg | 2
assets/icons/copilot.svg | 6
assets/icons/copy.svg | 2
assets/icons/countdown_timer.svg | 2
assets/icons/crosshair.svg | 10
assets/icons/cursor_i_beam.svg | 4
assets/icons/dash.svg | 2
assets/icons/database_zap.svg | 2
assets/icons/debug.svg | 20
assets/icons/debug_breakpoint.svg | 2
assets/icons/debug_continue.svg | 2
assets/icons/debug_detach.svg | 2
assets/icons/debug_disabled_breakpoint.svg | 2
assets/icons/debug_disabled_log_breakpoint.svg | 6
assets/icons/debug_ignore_breakpoints.svg | 2
assets/icons/debug_step_back.svg | 2
assets/icons/debug_step_into.svg | 2
assets/icons/debug_step_out.svg | 2
assets/icons/debug_step_over.svg | 2
assets/icons/diff.svg | 2
assets/icons/disconnected.svg | 2
assets/icons/download.svg | 2
assets/icons/envelope.svg | 4
assets/icons/eraser.svg | 2
assets/icons/escape.svg | 2
assets/icons/exit.svg | 6
assets/icons/expand_down.svg | 4
assets/icons/expand_up.svg | 4
assets/icons/expand_vertical.svg | 2
assets/icons/eye.svg | 4
assets/icons/file.svg | 2
assets/icons/file_code.svg | 2
assets/icons/file_diff.svg | 2
assets/icons/file_doc.svg | 6
assets/icons/file_generic.svg | 6
assets/icons/file_git.svg | 8
assets/icons/file_icons/ai.svg | 2
assets/icons/file_icons/audio.svg | 12
assets/icons/file_icons/book.svg | 6
assets/icons/file_icons/bun.svg | 2
assets/icons/file_icons/chevron_down.svg | 2
assets/icons/file_icons/chevron_left.svg | 2
assets/icons/file_icons/chevron_right.svg | 2
assets/icons/file_icons/chevron_up.svg | 2
assets/icons/file_icons/code.svg | 4
assets/icons/file_icons/coffeescript.svg | 2
assets/icons/file_icons/conversations.svg | 2
assets/icons/file_icons/dart.svg | 2
assets/icons/file_icons/database.svg | 6
assets/icons/file_icons/diff.svg | 6
assets/icons/file_icons/eslint.svg | 2
assets/icons/file_icons/file.svg | 6
assets/icons/file_icons/folder.svg | 2
assets/icons/file_icons/folder_open.svg | 4
assets/icons/file_icons/font.svg | 2
assets/icons/file_icons/git.svg | 8
assets/icons/file_icons/gleam.svg | 4
assets/icons/file_icons/graphql.svg | 4
assets/icons/file_icons/hash.svg | 8
assets/icons/file_icons/heroku.svg | 2
assets/icons/file_icons/html.svg | 6
assets/icons/file_icons/image.svg | 6
assets/icons/file_icons/java.svg | 10
assets/icons/file_icons/lock.svg | 2
assets/icons/file_icons/magnifying_glass.svg | 2
assets/icons/file_icons/nix.svg | 12
assets/icons/file_icons/notebook.svg | 10
assets/icons/file_icons/package.svg | 2
assets/icons/file_icons/phoenix.svg | 2
assets/icons/file_icons/plus.svg | 2
assets/icons/file_icons/prettier.svg | 20
assets/icons/file_icons/project.svg | 2
assets/icons/file_icons/python.svg | 4
assets/icons/file_icons/replace.svg | 2
assets/icons/file_icons/replace_next.svg | 2
assets/icons/file_icons/rust.svg | 2
assets/icons/file_icons/scala.svg | 2
assets/icons/file_icons/settings.svg | 0
assets/icons/file_icons/tcl.svg | 2
assets/icons/file_icons/toml.svg | 6
assets/icons/file_icons/video.svg | 4
assets/icons/file_icons/vue.svg | 2
assets/icons/file_lock.svg | 2
assets/icons/file_markdown.svg | 2
assets/icons/file_rust.svg | 2
assets/icons/file_text_outlined.svg | 8
assets/icons/file_toml.svg | 6
assets/icons/file_tree.svg | 2
assets/icons/filter.svg | 2
assets/icons/flame.svg | 2
assets/icons/folder.svg | 2
assets/icons/folder_open.svg | 4
assets/icons/folder_search.svg | 2
assets/icons/font.svg | 2
assets/icons/font_size.svg | 2
assets/icons/font_weight.svg | 2
assets/icons/forward_arrow.svg | 4
assets/icons/git_branch.svg | 2
assets/icons/git_branch_alt.svg | 10
assets/icons/github.svg | 2
assets/icons/hash.svg | 2
assets/icons/history_rerun.svg | 6
assets/icons/image.svg | 2
assets/icons/info.svg | 4
assets/icons/json.svg | 4
assets/icons/keyboard.svg | 2
assets/icons/knockouts/x_fg.svg | 2
assets/icons/library.svg | 8
assets/icons/line_height.svg | 2
assets/icons/list_collapse.svg | 2
assets/icons/list_todo.svg | 2
assets/icons/list_tree.svg | 10
assets/icons/list_x.svg | 10
assets/icons/load_circle.svg | 2
assets/icons/location_edit.svg | 2
assets/icons/lock_outlined.svg | 6
assets/icons/magnifying_glass.svg | 2
assets/icons/maximize.svg | 8
assets/icons/menu.svg | 2
assets/icons/menu_alt.svg | 2
assets/icons/mic.svg | 6
assets/icons/mic_mute.svg | 12
assets/icons/minimize.svg | 8
assets/icons/notepad.svg | 2
assets/icons/option.svg | 4
assets/icons/pencil.svg | 4
assets/icons/person.svg | 2
assets/icons/pin.svg | 1
assets/icons/play_filled.svg | 2
assets/icons/play_outlined.svg | 2
assets/icons/plus.svg | 4
assets/icons/power.svg | 2
assets/icons/public.svg | 2
assets/icons/pull_request.svg | 2
assets/icons/quote.svg | 2
assets/icons/reader.svg | 6
assets/icons/refresh_title.svg | 2
assets/icons/regex.svg | 2
assets/icons/repl_neutral.svg | 8
assets/icons/repl_off.svg | 18
assets/icons/repl_pause.svg | 12
assets/icons/repl_play.svg | 10
assets/icons/replace.svg | 2
assets/icons/replace_next.svg | 2
assets/icons/rerun.svg | 2
assets/icons/return.svg | 4
assets/icons/rotate_ccw.svg | 2
assets/icons/rotate_cw.svg | 2
assets/icons/scissors.svg | 2
assets/icons/screen.svg | 6
assets/icons/select_all.svg | 0
assets/icons/send.svg | 2
assets/icons/server.svg | 8
assets/icons/settings.svg | 0
assets/icons/shield_check.svg | 4
assets/icons/shift.svg | 2
assets/icons/slash.svg | 2
assets/icons/sliders.svg | 12
assets/icons/space.svg | 2
assets/icons/sparkle.svg | 2
assets/icons/split.svg | 4
assets/icons/split_alt.svg | 2
assets/icons/square_dot.svg | 2
assets/icons/square_minus.svg | 4
assets/icons/square_plus.svg | 6
assets/icons/star.svg | 0
assets/icons/star_filled.svg | 0
assets/icons/stop.svg | 2
assets/icons/swatch_book.svg | 2
assets/icons/tab.svg | 6
assets/icons/terminal_alt.svg | 6
assets/icons/text_snippet.svg | 2
assets/icons/text_thread.svg | 10
assets/icons/thread.svg | 2
assets/icons/thread_from_summary.svg | 8
assets/icons/thumbs_down.svg | 2
assets/icons/thumbs_up.svg | 2
assets/icons/todo_complete.svg | 2
assets/icons/todo_pending.svg | 16
assets/icons/todo_progress.svg | 18
assets/icons/tool_copy.svg | 4
assets/icons/tool_delete_file.svg | 6
assets/icons/tool_diagnostics.svg | 6
assets/icons/tool_folder.svg | 2
assets/icons/tool_hammer.svg | 6
assets/icons/tool_notification.svg | 4
assets/icons/tool_pencil.svg | 4
assets/icons/tool_read.svg | 10
assets/icons/tool_regex.svg | 2
assets/icons/tool_search.svg | 4
assets/icons/tool_terminal.svg | 6
assets/icons/tool_think.svg | 2
assets/icons/tool_web.svg | 6
assets/icons/trash.svg | 6
assets/icons/undo.svg | 2
assets/icons/user_check.svg | 2
assets/icons/user_group.svg | 6
assets/icons/user_round_pen.svg | 2
assets/icons/warning.svg | 2
assets/icons/whole_word.svg | 0
assets/icons/x_circle.svg | 2
assets/icons/zed_assistant.svg | 2
assets/icons/zed_burn_mode.svg | 2
assets/icons/zed_burn_mode_on.svg | 2
assets/icons/zed_mcp_custom.svg | 2
assets/icons/zed_mcp_extension.svg | 2
assets/icons/zed_predict.svg | 6
assets/icons/zed_predict_down.svg | 6
assets/icons/zed_predict_error.svg | 4
assets/icons/zed_predict_up.svg | 6
assets/settings/default.json | 13
crates/acp_thread/src/acp_thread.rs | 132
crates/acp_thread/src/connection.rs | 4
crates/agent/src/thread.rs | 20
crates/agent/src/tool_use.rs | 6
crates/agent2/src/agent.rs | 11
crates/agent2/src/tests/mod.rs | 201
crates/agent2/src/thread.rs | 327
crates/agent2/src/tools/edit_file_tool.rs | 12
crates/agent_servers/src/acp/v0.rs | 6
crates/agent_servers/src/acp/v1.rs | 25
crates/agent_servers/src/claude.rs | 41
crates/agent_servers/src/claude/mcp_server.rs | 4
crates/agent_servers/src/e2e_tests.rs | 12
crates/agent_settings/src/agent_settings.rs | 2
crates/agent_ui/Cargo.toml | 1
crates/agent_ui/src/acp/completion_provider.rs | 1083
crates/agent_ui/src/acp/entry_view_state.rs | 387
crates/agent_ui/src/acp/message_editor.rs | 956
crates/agent_ui/src/acp/thread_view.rs | 689
crates/agent_ui/src/active_thread.rs | 2
crates/agent_ui/src/agent_configuration.rs | 4
crates/agent_ui/src/agent_panel.rs | 88
crates/agent_ui/src/agent_ui.rs | 7
crates/agent_ui/src/context_picker.rs | 43
crates/agent_ui/src/inline_assistant.rs | 2
crates/agent_ui/src/language_model_selector.rs | 6
crates/agent_ui/src/message_editor.rs | 33
crates/agent_ui/src/profile_selector.rs | 52
crates/agent_ui/src/slash_command_settings.rs | 11
crates/agent_ui/src/text_thread_editor.rs | 86
crates/agent_ui/src/thread_history.rs | 1
crates/assistant_slash_commands/Cargo.toml | 1
crates/assistant_slash_commands/src/assistant_slash_commands.rs | 2
crates/assistant_slash_commands/src/docs_command.rs | 543
crates/client/src/test.rs | 67
crates/client/src/user.rs | 34
crates/collab/Cargo.toml | 8
crates/collab/k8s/collab.template.yml | 6
crates/collab/migrations.sqlite/20221109000000_test_schema.sql | 61
crates/collab/migrations/20250816124707_make_admin_required_on_users.sql | 2
crates/collab/migrations/20250816133027_add_orb_customer_id_to_billing_customers.sql | 2
crates/collab/migrations/20250816135346_drop_rate_buckets_table.sql | 1
crates/collab/src/api.rs | 194
crates/collab/src/api/billing.rs | 59
crates/collab/src/api/events.rs | 166
crates/collab/src/db.rs | 5
crates/collab/src/db/ids.rs | 3
crates/collab/src/db/queries.rs | 4
crates/collab/src/db/queries/billing_customers.rs | 100
crates/collab/src/db/queries/billing_preferences.rs | 17
crates/collab/src/db/queries/billing_subscriptions.rs | 158
crates/collab/src/db/queries/processed_stripe_events.rs | 69
crates/collab/src/db/tables.rs | 4
crates/collab/src/db/tables/billing_customer.rs | 41
crates/collab/src/db/tables/billing_preference.rs | 32
crates/collab/src/db/tables/billing_subscription.rs | 176
crates/collab/src/db/tables/processed_stripe_event.rs | 16
crates/collab/src/db/tables/user.rs | 8
crates/collab/src/db/tests.rs | 1
crates/collab/src/db/tests/processed_stripe_event_tests.rs | 38
crates/collab/src/lib.rs | 61
crates/collab/src/llm.rs | 11
crates/collab/src/llm/db.rs | 74
crates/collab/src/llm/db/ids.rs | 11
crates/collab/src/llm/db/queries.rs | 5
crates/collab/src/llm/db/queries/providers.rs | 134
crates/collab/src/llm/db/queries/subscription_usages.rs | 38
crates/collab/src/llm/db/queries/usages.rs | 44
crates/collab/src/llm/db/seed.rs | 45
crates/collab/src/llm/db/tables.rs | 6
crates/collab/src/llm/db/tables/model.rs | 48
crates/collab/src/llm/db/tables/provider.rs | 25
crates/collab/src/llm/db/tables/subscription_usage.rs | 22
crates/collab/src/llm/db/tables/subscription_usage_meter.rs | 55
crates/collab/src/llm/db/tables/usage.rs | 52
crates/collab/src/llm/db/tables/usage_measure.rs | 36
crates/collab/src/llm/db/tests.rs | 107
crates/collab/src/llm/db/tests/provider_tests.rs | 31
crates/collab/src/llm/token.rs | 146
crates/collab/src/main.rs | 17
crates/collab/src/rpc.rs | 410
crates/collab/src/rpc/connection_pool.rs | 14
crates/collab/src/stripe_billing.rs | 156
crates/collab/src/stripe_client.rs | 285
crates/collab/src/stripe_client/fake_stripe_client.rs | 247
crates/collab/src/stripe_client/real_stripe_client.rs | 612
crates/collab/src/tests.rs | 2
crates/collab/src/tests/stripe_billing_tests.rs | 123
crates/collab/src/tests/test_server.rs | 6
crates/collab_ui/src/chat_panel.rs | 2
crates/collab_ui/src/collab_panel.rs | 8
crates/collab_ui/src/collab_panel/channel_modal.rs | 2
crates/collab_ui/src/notification_panel.rs | 4
crates/crashes/Cargo.toml | 2
crates/crashes/src/crashes.rs | 157
crates/debugger_ui/src/session/running.rs | 4
crates/diagnostics/src/diagnostics_tests.rs | 10
crates/edit_prediction_button/src/edit_prediction_button.rs | 6
crates/editor/src/editor.rs | 78
crates/editor/src/editor_settings.rs | 8
crates/editor/src/editor_tests.rs | 15
crates/editor/src/element.rs | 2
crates/editor/src/hover_popover.rs | 2
crates/editor/src/items.rs | 6
crates/editor/src/linked_editing_ranges.rs | 2
crates/editor/src/signature_help.rs | 2
crates/editor/src/test/editor_test_context.rs | 20
crates/extension/src/extension_host_proxy.rs | 34
crates/extension/src/extension_manifest.rs | 7
crates/extension_cli/src/main.rs | 4
crates/extension_host/benches/extension_compilation_benchmark.rs | 1
crates/extension_host/src/capability_granter.rs | 1
crates/extension_host/src/extension_host.rs | 15
crates/extension_host/src/extension_store_test.rs | 3
crates/extensions_ui/src/extensions_ui.rs | 2
crates/git_ui/src/conflict_view.rs | 4
crates/git_ui/src/git_panel.rs | 2
crates/git_ui/src/git_ui.rs | 87
crates/go_to_line/src/cursor_position.rs | 9
crates/gpui/examples/input.rs | 4
crates/gpui/src/app.rs | 3
crates/gpui/src/platform.rs | 6
crates/gpui/src/platform/linux/platform.rs | 29
crates/gpui/src/platform/mac/metal_renderer.rs | 9
crates/gpui/src/platform/mac/platform.rs | 12
crates/gpui/src/platform/test/platform.rs | 1
crates/gpui/src/platform/windows/platform.rs | 20
crates/icons/README.md | 2
crates/indexed_docs/Cargo.toml | 38
crates/indexed_docs/LICENSE-GPL | 1
crates/indexed_docs/src/extension_indexed_docs_provider.rs | 81
crates/indexed_docs/src/indexed_docs.rs | 16
crates/indexed_docs/src/providers.rs | 1
crates/indexed_docs/src/providers/rustdoc.rs | 291
crates/indexed_docs/src/providers/rustdoc/item.rs | 82
crates/indexed_docs/src/providers/rustdoc/popular_crates.txt | 252
crates/indexed_docs/src/providers/rustdoc/to_markdown.rs | 618
crates/indexed_docs/src/registry.rs | 62
crates/indexed_docs/src/store.rs | 346
crates/language_tools/src/lsp_log.rs | 2
crates/language_tools/src/lsp_tool.rs | 2
crates/language_tools/src/syntax_tree_view.rs | 2
crates/onboarding/src/welcome.rs | 2
crates/outline_panel/src/outline_panel.rs | 82
crates/project_panel/src/project_panel.rs | 83
crates/proto/proto/ai.proto | 8
crates/proto/proto/app.proto | 62
crates/proto/proto/zed.proto | 15
crates/proto/src/proto.rs | 11
crates/recent_projects/src/remote_servers.rs | 11
crates/remote/src/ssh_session.rs | 52
crates/remote_server/src/unix.rs | 93
crates/repl/src/session.rs | 2
crates/search/src/buffer_search.rs | 53
crates/search/src/project_search.rs | 59
crates/search/src/search.rs | 55
crates/search/src/search_bar.rs | 12
crates/settings_ui/src/keybindings.rs | 1
crates/storybook/src/stories/indent_guides.rs | 2
crates/telemetry_events/src/telemetry_events.rs | 108
crates/terminal_view/src/terminal_panel.rs | 4
crates/terminal_view/src/terminal_view.rs | 2
crates/theme/src/settings.rs | 75
crates/theme/src/theme.rs | 8
crates/vim/src/command.rs | 4
crates/vim/src/mode_indicator.rs | 4
crates/vim/src/normal/search.rs | 2
crates/vim/src/vim.rs | 2
crates/workspace/src/dock.rs | 2
crates/workspace/src/item.rs | 11
crates/workspace/src/notifications.rs | 2
crates/workspace/src/pane.rs | 20
crates/workspace/src/workspace.rs | 5
crates/zed/src/main.rs | 11
crates/zed/src/reliability.rs | 264
crates/zed/src/zed.rs | 62
crates/zed/src/zed/edit_prediction_registry.rs | 3
docs/src/configuring-zed.md | 1
docs/src/languages/emmet.md | 2
docs/src/linux.md | 1
docs/src/visual-customization.md | 4
extensions/emmet/.gitignore | 3
extensions/emmet/Cargo.toml | 16
extensions/emmet/LICENSE-APACHE | 1
extensions/emmet/extension.toml | 24
extensions/emmet/src/emmet.rs | 106
typos.toml | 3
438 files changed, 3,726 insertions(+), 10,116 deletions(-)
@@ -34,7 +34,6 @@ workspace-members = [
"zed_extension_api",
# exclude all extensions
- "zed_emmet",
"zed_glsl",
"zed_html",
"zed_proto",
@@ -173,9 +173,9 @@ dependencies = [
[[package]]
name = "agent-client-protocol"
-version = "0.0.24"
+version = "0.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd68bbbef8e424fb8a605c5f0b00c360f682c4528b0a5feb5ec928aaf5ce28e"
+checksum = "160971bb53ca0b2e70ebc857c21e24eb448745f1396371015f4c59e9a9e51ed0"
dependencies = [
"anyhow",
"futures 0.3.31",
@@ -353,7 +353,6 @@ dependencies = [
"gpui",
"html_to_markdown",
"http_client",
- "indexed_docs",
"indoc",
"inventory",
"itertools 0.14.0",
@@ -878,7 +877,6 @@ dependencies = [
"gpui",
"html_to_markdown",
"http_client",
- "indexed_docs",
"language",
"pretty_assertions",
"project",
@@ -1268,26 +1266,6 @@ dependencies = [
"syn 2.0.101",
]
-[[package]]
-name = "async-stripe"
-version = "0.40.0"
-source = "git+https://github.com/zed-industries/async-stripe?rev=3672dd4efb7181aa597bf580bf5a2f5d23db6735#3672dd4efb7181aa597bf580bf5a2f5d23db6735"
-dependencies = [
- "chrono",
- "futures-util",
- "http-types",
- "hyper 0.14.32",
- "hyper-rustls 0.24.2",
- "serde",
- "serde_json",
- "serde_path_to_error",
- "serde_qs 0.10.1",
- "smart-default 0.6.0",
- "smol_str 0.1.24",
- "thiserror 1.0.69",
- "tokio",
-]
-
[[package]]
name = "async-tar"
version = "0.5.0"
@@ -2089,12 +2067,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
-[[package]]
-name = "base64"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
-
[[package]]
name = "base64"
version = "0.21.7"
@@ -3287,7 +3259,6 @@ dependencies = [
"anyhow",
"assistant_context",
"assistant_slash_command",
- "async-stripe",
"async-trait",
"async-tungstenite",
"audio",
@@ -3303,7 +3274,6 @@ dependencies = [
"chrono",
"client",
"clock",
- "cloud_llm_client",
"collab_ui",
"collections",
"command_palette_hooks",
@@ -3314,7 +3284,6 @@ dependencies = [
"dap_adapters",
"dashmap 6.1.0",
"debugger_ui",
- "derive_more 0.99.19",
"editor",
"envy",
"extension",
@@ -3330,7 +3299,6 @@ dependencies = [
"http_client",
"hyper 0.14.32",
"indoc",
- "jsonwebtoken",
"language",
"language_model",
"livekit_api",
@@ -3376,7 +3344,6 @@ dependencies = [
"telemetry_events",
"text",
"theme",
- "thiserror 2.0.12",
"time",
"tokio",
"toml 0.8.20",
@@ -3878,7 +3845,7 @@ dependencies = [
"rustc-hash 1.1.0",
"rustybuzz 0.14.1",
"self_cell",
- "smol_str 0.2.2",
+ "smol_str",
"swash",
"sys-locale",
"ttf-parser 0.21.1",
@@ -4075,6 +4042,8 @@ dependencies = [
"minidumper",
"paths",
"release_channel",
+ "serde",
+ "serde_json",
"smol",
"workspace-hack",
]
@@ -6382,17 +6351,6 @@ dependencies = [
"windows-targets 0.48.5",
]
-[[package]]
-name = "getrandom"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.9.0+wasi-snapshot-preview1",
-]
-
[[package]]
name = "getrandom"
version = "0.2.15"
@@ -7996,27 +7954,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
-[[package]]
-name = "http-types"
-version = "2.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
-dependencies = [
- "anyhow",
- "async-channel 1.9.0",
- "base64 0.13.1",
- "futures-lite 1.13.0",
- "http 0.2.12",
- "infer",
- "pin-project-lite",
- "rand 0.7.3",
- "serde",
- "serde_json",
- "serde_qs 0.8.5",
- "serde_urlencoded",
- "url",
-]
-
[[package]]
name = "http_client"
version = "0.1.0"
@@ -8450,34 +8387,6 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
-[[package]]
-name = "indexed_docs"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "cargo_metadata",
- "collections",
- "derive_more 0.99.19",
- "extension",
- "fs",
- "futures 0.3.31",
- "fuzzy",
- "gpui",
- "heed",
- "html_to_markdown",
- "http_client",
- "indexmap",
- "indoc",
- "parking_lot",
- "paths",
- "pretty_assertions",
- "serde",
- "strum 0.27.1",
- "util",
- "workspace-hack",
-]
-
[[package]]
name = "indexmap"
version = "2.9.0"
@@ -8495,12 +8404,6 @@ version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
-[[package]]
-name = "infer"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
-
[[package]]
name = "inherent"
version = "1.0.12"
@@ -10277,7 +10180,7 @@ dependencies = [
"num-traits",
"range-map",
"scroll",
- "smart-default 0.7.1",
+ "smart-default",
]
[[package]]
@@ -13151,19 +13054,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
-[[package]]
-name = "rand"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
-dependencies = [
- "getrandom 0.1.16",
- "libc",
- "rand_chacha 0.2.2",
- "rand_core 0.5.1",
- "rand_hc",
-]
-
[[package]]
name = "rand"
version = "0.8.5"
@@ -13185,16 +13075,6 @@ dependencies = [
"rand_core 0.9.3",
]
-[[package]]
-name = "rand_chacha"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.5.1",
-]
-
[[package]]
name = "rand_chacha"
version = "0.3.1"
@@ -13215,15 +13095,6 @@ dependencies = [
"rand_core 0.9.3",
]
-[[package]]
-name = "rand_core"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
-dependencies = [
- "getrandom 0.1.16",
-]
-
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -13242,15 +13113,6 @@ dependencies = [
"getrandom 0.3.2",
]
-[[package]]
-name = "rand_hc"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
-dependencies = [
- "rand_core 0.5.1",
-]
-
[[package]]
name = "range-map"
version = "0.2.0"
@@ -14905,28 +14767,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "serde_qs"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
-dependencies = [
- "percent-encoding",
- "serde",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "serde_qs"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa"
-dependencies = [
- "percent-encoding",
- "serde",
- "thiserror 1.0.69",
-]
-
[[package]]
name = "serde_repr"
version = "0.1.20"
@@ -15303,17 +15143,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "smart-default"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
[[package]]
name = "smart-default"
version = "0.7.1"
@@ -15342,15 +15171,6 @@ dependencies = [
"futures-lite 2.6.0",
]
-[[package]]
-name = "smol_str"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "smol_str"
version = "0.2.2"
@@ -18199,12 +18019,6 @@ dependencies = [
"tracing",
]
-[[package]]
-name = "wasi"
-version = "0.9.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
-
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -20682,13 +20496,6 @@ dependencies = [
"workspace-hack",
]
-[[package]]
-name = "zed_emmet"
-version = "0.0.6"
-dependencies = [
- "zed_extension_api 0.1.0",
-]
-
[[package]]
name = "zed_extension_api"
version = "0.1.0"
@@ -81,7 +81,6 @@ members = [
"crates/http_client_tls",
"crates/icons",
"crates/image_viewer",
- "crates/indexed_docs",
"crates/edit_prediction",
"crates/edit_prediction_button",
"crates/inspector_ui",
@@ -199,7 +198,6 @@ members = [
# Extensions
#
- "extensions/emmet",
"extensions/glsl",
"extensions/html",
"extensions/proto",
@@ -306,7 +304,6 @@ http_client = { path = "crates/http_client" }
http_client_tls = { path = "crates/http_client_tls" }
icons = { path = "crates/icons" }
image_viewer = { path = "crates/image_viewer" }
-indexed_docs = { path = "crates/indexed_docs" }
edit_prediction = { path = "crates/edit_prediction" }
edit_prediction_button = { path = "crates/edit_prediction_button" }
inspector_ui = { path = "crates/inspector_ui" }
@@ -426,7 +423,7 @@ zlog_settings = { path = "crates/zlog_settings" }
#
agentic-coding-protocol = "0.0.10"
-agent-client-protocol = "0.0.24"
+agent-client-protocol = "0.0.26"
aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
any_vec = "0.14"
@@ -667,20 +664,6 @@ workspace-hack = "0.1.0"
yawc = { git = "https://github.com/deviant-forks/yawc", rev = "1899688f3e69ace4545aceb97b2a13881cf26142" }
zstd = "0.11"
-[workspace.dependencies.async-stripe]
-git = "https://github.com/zed-industries/async-stripe"
-rev = "3672dd4efb7181aa597bf580bf5a2f5d23db6735"
-default-features = false
-features = [
- "runtime-tokio-hyper-rustls",
- "billing",
- "checkout",
- "events",
- # The features below are only enabled to get the `events` feature to build.
- "chrono",
- "connect",
-]
-
[workspace.dependencies.windows]
version = "0.61"
features = [
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M10.5 8.75V10.5C8.43097 10.5 7.56903 10.5 5.5 10.5V10L10.5 6V5.5H5.5V7.25" stroke="black" stroke-width="1.5"/>
+ <path d="M10.5 8.75V10.5C8.43097 10.5 7.56903 10.5 5.5 10.5V10L10.5 6V5.5H5.5V7.25" stroke="black" stroke-width="1.2"/>
<path d="M1.5 8.5C1.77614 8.5 2 8.27614 2 8C2 7.72386 1.77614 7.5 1.5 7.5C1.22386 7.5 1 7.72386 1 8C1 8.27614 1.22386 8.5 1.5 8.5Z" fill="black"/>
<path d="M2.49976 6.33002C2.7759 6.33002 2.99976 6.10616 2.99976 5.83002C2.99976 5.55387 2.7759 5.33002 2.49976 5.33002C2.22361 5.33002 1.99976 5.55387 1.99976 5.83002C1.99976 6.10616 2.22361 6.33002 2.49976 6.33002Z" fill="black"/>
<path d="M2.49976 10.66C2.7759 10.66 2.99976 10.4361 2.99976 10.16C2.99976 9.88383 2.7759 9.65997 2.49976 9.65997C2.22361 9.65997 1.99976 9.88383 1.99976 10.16C1.99976 10.4361 2.22361 10.66 2.49976 10.66Z" fill="black"/>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.8989 5.77778L11.6434 4.52222C10.6384 3.55068 9.29673 3.00526 7.89893 3C6.57285 3 5.30103 3.52678 4.36343 4.46447C3.78887 5.03901 3.36856 5.73897 3.12921 6.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.8989 3V5.77778H10.1211" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3.1012 10.2222L4.3568 11.4778C5.3618 12.4493 6.70342 12.9947 8.10122 13C9.42731 13 10.6991 12.4732 11.6368 11.5355C12.2163 10.956 12.6389 10.2487 12.8772 9.47994" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.87891 10.2222H3.10111V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.8989 5.77778L11.6434 4.52222C10.6384 3.55068 9.29673 3.00526 7.89893 3C6.57285 3 5.30103 3.52678 4.36343 4.46447C3.78887 5.03901 3.36856 5.73897 3.12921 6.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.8989 3V5.77778H10.1211" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.1012 10.2222L4.3568 11.4778C5.3618 12.4493 6.70342 12.9947 8.10122 13C9.42731 13 10.6991 12.4732 11.6368 11.5355C12.2163 10.956 12.6389 10.2487 12.8772 9.47994" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.87891 10.2222H3.10111V13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 13L12.5 8.5M8 13L3.5 8.5M8 13V3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 13L12.5 8.5M8 13L3.5 8.5M8 13V3" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m2.5 10.667 2.667 2.666 2.666-2.666M5.167 13.333V2.667M11.833 6.667v-4H10.5M10.5 6.667h2.667M13.167 10.667a1.333 1.333 0 0 0-2.667 0V12a1.333 1.333 0 0 0 2.667 0v-1.333Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="m2.5 10.667 2.667 2.666 2.666-2.666M5.167 13.333V2.667M11.833 6.667v-4H10.5M10.5 6.667h2.667M13.167 10.667a1.333 1.333 0 0 0-2.667 0V12a1.333 1.333 0 0 0 2.667 0v-1.333Z"/></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.25 4.25L11.125 11.125" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.75 4.25006V11.7501H4.25" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.25 4.25L11.125 11.125" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.75 4.25006V11.7501H4.25" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3.5 7.50001L8 3M3.5 7.50001L8 12M3.5 7.50001H12.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.5 7.50001L8 3M3.5 7.50001L8 12M3.5 7.50001H12.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.5 8L8 12.5M12.5 8L8 3.5M12.5 8H3.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5 8L8 12.5M12.5 8L8 3.5M12.5 8H3.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11 2L13 4.5L11 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.5 4.5H2.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5 14L3 11.5L5 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3 11.5H13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11 2L13 4.5L11 7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5 4.5H2.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 14L3 11.5L5 9" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 11.5H13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 3L12.5 7.5M8 3L3.5 7.5M8 3V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 3L12.5 7.5M8 3L3.5 7.5M8 3V13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.5 11.5L11.5 4.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4.5 4.5L11.5 4.5L11.5 11.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.5 11.5L11.5 4.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.5 4.5L11.5 4.5L11.5 11.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10.6667 6C11.003 6.44823 11.2208 6.97398 11.3001 7.52867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.9094 3.75732C13.7621 4.6095 14.3383 5.69876 14.5629 6.88315C14.7875 8.06754 14.6502 9.29213 14.1688 10.3973" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M2.66675 2L13.6667 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.33333 4.66669L4.942 5.05802C4.85494 5.1456 4.75136 5.21504 4.63726 5.2623C4.52317 5.30957 4.40083 5.33372 4.27733 5.33335H2.66667C2.48986 5.33335 2.32029 5.40359 2.19526 5.52862C2.07024 5.65364 2 5.82321 2 6.00002V10C2 10.1768 2.07024 10.3464 2.19526 10.4714C2.32029 10.5964 2.48986 10.6667 2.66667 10.6667H4.27733C4.40083 10.6663 4.52317 10.6905 4.63726 10.7377C4.75136 10.785 4.85494 10.8544 4.942 10.942L7.19733 13.198C7.26307 13.2639 7.34687 13.3088 7.43813 13.3269C7.52939 13.3451 7.62399 13.3358 7.70995 13.3002C7.79591 13.2646 7.86936 13.2042 7.921 13.1268C7.97263 13.0494 8.00013 12.9584 8 12.8654V7.33335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.21875 2.78136C7.28267 2.71719 7.36421 2.67345 7.45303 2.65568C7.54184 2.63791 7.63393 2.64691 7.71762 2.68154C7.80132 2.71618 7.87284 2.77488 7.92312 2.85022C7.97341 2.92555 8.0002 3.01412 8.00008 3.10469V3.56202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.6667 6C11.003 6.44823 11.2208 6.97398 11.3001 7.52867" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.9094 3.75732C13.7621 4.6095 14.3383 5.69876 14.5629 6.88315C14.7875 8.06754 14.6502 9.29213 14.1688 10.3973" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.66675 2L13.6667 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.33333 4.66669L4.942 5.05802C4.85494 5.1456 4.75136 5.21504 4.63726 5.2623C4.52317 5.30957 4.40083 5.33372 4.27733 5.33335H2.66667C2.48986 5.33335 2.32029 5.40359 2.19526 5.52862C2.07024 5.65364 2 5.82321 2 6.00002V10C2 10.1768 2.07024 10.3464 2.19526 10.4714C2.32029 10.5964 2.48986 10.6667 2.66667 10.6667H4.27733C4.40083 10.6663 4.52317 10.6905 4.63726 10.7377C4.75136 10.785 4.85494 10.8544 4.942 10.942L7.19733 13.198C7.26307 13.2639 7.34687 13.3088 7.43813 13.3269C7.52939 13.3451 7.62399 13.3358 7.70995 13.3002C7.79591 13.2646 7.86936 13.2042 7.921 13.1268C7.97263 13.0494 8.00013 12.9584 8 12.8654V7.33335" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.21875 2.78136C7.28267 2.71719 7.36421 2.67345 7.45303 2.65568C7.54184 2.63791 7.63393 2.64691 7.71762 2.68154C7.80132 2.71618 7.87284 2.77488 7.92312 2.85022C7.97341 2.92555 8.0002 3.01412 8.00008 3.10469V3.56202" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 3.13467C7.99987 3.04181 7.97223 2.95107 7.92057 2.8739C7.86892 2.79674 7.79557 2.7366 7.70977 2.70108C7.62397 2.66557 7.52958 2.65626 7.43849 2.67434C7.34741 2.69242 7.26373 2.73707 7.198 2.80266L4.942 5.058C4.85494 5.14558 4.75136 5.21502 4.63726 5.26228C4.52317 5.30954 4.40083 5.33369 4.27733 5.33333H2.66667C2.48986 5.33333 2.32029 5.40357 2.19526 5.52859C2.07024 5.65362 2 5.82319 2 6V10C2 10.1768 2.07024 10.3464 2.19526 10.4714C2.32029 10.5964 2.48986 10.6667 2.66667 10.6667H4.27733C4.40083 10.6663 4.52317 10.6905 4.63726 10.7377C4.75136 10.785 4.85494 10.8544 4.942 10.942L7.19733 13.198C7.26307 13.2639 7.34687 13.3087 7.43813 13.3269C7.52939 13.3451 7.62399 13.3358 7.70995 13.3002C7.79591 13.2645 7.86936 13.2042 7.921 13.1268C7.97263 13.0494 8.00013 12.9584 8 12.8653V3.13467Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.6667 6C11.0995 6.57699 11.3334 7.27877 11.3334 8C11.3334 8.72123 11.0995 9.42301 10.6667 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.9094 12.2427C13.4666 11.6855 13.9085 11.0241 14.2101 10.2961C14.5116 9.56815 14.6668 8.78793 14.6668 7.99999C14.6668 7.21205 14.5116 6.43183 14.2101 5.70387C13.9085 4.97591 13.4666 4.31448 12.9094 3.75732" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 3.13467C7.99987 3.04181 7.97223 2.95107 7.92057 2.8739C7.86892 2.79674 7.79557 2.7366 7.70977 2.70108C7.62397 2.66557 7.52958 2.65626 7.43849 2.67434C7.34741 2.69242 7.26373 2.73707 7.198 2.80266L4.942 5.058C4.85494 5.14558 4.75136 5.21502 4.63726 5.26228C4.52317 5.30954 4.40083 5.33369 4.27733 5.33333H2.66667C2.48986 5.33333 2.32029 5.40357 2.19526 5.52859C2.07024 5.65362 2 5.82319 2 6V10C2 10.1768 2.07024 10.3464 2.19526 10.4714C2.32029 10.5964 2.48986 10.6667 2.66667 10.6667H4.27733C4.40083 10.6663 4.52317 10.6905 4.63726 10.7377C4.75136 10.785 4.85494 10.8544 4.942 10.942L7.19733 13.198C7.26307 13.2639 7.34687 13.3087 7.43813 13.3269C7.52939 13.3451 7.62399 13.3358 7.70995 13.3002C7.79591 13.2645 7.86936 13.2042 7.921 13.1268C7.97263 13.0494 8.00013 12.9584 8 12.8653V3.13467Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.6667 6C11.0995 6.57699 11.3334 7.27877 11.3334 8C11.3334 8.72123 11.0995 9.42301 10.6667 10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.9094 12.2427C13.4666 11.6855 13.9085 11.0241 14.2101 10.2961C14.5116 9.56815 14.6668 8.78793 14.6668 7.99999C14.6668 7.21205 14.5116 6.43183 14.2101 5.70387C13.9085 4.97591 13.4666 4.31448 12.9094 3.75732" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.79998 4C6.50183 4.00002 6.21436 4.10574 5.99358 4.29657L2.19677 7.57657C2.1348 7.63013 2.08528 7.69545 2.05139 7.76832C2.01751 7.8412 2 7.92001 2 7.99971C2 8.07941 2.01751 8.15823 2.05139 8.23111C2.08528 8.30398 2.1348 8.36929 2.19677 8.42286L5.99358 11.7034C6.21436 11.8943 6.50183 12 6.79998 12H12.8C13.1183 12 13.4235 11.8796 13.6485 11.6653C13.8736 11.4509 14 11.1602 14 10.8571V5.14286C14 4.83975 13.8736 4.54906 13.6485 4.33474C13.4235 4.12041 13.1183 4 12.8 4H6.79998Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8.5 6.5L11.5 9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.5 6.5L8.5 9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.79998 4C6.50183 4.00002 6.21436 4.10574 5.99358 4.29657L2.19677 7.57657C2.1348 7.63013 2.08528 7.69545 2.05139 7.76832C2.01751 7.8412 2 7.92001 2 7.99971C2 8.07941 2.01751 8.15823 2.05139 8.23111C2.08528 8.30398 2.1348 8.36929 2.19677 8.42286L5.99358 11.7034C6.21436 11.8943 6.50183 12 6.79998 12H12.8C13.1183 12 13.4235 11.8796 13.6485 11.6653C13.8736 11.4509 14 11.1602 14 10.8571V5.14286C14 4.83975 13.8736 4.54906 13.6485 4.33474C13.4235 4.12041 13.1183 4 12.8 4H6.79998Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.5 6.5L11.5 9.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.5 6.5L8.5 9.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M5.33333 5.8C5.33333 5.05739 5.61429 4.3452 6.11438 3.8201C6.61448 3.295 7.29276 3 8 3C8.70724 3 9.38552 3.295 9.88562 3.8201C10.3857 4.3452 10.6667 5.05739 10.6667 5.8C10.6667 9.06667 12 10 12 10H4C4 10 5.33333 9.06667 5.33333 5.8Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M6 12.5C6.19692 12.8028 6.48641 13.0554 6.83822 13.2313C7.19004 13.4072 7.59127 13.5 8 13.5C8.40873 13.5 8.80996 13.4072 9.16178 13.2313C9.51359 13.0554 9.80308 12.8028 10 12.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M5.33333 5.8C5.33333 5.05739 5.61429 4.3452 6.11438 3.8201C6.61448 3.295 7.29276 3 8 3C8.70724 3 9.38552 3.295 9.88562 3.8201C10.3857 4.3452 10.6667 5.05739 10.6667 5.8C10.6667 9.06667 12 10 12 10H4C4 10 5.33333 9.06667 5.33333 5.8Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M6 12.5C6.19692 12.8028 6.48641 13.0554 6.83822 13.2313C7.19004 13.4072 7.59127 13.5 8 13.5C8.40873 13.5 8.80996 13.4072 9.16178 13.2313C9.51359 13.0554 9.80308 12.8028 10 12.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M4.86142 8.6961C4.47786 9.66547 4 9.99997 4 9.99997H12C12 9.99997 10.6667 9.06664 10.6667 5.79997C10.6667 5.05737 10.3857 4.34518 9.88562 3.82007C9.52389 3.44026 9.06893 3.18083 8.57722 3.06635" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M6 12.5C6.19692 12.8028 6.48641 13.0554 6.83822 13.2313C7.19004 13.4072 7.59127 13.5 8 13.5C8.40873 13.5 8.80996 13.4072 9.16178 13.2313C9.51359 13.0554 9.80308 12.8028 10 12.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M4.86142 8.6961C4.47786 9.66547 4 9.99997 4 9.99997H12C12 9.99997 10.6667 9.06664 10.6667 5.79997C10.6667 5.05737 10.3857 4.34518 9.88562 3.82007C9.52389 3.44026 9.06893 3.18083 8.57722 3.06635" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M6 12.5C6.19692 12.8028 6.48641 13.0554 6.83822 13.2313C7.19004 13.4072 7.59127 13.5 8 13.5C8.40873 13.5 8.80996 13.4072 9.16178 13.2313C9.51359 13.0554 9.80308 12.8028 10 12.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="4.5" cy="4.5" r="2.5" fill="black"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M10.6667 5.8C10.6667 5.05739 10.3857 4.3452 9.88562 3.8201C9.38552 3.295 8.70724 3 8 3C7.29276 3 6.61448 3.295 6.11438 3.8201C5.61428 4.3452 5.33333 5.05739 5.33333 5.8C5.33333 9.06667 4 10 4 10H7.375" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M6 12.5C6.19692 12.8028 6.48641 13.0554 6.83822 13.2313C7.19004 13.4072 7.59127 13.5 8 13.5C8.40873 13.5 8.80996 13.4072 9.16178 13.2313C9.51359 13.0554 9.80308 12.8028 10 12.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M4 3L12.5 11.5" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+ <path d="M10.6667 5.8C10.6667 5.05739 10.3857 4.3452 9.88562 3.8201C9.38552 3.295 8.70724 3 8 3C7.29276 3 6.61448 3.295 6.11438 3.8201C5.61428 4.3452 5.33333 5.05739 5.33333 5.8C5.33333 9.06667 4 10 4 10H7.375" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M6 12.5C6.19692 12.8028 6.48641 13.0554 6.83822 13.2313C7.19004 13.4072 7.59127 13.5 8 13.5C8.40873 13.5 8.80996 13.4072 9.16178 13.2313C9.51359 13.0554 9.80308 12.8028 10 12.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M4 3L12.5 11.5" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M5.33333 5.8C5.33333 5.05739 5.61429 4.3452 6.11438 3.8201C6.61448 3.295 7.29276 3 8 3C8.70724 3 9.38552 3.295 9.88562 3.8201C10.3857 4.3452 10.6667 5.05739 10.6667 5.8C10.6667 9.06667 12 10 12 10H4C4 10 5.33333 9.06667 5.33333 5.8Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M6 12.5C6.19692 12.8028 6.48641 13.0554 6.83822 13.2313C7.19004 13.4072 7.59127 13.5 8 13.5C8.40873 13.5 8.80996 13.4072 9.16178 13.2313C9.51359 13.0554 9.80308 12.8028 10 12.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M12 2.02081C12.617 2.89491 13.0754 3.88797 13.2528 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M4 2.02081C3.38299 2.89491 2.92461 3.88797 2.74719 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M5.33333 5.8C5.33333 5.05739 5.61429 4.3452 6.11438 3.8201C6.61448 3.295 7.29276 3 8 3C8.70724 3 9.38552 3.295 9.88562 3.8201C10.3857 4.3452 10.6667 5.05739 10.6667 5.8C10.6667 9.06667 12 10 12 10H4C4 10 5.33333 9.06667 5.33333 5.8Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M6 12.5C6.19692 12.8028 6.48641 13.0554 6.83822 13.2313C7.19004 13.4072 7.59127 13.5 8 13.5C8.40873 13.5 8.80996 13.4072 9.16178 13.2313C9.51359 13.0554 9.80308 12.8028 10 12.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M12 2.02081C12.617 2.89491 13.0754 3.88797 13.2528 5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M4 2.02081C3.38299 2.89491 2.92461 3.88797 2.74719 5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 10.667a1.333 1.333 0 1 0-2.667 0V12A1.333 1.333 0 1 0 12 12v-1.333ZM6.667 4A1.333 1.333 0 0 0 4 4v1.333a1.333 1.333 0 1 0 2.667 0V4ZM4 13.333h2.667M9.333 6.667H12M4 9.333h1.333v4M9.333 2.667h1.334v4"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M12 10.667a1.333 1.333 0 1 0-2.667 0V12A1.333 1.333 0 1 0 12 12v-1.333ZM6.667 4A1.333 1.333 0 0 0 4 4v1.333a1.333 1.333 0 1 0 2.667 0V4ZM4 13.333h2.667M9.333 6.667H12M4 9.333h1.333v4M9.333 2.667h1.334v4"/></svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M9.29787 2.8462C9.41607 2.90046 9.51178 2.98975 9.5701 3.10016C9.62841 3.21057 9.64605 3.3359 9.62028 3.45666L8.96749 6.51117H12.2195C12.334 6.51115 12.446 6.54191 12.5423 6.59976C12.6386 6.65762 12.7151 6.74013 12.7627 6.83748C12.8102 6.93482 12.8269 7.04291 12.8106 7.14885C12.7943 7.2548 12.7458 7.35413 12.6709 7.43504L7.49631 13.0184C7.40998 13.1115 7.29318 13.1752 7.16411 13.1997C7.03504 13.2241 6.90092 13.2081 6.78264 13.1539C6.66437 13.0997 6.56859 13.0104 6.5102 12.9C6.4518 12.7896 6.43408 12.6643 6.45979 12.5435L7.11259 9.48899H3.86054C3.74609 9.489 3.63405 9.45825 3.53776 9.40039C3.44147 9.34254 3.36498 9.26003 3.31742 9.16268C3.26986 9.06534 3.25322 8.95725 3.26949 8.85131C3.28576 8.74536 3.33423 8.64603 3.40916 8.56513L8.58377 2.98169C8.67012 2.88856 8.78699 2.82478 8.91616 2.80028C9.04533 2.77576 9.17953 2.79192 9.29787 2.8462Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M9.29787 2.8462C9.41607 2.90046 9.51178 2.98975 9.5701 3.10016C9.62841 3.21057 9.64605 3.3359 9.62028 3.45666L8.96749 6.51117H12.2195C12.334 6.51115 12.446 6.54191 12.5423 6.59976C12.6386 6.65762 12.7151 6.74013 12.7627 6.83748C12.8102 6.93482 12.8269 7.04291 12.8106 7.14885C12.7943 7.2548 12.7458 7.35413 12.6709 7.43504L7.49631 13.0184C7.40998 13.1115 7.29318 13.1752 7.16411 13.1997C7.03504 13.2241 6.90092 13.2081 6.78264 13.1539C6.66437 13.0997 6.56859 13.0104 6.5102 12.9C6.4518 12.7896 6.43408 12.6643 6.45979 12.5435L7.11259 9.48899H3.86054C3.74609 9.489 3.63405 9.45825 3.53776 9.40039C3.44147 9.34254 3.36498 9.26003 3.31742 9.16268C3.26986 9.06534 3.25322 8.95725 3.26949 8.85131C3.28576 8.74536 3.33423 8.64603 3.40916 8.56513L8.58377 2.98169C8.67012 2.88856 8.78699 2.82478 8.91616 2.80028C9.04533 2.77576 9.17953 2.79192 9.29787 2.8462Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 12.125v-8.25c0-.365.132-.714.366-.972.235-.258.552-.403.884-.403H12v11H5.25c-.332 0-.65-.145-.884-.403A1.448 1.448 0 0 1 4 12.125Zm0 0c0-.365.132-.714.366-.972.235-.258.552-.403.884-.403H12"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M4 12.125v-8.25c0-.365.132-.714.366-.972.235-.258.552-.403.884-.403H12v11H5.25c-.332 0-.65-.145-.884-.403A1.448 1.448 0 0 1 4 12.125Zm0 0c0-.365.132-.714.366-.972.235-.258.552-.403.884-.403H12"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.5 10.5V3.643c0-.303.113-.594.315-.808.202-.215.476-.335.762-.335H9.5"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.5 9.5h-.667c-.353 0-.692.105-.942.293-.25.187-.391.442-.391.707 0 .265.14.52.39.707.25.188.59.293.943.293H4.5M13.5 11.25H7.577c-.286 0-.56.118-.762.33a1.151 1.151 0 0 0-.315.795m0 0c0 .298.113.585.315.796.202.21.476.329.762.329H13.5v-9H7.577c-.286 0-.56.119-.762.33a1.151 1.151 0 0 0-.315.795v6.75Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.5 10.5V3.643c0-.303.113-.594.315-.808.202-.215.476-.335.762-.335H9.5"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M4.5 9.5h-.667c-.353 0-.692.105-.942.293-.25.187-.391.442-.391.707 0 .265.14.52.39.707.25.188.59.293.943.293H4.5M13.5 11.25H7.577c-.286 0-.56.118-.762.33a1.151 1.151 0 0 0-.315.795m0 0c0 .298.113.585.315.796.202.21.476.329.762.329H13.5v-9H7.577c-.286 0-.56.119-.762.33a1.151 1.151 0 0 0-.315.795v6.75Z"/></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.17279 8.26346C4.87566 8.62402 5.68419 8.72168 6.4527 8.53885C7.2212 8.35601 7.89913 7.90471 8.36433 7.26626C8.82953 6.62781 9.0514 5.8442 8.98996 5.05664C8.92852 4.26908 8.58781 3.52936 8.02922 2.97078C7.47064 2.41219 6.73092 2.07148 5.94336 2.01004C5.1558 1.9486 4.37219 2.17047 3.73374 2.63567C3.09529 3.10087 2.64399 3.7788 2.46115 4.5473C2.27832 5.31581 2.37598 6.12435 2.73654 6.82721L2 9L4.17279 8.26346Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.07168 11C7.16761 11.4537 7.35843 11.8857 7.63567 12.2662C8.10087 12.9047 8.7788 13.356 9.5473 13.5388C10.3158 13.7217 11.1243 13.624 11.8272 13.2634L14 14L13.2635 11.8272C13.624 11.1243 13.7217 10.3158 13.5388 9.54728C13.356 8.77877 12.9047 8.10084 12.2663 7.63564C11.8858 7.3584 11.4537 7.16759 11 7.07166" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.17279 8.26346C4.87566 8.62402 5.68419 8.72168 6.4527 8.53885C7.2212 8.35601 7.89913 7.90471 8.36433 7.26626C8.82953 6.62781 9.0514 5.8442 8.98996 5.05664C8.92852 4.26908 8.58781 3.52936 8.02922 2.97078C7.47064 2.41219 6.73092 2.07148 5.94336 2.01004C5.1558 1.9486 4.37219 2.17047 3.73374 2.63567C3.09529 3.10087 2.64399 3.7788 2.46115 4.5473C2.27832 5.31581 2.37598 6.12435 2.73654 6.82721L2 9L4.17279 8.26346Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.07168 11C7.16761 11.4537 7.35843 11.8857 7.63567 12.2662C8.10087 12.9047 8.7788 13.356 9.5473 13.5388C10.3158 13.7217 11.1243 13.624 11.8272 13.2634L14 14L13.2635 11.8272C13.624 11.1243 13.7217 10.3158 13.5388 9.54728C13.356 8.77877 12.9047 8.10084 12.2663 7.63564C11.8858 7.3584 11.4537 7.16759 11 7.07166" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.625 8.98121L7.03402 10.7714L11.3437 4.75989" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.625 8.98121L7.03402 10.7714L11.3437 4.75989" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.94873 9.02564L7.48722 10.0513L10.0514 6.46149" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.5"/>
+<path d="M5.94873 9.02564L7.48722 10.0513L10.0514 6.46149" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.5999 4.38336L4.99996 10.9833L2 7.98332" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M14 6.78339L9.50009 11.2833L8.6001 10.3833" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.5999 4.38336L4.99996 10.9833L2 7.98332" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M14 6.78339L9.50009 11.2833L8.6001 10.3833" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.15186 6.47321L7.99258 10.1696L11.8483 6.47321" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.15186 6.47321L7.99258 10.1696L11.8483 6.47321" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M9.55361 4.15179L5.85718 7.99251L9.55361 11.8482" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.55361 4.15179L5.85718 7.99251L9.55361 11.8482" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.44653 4.16071L10.1608 8.00143L6.44653 11.8571" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.44653 4.16071L10.1608 8.00143L6.44653 11.8571" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.15186 9.56252L7.99258 5.86609L11.8483 9.56252" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.15186 9.56252L7.99258 5.86609L11.8483 9.56252" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.66675 10L8.00008 13.3333L11.3334 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4.66675 6.00002L8.00008 2.66669L11.3334 6.00002" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.66675 10L8.00008 13.3333L11.3334 10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.66675 6.00002L8.00008 2.66669L11.3334 6.00002" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6.54492 6.5C6.66248 6.16582 6.8945 5.88404 7.19991 5.70455C7.50532 5.52506 7.86439 5.45945 8.21354 5.51934C8.56268 5.57922 8.87937 5.76075 9.1075 6.03175C9.33564 6.30276 9.4605 6.64576 9.45997 7C9.45997 8.00002 7.95994 8.50003 7.95994 8.50003" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 10.5H8.005" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.54492 6.5C6.66248 6.16582 6.8945 5.88404 7.19991 5.70455C7.50532 5.52506 7.86439 5.45945 8.21354 5.51934C8.56268 5.57922 8.87937 5.76075 9.1075 6.03175C9.33564 6.30276 9.4605 6.64576 9.45997 7C9.45997 8.00002 7.95994 8.50003 7.95994 8.50003" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 10.5H8.005" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.70581 4.5L11.294 11.5M11.294 4.5L4.70581 11.5" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M4.70581 4.5L11.294 11.5M11.294 4.5L4.70581 11.5" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8.001 9v4l-2-2M8.001 13l2-2"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3.436 10.389a4.215 4.215 0 0 1-1.424-3.484 4.227 4.227 0 0 1 1.92-3.236 4.19 4.19 0 0 1 5.335.665 4.22 4.22 0 0 1 .96 1.677H11.3c.584 0 1.151.19 1.618.54a2.71 2.71 0 0 1 .913 3.116A2.709 2.709 0 0 1 12.762 11"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8.001 9v4l-2-2M8.001 13l2-2"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.436 10.389a4.215 4.215 0 0 1-1.424-3.484 4.227 4.227 0 0 1 1.92-3.236 4.19 4.19 0 0 1 5.335.665 4.22 4.22 0 0 1 .96 1.677H11.3c.584 0 1.151.19 1.618.54a2.71 2.71 0 0 1 .913 3.116A2.709 2.709 0 0 1 12.762 11"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m11.75 10.5 2.5-2.5-2.5-2.5M4.25 5.5 1.75 8l2.5 2.5M9.563 3 6.437 13"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="m11.75 10.5 2.5-2.5-2.5-2.5M4.25 5.5 1.75 8l2.5 2.5M9.563 3 6.437 13"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 12.8a4.8 4.8 0 1 0 0-9.6 4.8 4.8 0 0 0 0 9.6Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 9.2a1.2 1.2 0 1 0 0-2.4 1.2 1.2 0 0 0 0 2.4ZM8 2v1.2M8 14v-1.2M11 13.196l-.6-1.038M7.4 6.962 5 2.804M13.196 11l-1.038-.6M2.804 5l1.038.6M9.2 8H14M2 8h1.2M13.196 5l-1.038.6M2.804 11l1.038-.6M11 2.804l-.6 1.038M7.4 9.038 5 13.196"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 12.8a4.8 4.8 0 1 0 0-9.6 4.8 4.8 0 0 0 0 9.6Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 9.2a1.2 1.2 0 1 0 0-2.4 1.2 1.2 0 0 0 0 2.4ZM8 2v1.2M8 14v-1.2M11 13.196l-.6-1.038M7.4 6.962 5 2.804M13.196 11l-1.038-.6M2.804 5l1.038.6M9.2 8H14M2 8h1.2M13.196 5l-1.038.6M2.804 11l1.038-.6M11 2.804l-.6 1.038M7.4 9.038 5 13.196"/></svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3.5 6.12487L7.64656 1.97852C7.84183 1.78327 8.1584 1.78328 8.35366 1.97853L12.5 6.12487" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M3.5 6.12487L7.64656 1.97852C7.84183 1.78327 8.1584 1.78328 8.35366 1.97853L12.5 6.12487" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,9 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.44643 8.76593C6.83106 8.76593 7.14286 9.0793 7.14286 9.46588V10.9825C7.14286 11.369 6.83106 11.6824 6.44643 11.6824C6.06181 11.6824 5.75 11.369 5.75 10.9825V9.46588C5.75 9.0793 6.06181 8.76593 6.44643 8.76593Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.57168 8.76593C9.95631 8.76593 10.2681 9.0793 10.2681 9.46588V10.9825C10.2681 11.369 9.95631 11.6824 9.57168 11.6824C9.18705 11.6824 8.87524 11.369 8.87524 10.9825V9.46588C8.87524 9.0793 9.18705 8.76593 9.57168 8.76593Z" fill="black"/>
-<path d="M7.99976 4.17853C7.99976 6.67853 5.83695 7.28202 4.30332 7.28202C2.76971 7.28202 2.44604 6.1547 2.44604 4.76409C2.44604 3.37347 3.68929 2.24615 5.2229 2.24615C6.75651 2.24615 7.99976 2.78791 7.99976 4.17853Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.5"/>
-<path d="M8 4.17853C8 6.67853 10.1628 7.28202 11.6965 7.28202C13.2301 7.28202 13.5537 6.1547 13.5537 4.76409C13.5537 3.37347 12.3105 2.24615 10.7769 2.24615C9.24325 2.24615 8 2.78791 8 4.17853Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.5"/>
-<path d="M12.5894 6.875C12.5894 6.875 13.3413 7.35585 13.7144 8.08398C14.0876 8.81212 14.0894 10.4985 13.7144 11.1064C13.3395 11.7143 12.8931 12.1429 11.7637 12.7543C10.6344 13.3657 9.143 13.7321 9.143 13.7321H6.85728C6.85728 13.7321 5.37513 13.4107 4.23656 12.7543C3.09798 12.0978 2.55371 11.6786 2.28585 11.1064C2.01799 10.5342 1.92871 8.85715 2.28585 8.08398C2.64299 7.31081 3.42871 6.875 3.42871 6.875" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
+<path d="M7.99976 4.17853C7.99976 6.67853 5.83695 7.28202 4.30332 7.28202C2.76971 7.28202 2.44604 6.1547 2.44604 4.76409C2.44604 3.37347 3.68929 2.24615 5.2229 2.24615C6.75651 2.24615 7.99976 2.78791 7.99976 4.17853Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.2"/>
+<path d="M8 4.17853C8 6.67853 10.1628 7.28202 11.6965 7.28202C13.2301 7.28202 13.5537 6.1547 13.5537 4.76409C13.5537 3.37347 12.3105 2.24615 10.7769 2.24615C9.24325 2.24615 8 2.78791 8 4.17853Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.2"/>
+<path d="M12.5894 6.875C12.5894 6.875 13.3413 7.35585 13.7144 8.08398C14.0876 8.81212 14.0894 10.4985 13.7144 11.1064C13.3395 11.7143 12.8931 12.1429 11.7637 12.7543C10.6344 13.3657 9.143 13.7321 9.143 13.7321H6.85728C6.85728 13.7321 5.37513 13.4107 4.23656 12.7543C3.09798 12.0978 2.55371 11.6786 2.28585 11.1064C2.01799 10.5342 1.92871 8.85715 2.28585 8.08398C2.64299 7.31081 3.42871 6.875 3.42871 6.875" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
<path d="M11.9375 12.6016V7.33636L13.9052 7.99224V10.9255L11.9375 12.6016Z" fill="black" fill-opacity="0.75"/>
<path d="M4.01793 12.6016V7.33636L2.05029 7.99224V10.9255L4.01793 12.6016Z" fill="black" fill-opacity="0.75"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12.286 6H7.048C6.469 6 6 6.469 6 7.048v5.238c0 .578.469 1.047 1.048 1.047h5.238c.578 0 1.047-.469 1.047-1.047V7.048c0-.579-.469-1.048-1.047-1.048Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3.714 10a1.05 1.05 0 0 1-1.047-1.048V3.714a1.05 1.05 0 0 1 1.047-1.047h5.238A1.05 1.05 0 0 1 10 3.714"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M12.286 6H7.048C6.469 6 6 6.469 6 7.048v5.238c0 .578.469 1.047 1.048 1.047h5.238c.578 0 1.047-.469 1.047-1.047V7.048c0-.579-.469-1.048-1.047-1.048Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.714 10a1.05 1.05 0 0 1-1.047-1.048V3.714a1.05 1.05 0 0 1 1.047-1.047h5.238A1.05 1.05 0 0 1 10 3.714"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.8 2h2.4M8 9.2l1.8-1.8M8 14a4.8 4.8 0 1 0 0-9.6A4.8 4.8 0 0 0 8 14Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M6.8 2h2.4M8 9.2l1.8-1.8M8 14a4.8 4.8 0 1 0 0-9.6A4.8 4.8 0 0 0 8 14Z"/></svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.6863 11.3137 2 8 2C4.6863 2 2 4.6863 2 8C2 11.3137 4.6863 14 8 14Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M14 8L11 8" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5 8H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 5V2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 14V11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.6863 11.3137 2 8 2C4.6863 2 2 4.6863 2 8C2 11.3137 4.6863 14 8 14Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M14 8L11 8" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 8H2" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 5V2" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 14V11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11 13H10.4C9.76346 13 9.15302 12.7893 8.70296 12.4142C8.25284 12.0391 8 11.5304 8 11V5C8 4.46957 8.25284 3.96086 8.70296 3.58579C9.15302 3.21071 9.76346 3 10.4 3H11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5 13H5.6C6.23654 13 6.84698 12.7893 7.29704 12.4142C7.74716 12.0391 8 11.5304 8 11V5C8 4.46957 7.74716 3.96086 7.29704 3.58579C6.84698 3.21071 6.23654 3 5.6 3H5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11 13H10.4C9.76346 13 9.15302 12.7893 8.70296 12.4142C8.25284 12.0391 8 11.5304 8 11V5C8 4.46957 8.25284 3.96086 8.70296 3.58579C9.15302 3.21071 9.76346 3 10.4 3H11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 13H5.6C6.23654 13 6.84698 12.7893 7.29704 12.4142C7.74716 12.0391 8 11.5304 8 11V5C8 4.46957 7.74716 3.96086 7.29704 3.58579C6.84698 3.21071 6.23654 3 5.6 3H5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3.333 8h9.334"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.333 8h9.334"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8.017 5.625c2.974 0 5.385-.804 5.385-1.795 0-.991-2.41-1.795-5.385-1.795-2.973 0-5.384.804-5.384 1.795 0 .991 2.41 1.795 5.384 1.795Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.633 3.83v8.376c-.003.288.201.571.596.827.394.256.967.477 1.671.643a13.12 13.12 0 0 0 2.373.314c.854.04 1.725.01 2.54-.085M13.402 3.83v1.795M13.402 8.018l-1.795 2.991H14l-1.795 2.992"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.633 8.018c0 .28.198.556.575.805.378.25.925.467 1.599.634.673.167 1.454.279 2.28.327.827.048 1.676.032 2.48-.049"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8.017 5.625c2.974 0 5.385-.804 5.385-1.795 0-.991-2.41-1.795-5.385-1.795-2.973 0-5.384.804-5.384 1.795 0 .991 2.41 1.795 5.384 1.795Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.633 3.83v8.376c-.003.288.201.571.596.827.394.256.967.477 1.671.643a13.12 13.12 0 0 0 2.373.314c.854.04 1.725.01 2.54-.085M13.402 3.83v1.795M13.402 8.018l-1.795 2.991H14l-1.795 2.992"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.633 8.018c0 .28.198.556.575.805.378.25.925.467 1.599.634.673.167 1.454.279 2.28.327.827.048 1.676.032 2.48-.049"/></svg>
@@ -1,12 +1,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.44727 2.19177L6.38617 3.11055" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.64722 3.11055L10.553 2.19177" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.66298 6.07369L5.66298 5.10997C5.64886 4.80689 5.69884 4.50419 5.80993 4.22016C5.92101 3.93613 6.09088 3.67665 6.3093 3.45738C6.52771 3.23811 6.79013 3.0636 7.08074 2.94437C7.37134 2.82514 7.68409 2.76367 8.00011 2.76367C8.31614 2.76367 8.62889 2.82514 8.91949 2.94437C9.21008 3.0636 9.4725 3.23811 9.69092 3.45738C9.90933 3.67665 10.0792 3.93613 10.1903 4.22016C10.3014 4.50419 10.3514 4.80689 10.3373 5.10997V6.07369" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8.00017 13.1366C6.09924 13.1366 4.54395 11.6686 4.54395 9.87441V8.24333C4.54395 7.66653 4.7867 7.11337 5.21882 6.70552C5.65092 6.29767 6.237 6.06854 6.8481 6.06854H9.15225C9.76335 6.06854 10.3494 6.29767 10.7815 6.70552C11.2136 7.11337 11.4564 7.66653 11.4564 8.24333V9.87441C11.4564 11.6686 9.9011 13.1366 8.00017 13.1366Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4.54343 6.22415C3.43167 6.10894 2.51001 5.12967 2.51001 3.91998" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4.54367 8.83472H2.2395" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M2.35449 13.4175C2.35449 12.2078 3.33376 11.1709 4.54345 11.1134" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13.1673 3.91998C13.1673 5.12967 12.2455 6.10894 11.1511 6.22415" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13.7605 8.83472H11.4563" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.4563 11.1134C12.666 11.1709 13.6453 12.2078 13.6453 13.4175" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.44727 2.19177L6.38617 3.11055" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.64722 3.11055L10.553 2.19177" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.66298 6.07369L5.66298 5.10997C5.64886 4.80689 5.69884 4.50419 5.80993 4.22016C5.92101 3.93613 6.09088 3.67665 6.3093 3.45738C6.52771 3.23811 6.79013 3.0636 7.08074 2.94437C7.37134 2.82514 7.68409 2.76367 8.00011 2.76367C8.31614 2.76367 8.62889 2.82514 8.91949 2.94437C9.21008 3.0636 9.4725 3.23811 9.69092 3.45738C9.90933 3.67665 10.0792 3.93613 10.1903 4.22016C10.3014 4.50419 10.3514 4.80689 10.3373 5.10997V6.07369" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.00017 13.1366C6.09924 13.1366 4.54395 11.6686 4.54395 9.87441V8.24333C4.54395 7.66653 4.7867 7.11337 5.21882 6.70552C5.65092 6.29767 6.237 6.06854 6.8481 6.06854H9.15225C9.76335 6.06854 10.3494 6.29767 10.7815 6.70552C11.2136 7.11337 11.4564 7.66653 11.4564 8.24333V9.87441C11.4564 11.6686 9.9011 13.1366 8.00017 13.1366Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.54343 6.22415C3.43167 6.10894 2.51001 5.12967 2.51001 3.91998" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.54367 8.83472H2.2395" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.35449 13.4175C2.35449 12.2078 3.33376 11.1709 4.54345 11.1134" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.1673 3.91998C13.1673 5.12967 12.2455 6.10894 11.1511 6.22415" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.7605 8.83472H11.4563" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.4563 11.1134C12.666 11.1709 13.6453 12.2078 13.6453 13.4175" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" fill="black" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.167 3v10M7.167 3l6 5-6 5V3Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M4.167 3v10M7.167 3l6 5-6 5V3Z"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" clip-path="url(#a)"><path d="m13 3 2-2M1 15l2-2M4.202 13.53a1.598 1.598 0 0 0 2.266 0L8 11.997 4.003 8 2.47 9.532a1.6 1.6 0 0 0 0 2.265l1.732 1.733ZM5 9l1.5-1.5M7 11l1.5-1.5M8 4.003 11.997 8l1.533-1.532a1.599 1.599 0 0 0 0-2.266L11.798 2.47a1.598 1.598 0 0 0-2.266 0L8 4.003Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" clip-path="url(#a)"><path d="m13 3 2-2M1 15l2-2M4.202 13.53a1.598 1.598 0 0 0 2.266 0L8 11.997 4.003 8 2.47 9.532a1.6 1.6 0 0 0 0 2.265l1.732 1.733ZM5 9l1.5-1.5M7 11l1.5-1.5M8 4.003 11.997 8l1.533-1.532a1.599 1.599 0 0 0 0-2.266L11.798 2.47a1.598 1.598 0 0 0-2.266 0L8 4.003Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.8889 2H4.11111C3.49746 2 3 2.59695 3 3.33333V12.6667C3 13.403 3.49746 14 4.11111 14H11.8889C12.5025 14 13 13.403 13 12.6667V3.33333C13 2.59695 12.5025 2 11.8889 2Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9 6H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10 10H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.8889 2H4.11111C3.49746 2 3 2.59695 3 3.33333V12.6667C3 13.403 3.49746 14 4.11111 14H11.8889C12.5025 14 13 13.403 13 12.6667V3.33333C13 2.59695 12.5025 2 11.8889 2Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 6H6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10 10H6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M2 2L14 14M5.81044 2.41392C6.89676 1.98976 8.08314 1.89138 9.22449 2.13079C10.3658 2.37021 11.4127 2.93705 12.237 3.76199C13.0613 4.58693 13.6273 5.6342 13.8658 6.77573C14.1044 7.91727 14.0051 9.10357 13.5801 10.1896M12.2484 12.2484C11.1176 13.3558 9.59562 13.9724 8.01292 13.9642C6.43021 13.956 4.91467 13.3236 3.79552 12.2045C2.67636 11.0853 2.044 9.56979 2.03578 7.98708C2.02757 6.40438 2.64417 4.88236 3.75165 3.75165" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2 2L14 14M5.81044 2.41392C6.89676 1.98976 8.08314 1.89138 9.22449 2.13079C10.3658 2.37021 11.4127 2.93705 12.237 3.76199C13.0613 4.58693 13.6273 5.6342 13.8658 6.77573C14.1044 7.91727 14.0051 9.10357 13.5801 10.1896M12.2484 12.2484C11.1176 13.3558 9.59562 13.9724 8.01292 13.9642C6.43021 13.956 4.91467 13.3236 3.79552 12.2045C2.67636 11.0853 2.044 9.56979 2.03578 7.98708C2.02757 6.40438 2.64417 4.88236 3.75165 3.75165" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14 11.333A6 6 0 0 0 4 6.867l-1 .9"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M2 4.667v4h4"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 12a.667.667 0 1 0 0-1.333A.667.667 0 0 0 8 12Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M14 11.333A6 6 0 0 0 4 6.867l-1 .9"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M2 4.667v4h4"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 12a.667.667 0 1 0 0-1.333A.667.667 0 0 0 8 12Z"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3.333 10 8 14.667 12.667 10M8 5.333v9.334"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 2.667a.667.667 0 1 0 0-1.334.667.667 0 0 0 0 1.334Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.333 10 8 14.667 12.667 10M8 5.333v9.334"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 2.667a.667.667 0 1 0 0-1.334.667.667 0 0 0 0 1.334Z"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3.333 6 8 1.333 12.667 6M8 10.667V1.333"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 13.333a.667.667 0 1 1 0 1.334.667.667 0 0 1 0-1.334Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.333 6 8 1.333 12.667 6M8 10.667V1.333"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 13.333a.667.667 0 1 1 0 1.334.667.667 0 0 1 0-1.334Z"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2 11.333a6 6 0 0 1 10-4.466l1 .9"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M14 4.667v4h-4"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 12a.667.667 0 1 1 0-1.333A.667.667 0 0 1 8 12Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2 11.333a6 6 0 0 1 10-4.466l1 .9"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M14 4.667v4h-4"/><path fill="#000" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 12a.667.667 0 1 1 0-1.333A.667.667 0 0 1 8 12Z"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 3v8M4 7h8M4 13h8"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 3v8M4 7h8M4 13h8"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m2 2 12 12M4.269 4.27a4.2 4.2 0 0 0 1.93 7.93h5.1c.267 0 .53-.039.785-.116M13.72 10.7a2.699 2.699 0 0 0-2.42-3.9h-1.074A4.204 4.204 0 0 0 6.8 3.842"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="m2 2 12 12M4.269 4.27a4.2 4.2 0 0 0 1.93 7.93h5.1c.267 0 .53-.039.785-.116M13.72 10.7a2.699 2.699 0 0 0-2.42-3.9h-1.074A4.204 4.204 0 0 0 6.8 3.842"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 9.667v2.222A1.111 1.111 0 0 1 11.889 13H4.11A1.111 1.111 0 0 1 3 11.889V9.667M5.222 6.889 8 9.667l2.778-2.778M8 9.667V3"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M13 9.667v2.222A1.111 1.111 0 0 1 11.889 13H4.11A1.111 1.111 0 0 1 3 11.889V9.667M5.222 6.889 8 9.667l2.778-2.778M8 9.667V3"/></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M13 4C13.5523 4 14 4.44772 14 5V11C14 11.5523 13.5523 12 13 12H3C2.44772 12 2 11.5523 2 11V5C2 4.44772 2.44772 4 3 4H13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M13.5 5L7.9999 8.5L2.5 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M13 4C13.5523 4 14 4.44772 14 5V11C14 11.5523 13.5523 12 13 12H3C2.44772 12 2 11.5523 2 11V5C2 4.44772 2.44772 4 3 4H13Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M13.5 5L7.9999 8.5L2.5 5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m5.015 12.983-2.567-2.382c-.597-.554-.597-1.385 0-1.884L8.179 3.4c.597-.554 1.493-.554 2.03 0L13.552 6.5c.597.554.597 1.385 0 1.884l-4.955 4.598M14 12.983H5M4.5 7.483l5 5"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="m5.015 12.983-2.567-2.382c-.597-.554-.597-1.385 0-1.884L8.179 3.4c.597-.554 1.493-.554 2.03 0L13.552 6.5c.597.554.597 1.385 0 1.884l-4.955 4.598M14 12.983H5M4.5 7.483l5 5"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 6V3h3M3 3l5 5M8 3a5 5 0 1 1-5 5"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3 6V3h3M3 3l5 5M8 3a5 5 0 1 1-5 5"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10.437 11.0461L13.4831 8L10.437 4.95392" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13 8L8 8" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6.6553 13.4659H4.21843C3.89528 13.4659 3.58537 13.3375 3.35687 13.109C3.12837 12.8805 3 12.5706 3 12.2475V3.71843C3 3.39528 3.12837 3.08537 3.35687 2.85687C3.58537 2.62837 3.89528 2.5 4.21843 2.5H6.6553" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.437 11.0461L13.4831 8L10.437 4.95392" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13 8L8 8" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.6553 13.4659H4.21843C3.89528 13.4659 3.58537 13.3375 3.35687 13.109C3.12837 12.8805 3 12.5706 3 12.2475V3.71843C3 3.39528 3.12837 3.08537 3.35687 2.85687C3.58537 2.62837 3.89528 2.5 4.21843 2.5H6.6553" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.1998 9.60002L7.9998 12.8M7.9998 12.8L4.7998 9.60002M7.9998 12.8V6.40002" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.33325 3.73334H10.6666" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M11.1998 9.60002L7.9998 12.8M7.9998 12.8L4.7998 9.60002M7.9998 12.8V6.40002" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.33325 3.73334H10.6666" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.80005 6.93334L8.00005 3.73334M8.00005 3.73334L11.2 6.93334M8.00005 3.73334V10.1333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.3335 12.8H10.6668" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M4.80005 6.93334L8.00005 3.73334M8.00005 3.73334L11.2 6.93334M8.00005 3.73334V10.1333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.3335 12.8H10.6668" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" clip-path="url(#a)"><path d="M8 14.667v-4M8 5.333v-4M2.667 8H1.333M6.667 8H5.333M10.667 8H9.333M14.667 8h-1.334M10 12.667l-2 2-2-2M10 3.333l-2-2-2 2"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" clip-path="url(#a)"><path d="M8 14.667v-4M8 5.333v-4M2.667 8H1.333M6.667 8H5.333M10.667 8H9.333M14.667 8h-1.334M10 12.667l-2 2-2-2M10 3.333l-2-2-2 2"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M2.0375 8.2088C1.9875 8.07409 1.9875 7.92592 2.0375 7.79122C2.5245 6.61039 3.35114 5.60076 4.41264 4.89031C5.47414 4.17986 6.72268 3.8006 7.99999 3.8006C9.2773 3.8006 10.5258 4.17986 11.5873 4.89031C12.6488 5.60076 13.4755 6.61039 13.9625 7.79122C14.0125 7.92592 14.0125 8.07409 13.9625 8.2088C13.4755 9.38962 12.6488 10.3993 11.5873 11.1097C10.5258 11.8202 9.2773 12.1994 7.99999 12.1994C6.72268 12.1994 5.47414 11.8202 4.41264 11.1097C3.35114 10.3993 2.5245 9.38962 2.0375 8.2088Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8.0001 9.79988C8.99416 9.79988 9.80001 8.99404 9.80001 7.99998C9.80001 7.00592 8.99416 6.20007 8.0001 6.20007C7.00604 6.20007 6.2002 7.00592 6.2002 7.99998C6.2002 8.99404 7.00604 9.79988 8.0001 9.79988Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.0375 8.2088C1.9875 8.07409 1.9875 7.92592 2.0375 7.79122C2.5245 6.61039 3.35114 5.60076 4.41264 4.89031C5.47414 4.17986 6.72268 3.8006 7.99999 3.8006C9.2773 3.8006 10.5258 4.17986 11.5873 4.89031C12.6488 5.60076 13.4755 6.61039 13.9625 7.79122C14.0125 7.92592 14.0125 8.07409 13.9625 8.2088C13.4755 9.38962 12.6488 10.3993 11.5873 11.1097C10.5258 11.8202 9.2773 12.1994 7.99999 12.1994C6.72268 12.1994 5.47414 11.8202 4.41264 11.1097C3.35114 10.3993 2.5245 9.38962 2.0375 8.2088Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.0001 9.79988C8.99416 9.79988 9.80001 8.99404 9.80001 7.99998C9.80001 7.00592 8.99416 6.20007 8.0001 6.20007C7.00604 6.20007 6.2002 7.00592 6.2002 7.99998C6.2002 8.99404 7.00604 9.79988 8.0001 9.79988Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.875 2H4.25c-.332 0-.65.126-.884.351-.234.226-.366.53-.366.849v9.6c0 .318.132.623.366.849.235.225.552.351.884.351h7.5c.332 0 .65-.127.884-.351.234-.225.366-.53.366-.85V5L9.875 2Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 2v2.667A1.333 1.333 0 0 0 10.333 6H13"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M9.875 2H4.25c-.332 0-.65.126-.884.351-.234.226-.366.53-.366.849v9.6c0 .318.132.623.366.849.235.225.552.351.884.351h7.5c.332 0 .65-.127.884-.351.234-.225.366-.53.366-.85V5L9.875 2Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M9 2v2.667A1.333 1.333 0 0 0 10.333 6H13"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.8 8.3 5.6 9.8l1.2 1.5M9.2 8.3l1.2 1.5-1.2 1.5M9.2 2v2.4a1.2 1.2 0 0 0 1.2 1.2h2.4"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.8 2H4.4a1.2 1.2 0 0 0-1.2 1.2v9.6A1.2 1.2 0 0 0 4.4 14h7.2a1.2 1.2 0 0 0 1.2-1.2V5l-3-3Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M6.8 8.3 5.6 9.8l1.2 1.5M9.2 8.3l1.2 1.5-1.2 1.5M9.2 2v2.4a1.2 1.2 0 0 0 1.2 1.2h2.4"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M9.8 2H4.4a1.2 1.2 0 0 0-1.2 1.2v9.6A1.2 1.2 0 0 0 4.4 14h7.2a1.2 1.2 0 0 0 1.2-1.2V5l-3-3Z"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.8 2H4.4a1.2 1.2 0 0 0-1.2 1.2v9.6A1.2 1.2 0 0 0 4.4 14h7.2a1.2 1.2 0 0 0 1.2-1.2V5l-3-3ZM6.2 6.8h3.6M8 8.6V5M6.2 11h3.6"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M9.8 2H4.4a1.2 1.2 0 0 0-1.2 1.2v9.6A1.2 1.2 0 0 0 4.4 14h7.2a1.2 1.2 0 0 0 1.2-1.2V5l-3-3ZM6.2 6.8h3.6M8 8.6V5M6.2 11h3.6"/></svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M13 11V11.8374C13 11.9431 12.9665 12.046 12.9044 12.1315L12.1498 13.1691C12.0557 13.2985 11.9054 13.375 11.7454 13.375H4.25461C4.09464 13.375 3.94433 13.2985 3.85024 13.1691L3.09563 12.1315C3.03348 12.046 3 11.9431 3 11.8374V3" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M13 11V11.8374C13 11.9431 12.9665 12.046 12.9044 12.1315L12.1498 13.1691C12.0557 13.2985 11.9054 13.375 11.7454 13.375H4.25461C4.09464 13.375 3.94433 13.2985 3.85024 13.1691L3.09563 12.1315C3.03348 12.046 3 11.9431 3 11.8374V3" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
<path d="M3 13V11L8 12H13V13H3Z" fill="black"/>
-<path d="M6.63246 3.04418C7.44914 3.31641 8 4.08069 8 4.94155V11.7306C8 12.0924 7.62757 12.3345 7.29693 12.1875L3.79693 10.632C3.61637 10.5518 3.5 10.3727 3.5 10.1751V2.69374C3.5 2.35246 3.83435 2.11148 4.15811 2.2194L6.63246 3.04418Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.5 3C8.67157 3 8 3.67157 8 4.5V13C8 12.1954 11.2366 12.0382 12.5017 12.0075C12.7778 12.0008 13 11.7761 13 11.5V3.5C13 3.22386 12.7761 3 12.5 3H9.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.63246 3.04418C7.44914 3.31641 8 4.08069 8 4.94155V11.7306C8 12.0924 7.62757 12.3345 7.29693 12.1875L3.79693 10.632C3.61637 10.5518 3.5 10.3727 3.5 10.1751V2.69374C3.5 2.35246 3.83435 2.11148 4.15811 2.2194L6.63246 3.04418Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.5 3C8.67157 3 8 3.67157 8 4.5V13C8 12.1954 11.2366 12.0382 12.5017 12.0075C12.7778 12.0008 13 11.7761 13 11.5V3.5C13 3.22386 12.7761 3 12.5 3H9.5Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 5H11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M3 8H13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M3 11H9" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M3 5H11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M3 8H13" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M3 11H9" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 13C6.10457 13 7 12.1046 7 11C7 9.89543 6.10457 9 5 9C3.89543 9 3 9.89543 3 11C3 12.1046 3.89543 13 5 13Z" stroke="black" stroke-width="1.5"/>
-<path d="M11 7C12.1046 7 13 6.10457 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5C9 6.10457 9.89543 7 11 7Z" fill="black" stroke="black" stroke-width="1.5"/>
-<path d="M4.625 3.625V8.375" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M11 7C11 9.20914 9.20914 11 7 11" stroke="black" stroke-width="1.5"/>
+<path d="M5 13C6.10457 13 7 12.1046 7 11C7 9.89543 6.10457 9 5 9C3.89543 9 3 9.89543 3 11C3 12.1046 3.89543 13 5 13Z" stroke="black" stroke-width="1.2"/>
+<path d="M11 7C12.1046 7 13 6.10457 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5C9 6.10457 9.89543 7 11 7Z" fill="black" stroke="black" stroke-width="1.2"/>
+<path d="M4.625 3.625V8.375" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M11 7C11 9.20914 9.20914 11 7 11" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M10.5 8.75V10.5C8.43097 10.5 7.56903 10.5 5.5 10.5V10L10.5 6V5.5H5.5V7.25" stroke="black" stroke-width="1.5"/>
+ <path d="M10.5 8.75V10.5C8.43097 10.5 7.56903 10.5 5.5 10.5V10L10.5 6V5.5H5.5V7.25" stroke="black" stroke-width="1.2"/>
<path d="M1.5 8.5C1.77614 8.5 2 8.27614 2 8C2 7.72386 1.77614 7.5 1.5 7.5C1.22386 7.5 1 7.72386 1 8C1 8.27614 1.22386 8.5 1.5 8.5Z" fill="black"/>
<path d="M2.49976 6.33002C2.7759 6.33002 2.99976 6.10616 2.99976 5.83002C2.99976 5.55387 2.7759 5.33002 2.49976 5.33002C2.22361 5.33002 1.99976 5.55387 1.99976 5.83002C1.99976 6.10616 2.22361 6.33002 2.49976 6.33002Z" fill="black"/>
<path d="M2.49976 10.66C2.7759 10.66 2.99976 10.4361 2.99976 10.16C2.99976 9.88383 2.7759 9.65997 2.49976 9.65997C2.22361 9.65997 1.99976 9.88383 1.99976 10.16C1.99976 10.4361 2.22361 10.66 2.49976 10.66Z" fill="black"/>
@@ -1,8 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3.5 6.66666V8.66666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.5 5V11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.5 3V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.5 5.33334V10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.5 4V12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13.5 6.66666V8.66666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.5 6.66666V8.66666" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.5 5V11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.5 3V13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.5 5.33334V10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.5 4V12" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.5 6.66666V8.66666" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M13 11V11.8374C13 11.9431 12.9665 12.046 12.9044 12.1315L12.1498 13.1691C12.0557 13.2985 11.9054 13.375 11.7454 13.375H4.25461C4.09464 13.375 3.94433 13.2985 3.85024 13.1691L3.09563 12.1315C3.03348 12.046 3 11.9431 3 11.8374V3" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M13 11V11.8374C13 11.9431 12.9665 12.046 12.9044 12.1315L12.1498 13.1691C12.0557 13.2985 11.9054 13.375 11.7454 13.375H4.25461C4.09464 13.375 3.94433 13.2985 3.85024 13.1691L3.09563 12.1315C3.03348 12.046 3 11.9431 3 11.8374V3" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
<path d="M3 13V11L8 12H13V13H3Z" fill="black"/>
-<path d="M6.63246 3.04418C7.44914 3.31641 8 4.08069 8 4.94155V11.7306C8 12.0924 7.62757 12.3345 7.29693 12.1875L3.79693 10.632C3.61637 10.5518 3.5 10.3727 3.5 10.1751V2.69374C3.5 2.35246 3.83435 2.11148 4.15811 2.2194L6.63246 3.04418Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.5 3C8.67157 3 8 3.67157 8 4.5V13C8 12.1954 11.2366 12.0382 12.5017 12.0075C12.7778 12.0008 13 11.7761 13 11.5V3.5C13 3.22386 12.7761 3 12.5 3H9.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.63246 3.04418C7.44914 3.31641 8 4.08069 8 4.94155V11.7306C8 12.0924 7.62757 12.3345 7.29693 12.1875L3.79693 10.632C3.61637 10.5518 3.5 10.3727 3.5 10.1751V2.69374C3.5 2.35246 3.83435 2.11148 4.15811 2.2194L6.63246 3.04418Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.5 3C8.67157 3 8 3.67157 8 4.5V13C8 12.1954 11.2366 12.0382 12.5017 12.0075C12.7778 12.0008 13 11.7761 13 11.5V3.5C13 3.22386 12.7761 3 12.5 3H9.5Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M11.0426 5.32305L11.0426 5.32306L11.0457 5.32471C12.4534 6.05668 13.25 7.21804 13.25 8.42984C13.25 9.40862 12.7315 10.3471 11.7886 11.0652C10.845 11.7839 9.50819 12.25 8 12.25C6.49181 12.25 5.155 11.7839 4.21141 11.0652C3.2685 10.3471 2.75 9.40862 2.75 8.42984C2.75 7.21804 3.54655 6.05668 4.95426 5.32471L4.95427 5.32473L4.95849 5.3225C5.44976 5.06306 5.93128 4.79038 6.4063 4.50125C6.82126 4.25139 7.14467 4.05839 7.42857 3.92422C7.71398 3.78934 7.88783 3.75 8 3.75C8.28571 3.75 8.57685 3.89469 9.43489 4.41073L9.43488 4.41075L9.43944 4.41345C9.47377 4.43377 9.50881 4.45453 9.54456 4.47572C9.94472 4.71289 10.4345 5.00316 11.0426 5.32305Z" stroke="black" stroke-width="1.5"/>
+ <path d="M11.0426 5.32305L11.0426 5.32306L11.0457 5.32471C12.4534 6.05668 13.25 7.21804 13.25 8.42984C13.25 9.40862 12.7315 10.3471 11.7886 11.0652C10.845 11.7839 9.50819 12.25 8 12.25C6.49181 12.25 5.155 11.7839 4.21141 11.0652C3.2685 10.3471 2.75 9.40862 2.75 8.42984C2.75 7.21804 3.54655 6.05668 4.95426 5.32471L4.95427 5.32473L4.95849 5.3225C5.44976 5.06306 5.93128 4.79038 6.4063 4.50125C6.82126 4.25139 7.14467 4.05839 7.42857 3.92422C7.71398 3.78934 7.88783 3.75 8 3.75C8.28571 3.75 8.57685 3.89469 9.43489 4.41073L9.43488 4.41075L9.43944 4.41345C9.47377 4.43377 9.50881 4.45453 9.54456 4.47572C9.94472 4.71289 10.4345 5.00316 11.0426 5.32305Z" stroke="black" stroke-width="1.2"/>
<path d="M13 7C15.5 12.5 7.92993 15 3.92993 11" stroke="black" stroke-width="1.25"/>
<circle cx="6" cy="7.75" r="1" fill="black"/>
<circle cx="10" cy="7.75" r="1" fill="black"/>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.63281 6.66406L7.99344 9.89844L11.3672 6.66406" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.63281 6.66406L7.99344 9.89844L11.3672 6.66406" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M9.35938 4.63281L6.125 7.99344L9.35938 11.3672" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.35938 4.63281L6.125 7.99344L9.35938 11.3672" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.64062 4.64062L9.89062 8.00125L6.64062 11.375" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.64062 4.64062L9.89062 8.00125L6.64062 11.375" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.63281 9.36719L7.99344 6.13281L11.3672 9.36719" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.63281 9.36719L7.99344 6.13281L11.3672 9.36719" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.78125 3C3.90625 3 3.90625 4.5 3.90625 5.5C3.90625 6.5 3.40625 7.50106 2.40625 8C3.40625 8.50106 3.90625 9.5 3.90625 10.5C3.90625 11.5 3.90625 13 5.78125 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.2422 3C12.1172 3 12.1172 4.5 12.1172 5.5C12.1172 6.5 12.6172 7.50106 13.6172 8C12.6172 8.50106 12.1172 9.5 12.1172 10.5C12.1172 11.5 12.1172 13 10.2422 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.78125 3C3.90625 3 3.90625 4.5 3.90625 5.5C3.90625 6.5 3.40625 7.50106 2.40625 8C3.40625 8.50106 3.90625 9.5 3.90625 10.5C3.90625 11.5 3.90625 13 5.78125 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.2422 3C12.1172 3 12.1172 4.5 12.1172 5.5C12.1172 6.5 12.6172 7.50106 13.6172 8C12.6172 8.50106 12.1172 9.5 12.1172 10.5C12.1172 11.5 12.1172 13 10.2422 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3061 6.5778C11.2118 7.65229 5.59818 7.64305 3.55456 6.49718C3.34826 6.38151 3.00857 6.53238 3.02549 6.76829C3.25878 10.0209 5.09256 13 8.49998 13C11.8648 13 13.6714 10.1058 13.9591 6.91373C13.9819 6.66029 13.5325 6.46164 13.3061 6.5778Z" fill="black"/>
<path d="M10.0555 5.53646C10.4444 6.0547 11.9998 6.57297 12 4.49998C12.0002 2.42709 9.66679 2.94528 8.50013 4.49998C7.33348 6.05467 4.99991 6.57294 5 4.5C5.00009 2.42706 6.55548 2.94528 6.94443 3.46352" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
- <circle cx="4" cy="10.5" r="1.75" stroke="black" stroke-width="1.5"/>
+ <circle cx="4" cy="10.5" r="1.75" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.46115 9.43419C8.30678 9.43419 9.92229 8.43411 9.92229 6.21171C9.92229 3.98933 8.30678 2.98926 6.46115 2.98926C4.61553 2.98926 3 3.98933 3 6.21171C3 7.028 3.21794 7.67935 3.58519 8.17685C3.7184 8.35732 3.69033 8.77795 3.58387 8.97539C3.32908 9.44793 3.81048 9.9657 4.33372 9.84571C4.72539 9.75597 5.13621 9.63447 5.49574 9.4715C5.62736 9.41181 5.7727 9.38777 5.91631 9.40402C6.09471 9.42416 6.27678 9.43419 6.46115 9.43419Z" fill="black" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.3385 7.24835C12.7049 7.74561 12.9224 8.39641 12.9224 9.2117C12.9224 10.028 12.7044 10.6793 12.3372 11.1768C12.204 11.3573 12.232 11.7779 12.3385 11.9754C12.5933 12.4479 12.1119 12.9657 11.5886 12.8457C11.197 12.756 10.7862 12.6345 10.4266 12.4715C10.295 12.4118 10.1497 12.3878 10.0061 12.404C9.82765 12.4242 9.64558 12.4342 9.46121 12.4342C8.61469 12.4342 7.81658 12.2238 7.20055 11.7816" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.3385 7.24835C12.7049 7.74561 12.9224 8.39641 12.9224 9.2117C12.9224 10.028 12.7044 10.6793 12.3372 11.1768C12.204 11.3573 12.232 11.7779 12.3385 11.9754C12.5933 12.4479 12.1119 12.9657 11.5886 12.8457C11.197 12.756 10.7862 12.6345 10.4266 12.4715C10.295 12.4118 10.1497 12.3878 10.0061 12.404C9.82765 12.4242 9.64558 12.4342 9.46121 12.4342C8.61469 12.4342 7.81658 12.2238 7.20055 11.7816" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M12 11.25H11.25V12V13.25H7.31066L2.35809 8.29743L3.60151 4.56717L7.81937 2.88003L13.25 8.31066V11.25H12Z" stroke="black" stroke-width="1.5"/>
+ <path d="M12 11.25H11.25V12V13.25H7.31066L2.35809 8.29743L3.60151 4.56717L7.81937 2.88003L13.25 8.31066V11.25H12Z" stroke="black" stroke-width="1.2"/>
<path d="M10.928 11.4328L3.5 4.5H9.89645C9.96275 4.5 10.0263 4.52634 10.0732 4.57322L13.4268 7.92678C13.4737 7.97366 13.5 8.03725 13.5 8.10355V11.25C13.5 11.3881 13.3881 11.5 13.25 11.5H11.0985C11.0352 11.5 10.9743 11.476 10.928 11.4328Z" fill="black"/>
<path d="M4 11L4.5 5C3.97221 4.7361 3.33305 5.00085 3.14645 5.56066L2.19544 8.41368C2.07566 8.77302 2.16918 9.16918 2.43702 9.43702L4 11Z" fill="black"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.99993 6.85713C11.1558 6.85713 13.7142 5.83379 13.7142 4.57142C13.7142 3.30905 11.1558 2.28571 7.99993 2.28571C4.84402 2.28571 2.28564 3.30905 2.28564 4.57142C2.28564 5.83379 4.84402 6.85713 7.99993 6.85713Z" fill="black" stroke="black" stroke-width="1.5"/>
-<path d="M13.7142 4.57141V11.4286C13.7142 12.691 11.1558 13.7143 7.99993 13.7143C4.84402 13.7143 2.28564 12.691 2.28564 11.4286V4.57141" stroke="black" stroke-width="1.5"/>
-<path d="M13.7142 8C13.7142 9.26237 11.1558 10.2857 7.99993 10.2857C4.84402 10.2857 2.28564 9.26237 2.28564 8" stroke="black" stroke-width="1.5"/>
+<path d="M7.99993 6.85713C11.1558 6.85713 13.7142 5.83379 13.7142 4.57142C13.7142 3.30905 11.1558 2.28571 7.99993 2.28571C4.84402 2.28571 2.28564 3.30905 2.28564 4.57142C2.28564 5.83379 4.84402 6.85713 7.99993 6.85713Z" fill="black" stroke="black" stroke-width="1.2"/>
+<path d="M13.7142 4.57141V11.4286C13.7142 12.691 11.1558 13.7143 7.99993 13.7143C4.84402 13.7143 2.28564 12.691 2.28564 11.4286V4.57141" stroke="black" stroke-width="1.2"/>
+<path d="M13.7142 8C13.7142 9.26237 11.1558 10.2857 7.99993 10.2857C4.84402 10.2857 2.28564 9.26237 2.28564 8" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8.5 3L8.5 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5 6.5H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5 13H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.5 3L8.5 10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 6.5H12" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 13H12" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M13.5413 8.31248C13.6529 8.11911 13.6529 7.88086 13.5413 7.68748L11.0413 3.35736C10.9296 3.16398 10.7233 3.04486 10.5 3.04486H5.5C5.27671 3.04486 5.07038 3.16398 4.95873 3.35736L2.45873 7.68748C2.34709 7.88086 2.34709 8.11911 2.45873 8.31248L4.95873 12.6426C5.07038 12.836 5.27671 12.9551 5.5 12.9551H10.5C10.7233 12.9551 10.9296 12.836 11.0413 12.6426L13.5413 8.31248Z" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
+ <path d="M13.5413 8.31248C13.6529 8.11911 13.6529 7.88086 13.5413 7.68748L11.0413 3.35736C10.9296 3.16398 10.7233 3.04486 10.5 3.04486H5.5C5.27671 3.04486 5.07038 3.16398 4.95873 3.35736L2.45873 7.68748C2.34709 7.88086 2.34709 8.11911 2.45873 8.31248L4.95873 12.6426C5.07038 12.836 5.27671 12.9551 5.5 12.9551H10.5C10.7233 12.9551 10.9296 12.836 11.0413 12.6426L13.5413 8.31248Z" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
<path d="M7.74994 5.14432C7.90464 5.055 8.09524 5.055 8.24994 5.14432L10.348 6.35564C10.5027 6.44496 10.598 6.61002 10.598 6.78866V9.2113C10.598 9.38994 10.5027 9.555 10.348 9.64432L8.24994 10.8556C8.09524 10.945 7.90464 10.945 7.74994 10.8556L5.65186 9.64432C5.49716 9.555 5.40186 9.38994 5.40186 9.2113V6.78866C5.40186 6.61002 5.49716 6.44496 5.65186 6.35564L7.74994 5.14432Z" fill="black"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 5H11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M3 8H13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M3 11H9" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M3 5H11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M3 8H13" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M3 11H9" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8.26046 3.97337C8.3527 4.17617 8.4795 4.47151 8.57375 4.69341C8.65258 4.87898 8.83437 4.99999 9.03599 4.99999H12.5C12.7761 4.99999 13 5.22385 13 5.49999V12.125C13 12.4011 12.7761 12.625 12.5 12.625H3.5C3.22386 12.625 3 12.4011 3 12.125V3.86932C3 3.59318 3.22386 3.36932 3.5 3.36932H7.34219C7.74141 3.36932 8.09483 3.60924 8.26046 3.97337Z" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M8.26046 3.97337C8.3527 4.17617 8.4795 4.47151 8.57375 4.69341C8.65258 4.87898 8.83437 4.99999 9.03599 4.99999H12.5C12.7761 4.99999 13 5.22385 13 5.49999V12.125C13 12.4011 12.7761 12.625 12.5 12.625H3.5C3.22386 12.625 3 12.4011 3 12.125V3.86932C3 3.59318 3.22386 3.36932 3.5 3.36932H7.34219C7.74141 3.36932 8.09483 3.60924 8.26046 3.97337Z" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.42782 7.2487C4.43495 6.97194 4.65009 6.75 4.91441 6.75H13.5293C13.7935 6.75 14.007 6.97194 13.9998 7.2487C13.9628 8.6885 13.7533 12.75 12.5721 12.75H3.375C4.55631 12.75 4.3907 8.6885 4.42782 7.2487Z" fill="black" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
-<path d="M5.19598 12.625H3.66515C3.42618 12.625 3.22289 12.4453 3.18626 12.2017L1.94333 3.93602C1.89776 3.63295 2.12496 3.35938 2.42223 3.35938H5.78585C6.11241 3.35938 6.41702 3.52903 6.59618 3.81071L6.94517 4.35938H9.92811C10.4007 4.35938 10.8044 4.71102 10.8836 5.1917L11.1251 6.65624" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.42782 7.2487C4.43495 6.97194 4.65009 6.75 4.91441 6.75H13.5293C13.7935 6.75 14.007 6.97194 13.9998 7.2487C13.9628 8.6885 13.7533 12.75 12.5721 12.75H3.375C4.55631 12.75 4.3907 8.6885 4.42782 7.2487Z" fill="black" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
+<path d="M5.19598 12.625H3.66515C3.42618 12.625 3.22289 12.4453 3.18626 12.2017L1.94333 3.93602C1.89776 3.63295 2.12496 3.35938 2.42223 3.35938H5.78585C6.11241 3.35938 6.41702 3.52903 6.59618 3.81071L6.94517 4.35938H9.92811C10.4007 4.35938 10.8044 4.71102 10.8836 5.1917L11.1251 6.65624" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10.3352 13.2519H12.375M4.49719 13.2519L8.00001 2.74811L11.5028 13.2519M3.625 13.2519H5.6648M9.74908 9.16761H6.25095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.3352 13.2519H12.375M4.49719 13.2519L8.00001 2.74811L11.5028 13.2519M3.625 13.2519H5.6648M9.74908 9.16761H6.25095" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 13C6.10457 13 7 12.1046 7 11C7 9.89543 6.10457 9 5 9C3.89543 9 3 9.89543 3 11C3 12.1046 3.89543 13 5 13Z" stroke="black" stroke-width="1.5"/>
-<path d="M11 7C12.1046 7 13 6.10457 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5C9 6.10457 9.89543 7 11 7Z" fill="black" stroke="black" stroke-width="1.5"/>
-<path d="M4.625 3.625V8.375" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M11 7C11 9.20914 9.20914 11 7 11" stroke="black" stroke-width="1.5"/>
+<path d="M5 13C6.10457 13 7 12.1046 7 11C7 9.89543 6.10457 9 5 9C3.89543 9 3 9.89543 3 11C3 12.1046 3.89543 13 5 13Z" stroke="black" stroke-width="1.2"/>
+<path d="M11 7C12.1046 7 13 6.10457 13 5C13 3.89543 12.1046 3 11 3C9.89543 3 9 3.89543 9 5C9 6.10457 9.89543 7 11 7Z" fill="black" stroke="black" stroke-width="1.2"/>
+<path d="M4.625 3.625V8.375" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M11 7C11 9.20914 9.20914 11 7 11" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.3848 9.30444C7.3848 9.30444 7.53254 10.2646 8.53248 10.0882C9.53242 9.91193 9.36378 8.95549 9.36378 8.95549" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="#FF7676" stroke-opacity="0.52" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="#FF7676" stroke-opacity="0.52" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="6.25098" cy="7.75" r="0.75" fill="black"/>
<circle cx="10.1035" cy="7.25" r="0.75" fill="black"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 5L10.5981 9.5H5.40192L8 5Z" stroke="black" stroke-width="1.5"/>
-<path d="M8 3L12.3301 5.5V10.5L8 13L3.66987 10.5V5.5L8 3Z" stroke="black" stroke-width="1.5"/>
+<path d="M8 5L10.5981 9.5H5.40192L8 5Z" stroke="black" stroke-width="1.2"/>
+<path d="M8 3L12.3301 5.5V10.5L8 13L3.66987 10.5V5.5L8 3Z" stroke="black" stroke-width="1.2"/>
<circle cx="3.5" cy="5.5" r="1" fill="black"/>
<circle cx="12.5" cy="5.5" r="1" fill="black"/>
<circle cx="8" cy="3" r="1" fill="black"/>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.2795 3.63849L8.7478 12.0142" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M7.26626 3.99597L4.73462 12.3717" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M4.15991 6.37988H12.9099" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M3.09839 9.62408H11.8484" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M11.2795 3.63849L8.7478 12.0142" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M7.26626 3.99597L4.73462 12.3717" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M4.15991 6.37988H12.9099" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M3.09839 9.62408H11.8484" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<rect x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.5"/>
+<rect x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.2"/>
<path d="M6.74217 7.13317V4.26172V4.13672H6.61717H5.50781H5.38281V4.26172V8.8824V9.07567L5.55908 8.9964L6.33378 8.64803L6.33378 8.64803L6.33389 8.64798L6.33443 8.64774L6.3347 8.64762L6.3369 8.64667L6.34717 8.64225C6.35635 8.63832 6.37013 8.63248 6.38816 8.62501C6.42423 8.61006 6.47729 8.58857 6.54454 8.56272C6.67911 8.511 6.87014 8.44194 7.09531 8.3729C7.54765 8.2342 8.12948 8.09817 8.66592 8.09817C8.92095 8.09817 9.05676 8.16584 9.12979 8.241C9.2037 8.31708 9.23311 8.42118 9.23311 8.53361V11.7383V11.8633H9.35811H10.4922H10.6172V11.7383V8.53361V8.53322C10.6172 8.43725 10.6172 7.81276 10.1093 7.32276C9.86619 7.08825 9.42187 6.80777 8.69373 6.80777C8.00022 6.80777 7.28721 6.96253 6.74217 7.13317ZM8.45652 5.91932L8.29694 6.12171H8.55468H9.66094H9.71713L9.75443 6.07969C10.2672 5.502 10.5291 4.91889 10.616 4.27855L10.6353 4.13672H10.4922H9.38592H9.28153L9.26292 4.23944C9.1561 4.82914 8.88874 5.37113 8.45652 5.91932Z" fill="black" stroke="black" stroke-width="0.25"/>
<path d="M5.38281 11.7383V12.01L5.58915 11.8332L6.83447 10.766L6.94523 10.671L6.83447 10.5761L5.58915 9.50891L5.38281 9.33207V9.60382V11.7383Z" fill="black" stroke="black" stroke-width="0.25"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M9.15741 4.17108L6.84277 11.8289" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M4.74951 6L2.74951 8L4.74951 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.25 10L13.25 8L11.25 6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.15741 4.17108L6.84277 11.8289" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M4.74951 6L2.74951 8L4.74951 10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.25 10L13.25 8L11.25 6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 4C7.91421 4 8.25 3.66421 8.25 3.25C8.25 2.83579 7.91421 2.5 7.5 2.5C7.08579 2.5 6.75 2.83579 6.75 3.25C6.75 3.66421 7.08579 4 7.5 4Z" fill="black" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 8L10 6L12 8H8Z" fill="black"/>
-<path d="M3 11L6 8L8.375 10.375" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7 9L8.5 7.5L10 6L11.5 7.5L13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4.375 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H8.35938M10.6406 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H11.125" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M3 11L6 8L8.375 10.375" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7 9L8.5 7.5L10 6L11.5 7.5L13 9" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.375 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H8.35938M10.6406 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H11.125" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.99219 8.56632C6.5 9 10.5415 8.99989 12 7.99995C13.4585 7 12.5 9.49999 12.5 10" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M11.5 13C9 13.5781 6 13.5938 4 13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M10.0156 10.9844C8.51562 11.2031 6.5 11.2031 5 10.8906" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M6.5625 6.5C6.34375 6 6.06838 4.93125 6.99999 4.03125C7.93161 3.13125 8.58082 3.33636 9.00002 2" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M9.18477 6.50002C8.88168 6.05002 8.40637 5.71875 9.00014 5.40625C9.5939 5.09375 10.3126 4.65625 10.8751 3.53125" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M4.99219 8.56632C6.5 9 10.5415 8.99989 12 7.99995C13.4585 7 12.5 9.49999 12.5 10" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M11.5 13C9 13.5781 6 13.5938 4 13" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M10.0156 10.9844C8.51562 11.2031 6.5 11.2031 5 10.8906" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M6.5625 6.5C6.34375 6 6.06838 4.93125 6.99999 4.03125C7.93161 3.13125 8.58082 3.33636 9.00002 2" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M9.18477 6.50002C8.88168 6.05002 8.40637 5.71875 9.00014 5.40625C9.5939 5.09375 10.3126 4.65625 10.8751 3.53125" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.5"/>
+<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 6.5C3.25 5.80964 3.80964 5.25 4.5 5.25H11.5C12.1904 5.25 12.75 5.80964 12.75 6.5V12.5C12.75 13.1904 12.1904 13.75 11.5 13.75H4.5C3.80964 13.75 3.25 13.1904 3.25 12.5V6.5ZM8.75 9.66146C8.90559 9.48517 9 9.25361 9 9C9 8.44772 8.55228 8 8 8C7.44772 8 7 8.44772 7 9C7 9.25361 7.09441 9.48517 7.25 9.66146V11C7.25 11.4142 7.58579 11.75 8 11.75C8.41421 11.75 8.75 11.4142 8.75 11V9.66146Z" fill="black"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M13 13L10.4138 10.4138M3 7.31034C3 4.92981 4.92981 3 7.31034 3C9.6909 3 11.6207 4.92981 11.6207 7.31034C11.6207 9.6909 9.6909 11.6207 7.31034 11.6207C4.92981 11.6207 3 9.6909 3 7.31034Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13 13L10.4138 10.4138M3 7.31034C3 4.92981 4.92981 3 7.31034 3C9.6909 3 11.6207 4.92981 11.6207 7.31034C11.6207 9.6909 9.6909 11.6207 7.31034 11.6207C4.92981 11.6207 3 9.6909 3 7.31034Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,8 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.00005 4.76556L4.76569 2.74996M6.00005 4.76556L3.75 4.76563M6.00005 4.76556L7.25006 4.7656" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M10.0232 11.2311L11.2675 13.2406M10.0232 11.2311L12.2732 11.2199M10.0232 11.2311L8.7732 11.2373" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M9.99025 4.91551L10.9985 2.77781M9.99025 4.91551L8.75599 3.03419M9.99025 4.91551L10.6759 5.9607" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M6.0323 11.1009L5.03465 13.2436M6.0323 11.1009L7.27585 12.9761M6.0323 11.1009L5.34151 10.0592" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M11.883 8.19023L14.2466 8.19287M11.883 8.19023L13.0602 6.27268M11.883 8.19023L11.229 9.25547" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M4.12354 7.8356L1.76002 7.84465M4.12354 7.8356L2.95585 9.75894M4.12354 7.8356L4.7723 6.76713" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M6.00005 4.76556L4.76569 2.74996M6.00005 4.76556L3.75 4.76563M6.00005 4.76556L7.25006 4.7656" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M10.0232 11.2311L11.2675 13.2406M10.0232 11.2311L12.2732 11.2199M10.0232 11.2311L8.7732 11.2373" stroke="black" stroke-opacity="0.5" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M9.99025 4.91551L10.9985 2.77781M9.99025 4.91551L8.75599 3.03419M9.99025 4.91551L10.6759 5.9607" stroke="black" stroke-opacity="0.5" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M6.0323 11.1009L5.03465 13.2436M6.0323 11.1009L7.27585 12.9761M6.0323 11.1009L5.34151 10.0592" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M11.883 8.19023L14.2466 8.19287M11.883 8.19023L13.0602 6.27268M11.883 8.19023L11.229 9.25547" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M4.12354 7.8356L1.76002 7.84465M4.12354 7.8356L2.95585 9.75894M4.12354 7.8356L4.7723 6.76713" stroke="black" stroke-opacity="0.5" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,8 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.03125 3.96875C3.03125 3.41647 3.47897 2.96875 4.03125 2.96875H6V13H4.03125C3.47897 13 3.03125 12.5523 3.03125 12V3.96875Z" fill="black"/>
-<path d="M12.5 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H12.5C12.7761 13 13 12.7761 13 12.5V3.5C13 3.22386 12.7761 3 12.5 3Z" stroke="black" stroke-width="1.5"/>
-<path d="M10.5 5.75H8.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.5 8H8.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.5 10.25H8.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6 3V14" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H12.5C12.7761 13 13 12.7761 13 12.5V3.5C13 3.22386 12.7761 3 12.5 3Z" stroke="black" stroke-width="1.2"/>
+<path d="M10.5 5.75H8.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.5 8H8.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.5 10.25H8.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6 3V14" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M2.62671 4.88474L7.99977 7.78519M2.62671 4.88474L2.63131 10.9001L8.00436 13.8005M2.62671 4.88474L5.31111 3.54213M7.99977 7.78519L8.00436 13.8005M7.99977 7.78519L10.6841 6.33086M8.00436 13.8005L13.3729 10.8919L13.3683 4.87654M5.31111 3.54213L7.9955 2.19952L13.3683 4.87654M5.31111 3.54213L10.6841 6.33086M10.6841 6.33086L13.3683 4.87654" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.62671 4.88474L7.99977 7.78519M2.62671 4.88474L2.63131 10.9001L8.00436 13.8005M2.62671 4.88474L5.31111 3.54213M7.99977 7.78519L8.00436 13.8005M7.99977 7.78519L10.6841 6.33086M8.00436 13.8005L13.3729 10.8919L13.3683 4.87654M5.31111 3.54213L7.9955 2.19952L13.3683 4.87654M5.31111 3.54213L10.6841 6.33086M10.6841 6.33086L13.3683 4.87654" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.03125 13.5625V7.78125L2.5625 4.9375V10.75L8.03125 13.5625Z" fill="black"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M13 9C13 8.32138 12.9375 7.5 12.7188 6.75C12.0625 7.53125 10.875 8.1875 10 8.5C10.75 5.90625 9.5625 3.1875 8 3C8 4.96875 7.625 5.90625 6.5 7.5C5 5 3.5 6.5 3 7C3.5 7.5 4.21832 8.24064 4.34375 9.3125C4.6875 12.25 6.75 13 8.5 13C10.25 13 10.5 11 12.5 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M13 9C13 8.32138 12.9375 7.5 12.7188 6.75C12.0625 7.53125 10.875 8.1875 10 8.5C10.75 5.90625 9.5625 3.1875 8 3C8 4.96875 7.625 5.90625 6.5 7.5C5 5 3.5 6.5 3 7C3.5 7.5 4.21832 8.24064 4.34375 9.3125C4.6875 12.25 6.75 13 8.5 13C10.25 13 10.5 11 12.5 12" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.03125 9.15625C5.87694 9.15625 6.5625 8.47069 6.5625 7.625C6.5625 6.77931 5.87694 6.09375 5.03125 6.09375C4.18556 6.09375 3.5 6.77931 3.5 7.625C3.5 8.47069 4.18556 9.15625 5.03125 9.15625Z" fill="black"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 4V12M12 8H4" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M8 4V12M12 8H4" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,12 +1,12 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M3 3.86328H9.51563" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M12 3.86328H13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M10.6406 6.62628H13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M5.79688 6.62628H8.15625" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M3 6.62628H3.35937" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M8.15625 9.37372H13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M3 9.37372H5.64062" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M3 12.1094H4.54687" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M6.97656 12.1094H9.35938" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
- <path d="M11.8203 12.1094H13" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+ <path d="M3 3.86328H9.51563" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M12 3.86328H13" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M10.6406 6.62628H13" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M5.79688 6.62628H8.15625" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M3 6.62628H3.35937" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M8.15625 9.37372H13" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M3 9.37372H5.64062" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M3 12.1094H4.54687" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M6.97656 12.1094H9.35938" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+ <path d="M11.8203 12.1094H13" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3.03125 3V3.03125M3.03125 3.03125V9M3.03125 3.03125C3.03125 5 6 5 6 5M3.03125 9C3.03125 11 6 11 6 11M3.03125 9V12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.03125 3V3.03125M3.03125 3.03125V9M3.03125 3.03125C3.03125 5 6 5 6 5M3.03125 9C3.03125 11 6 11 6 11M3.03125 9V12" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="8" y="2.5" width="6" height="5" rx="1.5" fill="black"/>
<rect x="8" y="8.46875" width="6" height="5.0625" rx="1.5" fill="black"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.18452 2.91638C6.01625 2.91638 4.98489 3.77623 4.91991 4.94678H4.72024C3.81569 4.94678 3 5.63731 3 6.58698V8.10978C3 9.05945 3.81569 9.74998 4.72024 9.74998H5.33631C5.67376 9.74998 6.02976 9.48559 6.02976 9.06153C6.02976 8.46056 6.51694 7.97338 7.11791 7.97338H8.27976C9.18431 7.97338 10 7.28286 10 6.33318V5.06418C10 3.83417 8.93913 2.91638 7.73214 2.91638H7.18452Z" stroke="black" stroke-width="1.5"/>
-<path d="M8.79613 13.0836C9.97889 13.0836 11.0103 12.2025 11.0702 11.0191H11.2738C12.1885 11.0191 13 10.3146 13 9.36187V7.8135C13 6.86077 12.1885 6.15625 11.2738 6.15625H10.6544C10.3099 6.15625 9.96057 6.42749 9.96057 6.84577C9.96057 7.46262 9.46051 7.96268 8.84365 7.96268H7.69494C6.78027 7.96268 5.96875 8.6672 5.96875 9.61993V10.9102C5.96875 12.148 7.02678 13.0836 8.24554 13.0836H8.79613Z" stroke="black" stroke-width="1.5"/>
+<path d="M7.18452 2.91638C6.01625 2.91638 4.98489 3.77623 4.91991 4.94678H4.72024C3.81569 4.94678 3 5.63731 3 6.58698V8.10978C3 9.05945 3.81569 9.74998 4.72024 9.74998H5.33631C5.67376 9.74998 6.02976 9.48559 6.02976 9.06153C6.02976 8.46056 6.51694 7.97338 7.11791 7.97338H8.27976C9.18431 7.97338 10 7.28286 10 6.33318V5.06418C10 3.83417 8.93913 2.91638 7.73214 2.91638H7.18452Z" stroke="black" stroke-width="1.2"/>
+<path d="M8.79613 13.0836C9.97889 13.0836 11.0103 12.2025 11.0702 11.0191H11.2738C12.1885 11.0191 13 10.3146 13 9.36187V7.8135C13 6.86077 12.1885 6.15625 11.2738 6.15625H10.6544C10.3099 6.15625 9.96057 6.42749 9.96057 6.84577C9.96057 7.46262 9.46051 7.96268 8.84365 7.96268H7.69494C6.78027 7.96268 5.96875 8.6672 5.96875 9.61993V10.9102C5.96875 12.148 7.02678 13.0836 8.24554 13.0836H8.79613Z" stroke="black" stroke-width="1.2"/>
<path d="M7.20312 6.01758C7.64323 6.01758 8 5.6608 8 5.2207C8 4.7806 7.64323 4.42383 7.20312 4.42383C6.76302 4.42383 6.40625 4.7806 6.40625 5.2207C6.40625 5.6608 6.76302 6.01758 7.20312 6.01758Z" fill="black"/>
<path d="M8.79687 11.5939C9.23698 11.5939 9.59375 11.2372 9.59375 10.7971C9.59375 10.357 9.23698 10.0002 8.79687 10.0002C8.35677 10.0002 8 10.357 8 10.7971C8 11.2372 8.35677 11.5939 8.79687 11.5939Z" fill="black"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.99988 13C5.97267 13 4.22723 11.7936 3.44238 10.0595M7.99988 3C10.1122 3 11.9185 4.30981 12.6511 6.16152" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.99988 13C5.97267 13 4.22723 11.7936 3.44238 10.0595M7.99988 3C10.1122 3 11.9185 4.30981 12.6511 6.16152" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.65625 3.29688C3.00143 3.29688 3.28125 3.01705 3.28125 2.67188C3.28125 2.3267 3.00143 2.04688 2.65625 2.04688C2.31107 2.04688 2.03125 2.3267 2.03125 2.67188C2.03125 3.01705 2.31107 3.29688 2.65625 3.29688Z" fill="black"/>
<path d="M4.71094 3.29688C5.05612 3.29688 5.33594 3.01705 5.33594 2.67188C5.33594 2.3267 5.05612 2.04688 4.71094 2.04688C4.36576 2.04688 4.08594 2.3267 4.08594 2.67188C4.08594 3.01705 4.36576 3.29688 4.71094 3.29688Z" fill="black"/>
<path d="M5.96094 4.99219C6.30612 4.99219 6.58594 4.71237 6.58594 4.36719C6.58594 4.02201 6.30612 3.74219 5.96094 3.74219C5.61576 3.74219 5.33594 4.02201 5.33594 4.36719C5.33594 4.71237 5.61576 4.99219 5.96094 4.99219Z" fill="black"/>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M8.5 6.88672C10.433 6.88672 12 8.45372 12 10.3867V13M12 13L14 11M12 13L10 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M8.5 6.88672C10.433 6.88672 12 8.45372 12 10.3867V13M12 13L14 11M12 13L10 11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.27935 10.9821C5.32063 10.4038 4.9204 9.89049 4.35998 9.80276L3.60081 9.68387C3.37979 9.64945 3.20167 9.48001 3.15225 9.25614L3.01378 8.63511C2.96382 8.41235 3.05233 8.1807 3.23696 8.05125L3.8631 7.61242C4.33337 7.28297 4.47456 6.6369 4.18621 6.13364L3.79467 5.45092C3.68118 5.25261 3.69801 5.00374 3.83757 4.82321L4.22314 4.32436C4.3627 4.14438 4.59621 4.06994 4.81071 4.13772L5.57531 4.37769C6.11944 4.54879 6.70048 4.26159 6.90683 3.71886L7.1811 2.99782C7.26255 2.78395 7.46345 2.64285 7.68772 2.6423L8.31007 2.64063C8.53434 2.64007 8.73579 2.78006 8.81834 2.99337L9.09965 3.72275C9.30821 4.26214 9.88655 4.54712 10.429 4.37714L11.1632 4.14716C11.3772 4.07994 11.6096 4.15382 11.7492 4.3327L12.1374 4.83099" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.27935 10.9821C5.32063 10.4038 4.9204 9.89049 4.35998 9.80276L3.60081 9.68387C3.37979 9.64945 3.20167 9.48001 3.15225 9.25614L3.01378 8.63511C2.96382 8.41235 3.05233 8.1807 3.23696 8.05125L3.8631 7.61242C4.33337 7.28297 4.47456 6.6369 4.18621 6.13364L3.79467 5.45092C3.68118 5.25261 3.69801 5.00374 3.83757 4.82321L4.22314 4.32436C4.3627 4.14438 4.59621 4.06994 4.81071 4.13772L5.57531 4.37769C6.11944 4.54879 6.70048 4.26159 6.90683 3.71886L7.1811 2.99782C7.26255 2.78395 7.46345 2.64285 7.68772 2.6423L8.31007 2.64063C8.53434 2.64007 8.73579 2.78006 8.81834 2.99337L9.09965 3.72275C9.30821 4.26214 9.88655 4.54712 10.429 4.37714L11.1632 4.14716C11.3772 4.07994 11.6096 4.15382 11.7492 4.3327L12.1374 4.83099" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5827 1.27457C10.4978 2.48798 6.32365 2.94703 3.49893 2.99561C3.22283 3.00036 3.00001 3.22384 3.00001 3.49998L3.00002 6.00003C3.00002 6.27617 3.22284 6.50038 3.49895 6.49563C6.42334 6.44533 10.7942 5.95506 12.7954 4.64327C12.927 4.557 13 4.40741 13 4.25004L13 1.50027C13 1.29426 12.7607 1.17094 12.5827 1.27457Z" fill="black"/>
<path d="M12.3072 6.51584C12.6851 6.6855 12.8539 7.12936 12.6842 7.50724C12.5145 7.88511 12.0707 8.05391 11.6928 7.88425L12.3072 6.51584ZM3 5.02142C4.32178 5.02142 6.01669 5.1159 7.68605 5.34579C9.34359 5.57406 11.0313 5.94302 12.3072 6.51584L11.6928 7.88425C10.611 7.39853 9.08921 7.05318 7.48142 6.83177C5.88546 6.61199 4.25922 6.52142 3 6.52142L3 5.02142Z" fill="black"/>
-<path d="M3 10.0214C5.581 10.0214 9.64229 10.3915 12 11.45" stroke="black" stroke-width="1.5"/>
+<path d="M3 10.0214C5.581 10.0214 9.64229 10.3915 12 11.45" stroke="black" stroke-width="1.2"/>
<path d="M12.1401 10.0067C9.94879 11.0472 6.13586 11.4503 3.49893 11.4956C3.22283 11.5004 3.00001 11.7238 3.00001 12L3.00002 14.5C3.00002 14.7762 3.22284 15.0004 3.49895 14.9956C6.42334 14.9453 10.7942 14.4551 12.7954 13.1433C12.927 13.057 13 12.9074 13 12.75L13 10.5002C13 10.0882 12.5123 9.82995 12.1401 10.0067Z" fill="black"/>
<path d="M12.1401 5.75668C9.94879 6.7972 6.13586 7.20026 3.49893 7.24561C3.22283 7.25036 3.00001 7.47384 3.00001 7.74998L3.00002 10.25C3.00002 10.5262 3.22284 10.7504 3.49895 10.7456C6.42334 10.6953 10.7942 10.2051 12.7954 8.89327C12.927 8.807 13 8.65741 13 8.50004L13 6.25023C13 5.83821 12.5123 5.57995 12.1401 5.75668Z" fill="black"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3.03125 13C3.46875 12.2812 3.68556 12.0378 4.0625 11.5312M4.0625 11.5312C4.0625 9.86595 4.27768 8.02844 4.75687 7M4.0625 11.5312C4.75687 10.5981 6.6875 8.57812 7.92188 7.54688M4.0625 11.5312C7.875 11.5312 10.0507 9.46738 11.4062 8.03125C11.5818 7.84528 11.2307 7.34164 10.9157 6.96235C10.7718 6.78906 10.8964 6.50073 11.1213 6.48823C11.6657 6.45798 12.3874 6.36175 12.5 6.06684C12.7544 5.4003 12.9585 4.2437 13.0409 3.28832C13.0541 3.13644 12.9264 3.01119 12.7745 3.0243C10.5824 3.21343 8.22052 3.5262 6.5 4.82764" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M3.03125 13C3.46875 12.2812 3.68556 12.0378 4.0625 11.5312M4.0625 11.5312C4.0625 9.86595 4.27768 8.02844 4.75687 7M4.0625 11.5312C4.75687 10.5981 6.6875 8.57812 7.92188 7.54688M4.0625 11.5312C7.875 11.5312 10.0507 9.46738 11.4062 8.03125C11.5818 7.84528 11.2307 7.34164 10.9157 6.96235C10.7718 6.78906 10.8964 6.50073 11.1213 6.48823C11.6657 6.45798 12.3874 6.36175 12.5 6.06684C12.7544 5.4003 12.9585 4.2437 13.0409 3.28832C13.0541 3.13644 12.9264 3.01119 12.7745 3.0243C10.5824 3.21343 8.22052 3.5262 6.5 4.82764" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6 6H10" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M8 6V11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M5 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H5M11 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M6 6H10" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M8 6V11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M5 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H5M11 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M2.65625 3H12.8437C13.1199 3 13.3438 3.22386 13.3438 3.5V10.3438M13.3438 13H3.15625C2.88011 13 2.65625 12.7761 2.65625 12.5V5.65625" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M10 8.01562L6.65625 10.3125V5.6875L10 8.01562Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.65625 3H12.8437C13.1199 3 13.3438 3.22386 13.3438 3.5V10.3438M13.3438 13H3.15625C2.88011 13 2.65625 12.7761 2.65625 12.5V5.65625" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M10 8.01562L6.65625 10.3125V5.6875L10 8.01562Z" fill="black" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 9.13502L4.578 3.21202L4.47016 3.02509C4.42551 2.9477 4.34295 2.90002 4.25361 2.90002H2.43302C2.24057 2.90002 2.12029 3.10836 2.21651 3.27503L7.7835 12.917C7.87972 13.0837 8.12028 13.0837 8.2165 12.917L13.7835 3.27503C13.8797 3.10836 13.7594 2.90002 13.567 2.90002H11.7443C11.655 2.90002 11.5725 2.94767 11.5278 3.02502L8 9.13502Z" fill="black"/>
-<path d="M3.5 3.65002H6.80469L8 5.73596L9.20312 3.65002H12.5234" stroke="black" stroke-width="1.5"/>
+<path d="M3.5 3.65002H6.80469L8 5.73596L9.20312 3.65002H12.5234" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.5"/>
+<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.2"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 6.5C3.25 5.80964 3.80964 5.25 4.5 5.25H11.5C12.1904 5.25 12.75 5.80964 12.75 6.5V12.5C12.75 13.1904 12.1904 13.75 11.5 13.75H4.5C3.80964 13.75 3.25 13.1904 3.25 12.5V6.5ZM8.75 9.66146C8.90559 9.48517 9 9.25361 9 9C9 8.44772 8.55228 8 8 8C7.44772 8 7 8.44772 7 9C7 9.25361 7.09441 9.48517 7.25 9.66146V11C7.25 11.4142 7.58579 11.75 8 11.75C8.41421 11.75 8.75 11.4142 8.75 11V9.66146Z" fill="black"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" clip-path="url(#a)"><path d="M13 13.5v-8L9.5 2h-6a.5.5 0 0 0-.5.5V6"/><path d="M9 2v4h4M8 9.5V13h1a1.75 1.75 0 0 0 0-3.5H8ZM6 13V9.5L4.25 12 2.5 9.5V13"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" clip-path="url(#a)"><path d="M13 13.5v-8L9.5 2h-6a.5.5 0 0 0-.5.5V6"/><path d="M9 2v4h4M8 9.5V13h1a1.75 1.75 0 0 0 0-3.5H8ZM6 13V9.5L4.25 12 2.5 9.5V13"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.27935 10.9821C5.32063 10.4038 4.9204 9.89049 4.35998 9.80276L3.60081 9.68387C3.37979 9.64945 3.20167 9.48001 3.15225 9.25614L3.01378 8.63511C2.96382 8.41235 3.05233 8.1807 3.23696 8.05125L3.8631 7.61242C4.33337 7.28297 4.47456 6.6369 4.18621 6.13364L3.79467 5.45092C3.68118 5.25261 3.69801 5.00374 3.83757 4.82321L4.22314 4.32436C4.3627 4.14438 4.59621 4.06994 4.81071 4.13772L5.57531 4.37769C6.11944 4.54879 6.70048 4.26159 6.90683 3.71886L7.1811 2.99782C7.26255 2.78395 7.46345 2.64285 7.68772 2.6423L8.31007 2.64063C8.53434 2.64007 8.73579 2.78006 8.81834 2.99337L9.09965 3.72275C9.30821 4.26214 9.88655 4.54712 10.429 4.37714L11.1632 4.14716C11.3772 4.07994 11.6096 4.15382 11.7492 4.3327L12.1374 4.83099" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.27935 10.9821C5.32063 10.4038 4.9204 9.89049 4.35998 9.80276L3.60081 9.68387C3.37979 9.64945 3.20167 9.48001 3.15225 9.25614L3.01378 8.63511C2.96382 8.41235 3.05233 8.1807 3.23696 8.05125L3.8631 7.61242C4.33337 7.28297 4.47456 6.6369 4.18621 6.13364L3.79467 5.45092C3.68118 5.25261 3.69801 5.00374 3.83757 4.82321L4.22314 4.32436C4.3627 4.14438 4.59621 4.06994 4.81071 4.13772L5.57531 4.37769C6.11944 4.54879 6.70048 4.26159 6.90683 3.71886L7.1811 2.99782C7.26255 2.78395 7.46345 2.64285 7.68772 2.6423L8.31007 2.64063C8.53434 2.64007 8.73579 2.78006 8.81834 2.99337L9.09965 3.72275C9.30821 4.26214 9.88655 4.54712 10.429 4.37714L11.1632 4.14716C11.3772 4.07994 11.6096 4.15382 11.7492 4.3327L12.1374 4.83099" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M9.87504 2H4.25001C3.91848 2 3.60055 2.12643 3.36612 2.35148C3.1317 2.57652 3 2.88174 3 3.2V12.8C3 13.1182 3.1317 13.4234 3.36612 13.6485C3.60055 13.8735 3.91848 14 4.25001 14H11.75C12.0816 14 12.3995 13.8735 12.6339 13.6485C12.8683 13.4234 13 13.1182 13 12.8V5L9.87504 2Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9 2V4.66666C9 5.02029 9.14048 5.35942 9.39053 5.60948C9.64059 5.85952 9.97972 6 10.3333 6H13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8.4534 8.5H5.73267" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.2672 10.7207H5.73267" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.87504 2H4.25001C3.91848 2 3.60055 2.12643 3.36612 2.35148C3.1317 2.57652 3 2.88174 3 3.2V12.8C3 13.1182 3.1317 13.4234 3.36612 13.6485C3.60055 13.8735 3.91848 14 4.25001 14H11.75C12.0816 14 12.3995 13.8735 12.6339 13.6485C12.8683 13.4234 13 13.1182 13 12.8V5L9.87504 2Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 2V4.66666C9 5.02029 9.14048 5.35942 9.39053 5.60948C9.64059 5.85952 9.97972 6 10.3333 6H13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.4534 8.5H5.73267" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.2672 10.7207H5.73267" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6 6H10" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M8 6V11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M5 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H5M11 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M6 6H10" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M8 6V11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M5 3H3.5C3.22386 3 3 3.22386 3 3.5V12.5C3 12.7761 3.22386 13 3.5 13H5M11 3H12.5C12.7761 3 13 3.22386 13 3.5V12.5C13 12.7761 12.7761 13 12.5 13H11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 2.5V3.5M3 3.5V9M3 3.5C3 5.46875 5.96875 5 5.96875 5M3 9C3 11 5.96875 11 5.96875 11M3 9V12.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 2.5V3.5M3 3.5V9M3 3.5C3 5.46875 5.96875 5 5.96875 5M3 9C3 11 5.96875 11 5.96875 11M3 9V12.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 3H9.5C8.67157 3 8 3.67157 8 4.5V5.5C8 6.32843 8.67157 7 9.5 7H12C12.8284 7 13.5 6.32843 13.5 5.5V4.5C13.5 3.67157 12.8284 3 12 3Z" fill="black"/>
<path d="M12 9H9.5C8.67157 9 8 9.67157 8 10.5V11.5C8 12.3284 8.67157 13 9.5 13H12C12.8284 13 13.5 12.3284 13.5 11.5V10.5C13.5 9.67157 12.8284 9 12 9Z" fill="black"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.9416 2.99643C13.08 2.79636 12.9568 2.5 12.7352 2.5H3.26475C3.04317 2.5 2.91999 2.79636 3.0584 2.99643L6.04033 7.30646C6.24713 7.60535 6.35981 7.97674 6.35981 8.3596C6.35981 9.18422 6.35981 11.4639 6.35981 12.891C6.35981 13.2285 6.59643 13.5 6.88831 13.5H9.11168C9.40357 13.5 9.64019 13.2285 9.64019 12.891C9.64019 11.4639 9.64019 9.18422 9.64019 8.3596C9.64019 7.97674 9.75289 7.60535 9.95969 7.30646L12.9416 2.99643Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.9416 2.99643C13.08 2.79636 12.9568 2.5 12.7352 2.5H3.26475C3.04317 2.5 2.91999 2.79636 3.0584 2.99643L6.04033 7.30646C6.24713 7.60535 6.35981 7.97674 6.35981 8.3596C6.35981 9.18422 6.35981 11.4639 6.35981 12.891C6.35981 13.2285 6.59643 13.5 6.88831 13.5H9.11168C9.40357 13.5 9.64019 13.2285 9.64019 12.891C9.64019 11.4639 9.64019 9.18422 9.64019 8.3596C9.64019 7.97674 9.75289 7.60535 9.95969 7.30646L12.9416 2.99643Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5.5 9.868c.474 0 .928-.18 1.263-.5.335-.321.523-.756.523-1.21 0-.944-.357-1.369-.715-2.053C5.806 4.64 6.411 3.331 8 2c.357 1.71 1.429 3.353 2.857 4.447C12.286 7.542 13 8.842 13 10.21c0 .63-.13 1.252-.38 1.833a4.781 4.781 0 0 1-1.084 1.554 5.021 5.021 0 0 1-1.623 1.038 5.191 5.191 0 0 1-3.826 0 5.02 5.02 0 0 1-1.623-1.038 4.78 4.78 0 0 1-1.083-1.554A4.615 4.615 0 0 1 3 10.211c0-.79.31-1.57.714-2.053 0 .454.188.889.523 1.21.335.32.79.5 1.263.5Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M5.5 9.868c.474 0 .928-.18 1.263-.5.335-.321.523-.756.523-1.21 0-.944-.357-1.369-.715-2.053C5.806 4.64 6.411 3.331 8 2c.357 1.71 1.429 3.353 2.857 4.447C12.286 7.542 13 8.842 13 10.21c0 .63-.13 1.252-.38 1.833a4.781 4.781 0 0 1-1.084 1.554 5.021 5.021 0 0 1-1.623 1.038 5.191 5.191 0 0 1-3.826 0 5.02 5.02 0 0 1-1.623-1.038 4.78 4.78 0 0 1-1.083-1.554A4.615 4.615 0 0 1 3 10.211c0-.79.31-1.57.714-2.053 0 .454.188.889.523 1.21.335.32.79.5 1.263.5Z"/></svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.8 13C13.1183 13 13.4235 12.8761 13.6486 12.6554C13.8735 12.4349 14 12.1356 14 11.8236V5.94118C14 5.62916 13.8735 5.32992 13.6486 5.10929C13.4235 4.88866 13.1183 4.76471 12.8 4.76471H8.06C7.8593 4.76664 7.66133 4.71919 7.48418 4.6267C7.30703 4.53421 7.15637 4.39964 7.046 4.2353L6.56 3.52941C6.45073 3.36675 6.30199 3.23322 6.1271 3.14082C5.95221 3.04842 5.75666 3.00004 5.558 3H3.2C2.88174 3 2.57651 3.12395 2.35148 3.34458C2.12643 3.56521 2 3.86445 2 4.17647V11.8236C2 12.1356 2.12643 12.4349 2.35148 12.6554C2.57651 12.8761 2.88174 13 3.2 13H12.8Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.8 13C13.1183 13 13.4235 12.8761 13.6486 12.6554C13.8735 12.4349 14 12.1356 14 11.8236V5.94118C14 5.62916 13.8735 5.32992 13.6486 5.10929C13.4235 4.88866 13.1183 4.76471 12.8 4.76471H8.06C7.8593 4.76664 7.66133 4.71919 7.48418 4.6267C7.30703 4.53421 7.15637 4.39964 7.046 4.2353L6.56 3.52941C6.45073 3.36675 6.30199 3.23322 6.1271 3.14082C5.95221 3.04842 5.75666 3.00004 5.558 3H3.2C2.88174 3 2.57651 3.12395 2.35148 3.34458C2.12643 3.56521 2 3.86445 2 4.17647V11.8236C2 12.1356 2.12643 12.4349 2.35148 12.6554C2.57651 12.8761 2.88174 13 3.2 13H12.8Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.42782 7.2487C4.43495 6.97194 4.65009 6.75 4.91441 6.75H13.5293C13.7935 6.75 14.007 6.97194 13.9998 7.2487C13.9628 8.6885 13.7533 12.75 12.5721 12.75H3.375C4.55631 12.75 4.3907 8.6885 4.42782 7.2487Z" fill="black" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
-<path d="M5.19598 12.625H3.66515C3.42618 12.625 3.22289 12.4453 3.18626 12.2017L1.94333 3.93602C1.89776 3.63295 2.12496 3.35938 2.42223 3.35938H5.78585C6.11241 3.35938 6.41702 3.52903 6.59618 3.81071L6.94517 4.35938H9.92811C10.4007 4.35938 10.8044 4.71102 10.8836 5.1917L11.1251 6.65624" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.42782 7.2487C4.43495 6.97194 4.65009 6.75 4.91441 6.75H13.5293C13.7935 6.75 14.007 6.97194 13.9998 7.2487C13.9628 8.6885 13.7533 12.75 12.5721 12.75H3.375C4.55631 12.75 4.3907 8.6885 4.42782 7.2487Z" fill="black" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
+<path d="M5.19598 12.625H3.66515C3.42618 12.625 3.22289 12.4453 3.18626 12.2017L1.94333 3.93602C1.89776 3.63295 2.12496 3.35938 2.42223 3.35938H5.78585C6.11241 3.35938 6.41702 3.52903 6.59618 3.81071L6.94517 4.35938H9.92811C10.4007 4.35938 10.8044 4.71102 10.8836 5.1917L11.1251 6.65624" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.0001 13.9999L12.7334 12.7333" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.3333 13.3334C12.4378 13.3334 13.3333 12.4379 13.3333 11.3334C13.3333 10.2288 12.4378 9.33337 11.3333 9.33337C10.2287 9.33337 9.33325 10.2288 9.33325 11.3334C9.33325 12.4379 10.2287 13.3334 11.3333 13.3334Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6 13H3.2C2.88174 13 2.57651 12.8761 2.35148 12.6554C2.12643 12.4349 2 12.1356 2 11.8236V4.17647C2 3.86445 2.12643 3.56521 2.35148 3.34458C2.57651 3.12395 2.88174 3 3.2 3H5.558C5.75666 3.00004 5.95221 3.04842 6.1271 3.14082C6.30199 3.23322 6.45073 3.36675 6.56 3.52941L7.046 4.2353C7.15637 4.39964 7.30703 4.53421 7.48418 4.6267C7.66133 4.71919 7.8593 4.76664 8.06 4.76471H12.8C13.1183 4.76471 13.4235 4.88866 13.6486 5.10929C13.8735 5.32992 14 5.62916 14 5.94118V7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6 13H3.2C2.88174 13 2.57651 12.8761 2.35148 12.6554C2.12643 12.4349 2 12.1356 2 11.8236V4.17647C2 3.86445 2.12643 3.56521 2.35148 3.34458C2.57651 3.12395 2.88174 3 3.2 3H5.558C5.75666 3.00004 5.95221 3.04842 6.1271 3.14082C6.30199 3.23322 6.45073 3.36675 6.56 3.52941L7.046 4.2353C7.15637 4.39964 7.30703 4.53421 7.48418 4.6267C7.66133 4.71919 7.8593 4.76664 8.06 4.76471H12.8C13.1183 4.76471 13.4235 4.88866 13.6486 5.10929C13.8735 5.32992 14 5.62916 14 5.94118V7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 5V3h10v2M6 13h4M8 3v10"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3 5V3h10v2M6 13h4M8 3v10"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14 9.333h-3.333M10.667 10.667V8.333a1.667 1.667 0 1 1 3.333 0v2.334"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M3 8.667h4"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m2 10.667 3-6 3 6"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M14 9.333h-3.333M10.667 10.667V8.333a1.667 1.667 0 1 1 3.333 0v2.334"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M3 8.667h4"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="m2 10.667 3-6 3 6"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.27 8h5.626a2.5 2.5 0 1 1 0 5h-5a.625.625 0 0 1-.625-.625v-8.75A.625.625 0 0 1 4.896 3H9.27a2.5 2.5 0 1 1 0 5"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M4.27 8h5.626a2.5 2.5 0 1 1 0 5h-5a.625.625 0 0 1-.625-.625v-8.75A.625.625 0 0 1 4.896 3H9.27a2.5 2.5 0 1 1 0 5"/></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10 11.3334L13.3333 8.00002L10 4.66669" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M2.66675 12V10.6667C2.66675 9.95942 2.9477 9.28115 3.4478 8.78105C3.94789 8.28095 4.62617 8 5.33341 8H13.3334" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10 11.3334L13.3333 8.00002L10 4.66669" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.66675 12V10.6667C2.66675 9.95942 2.9477 9.28115 3.4478 8.78105C3.94789 8.28095 4.62617 8 5.33341 8H13.3334" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5 3v7M11.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM4.5 13a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM11 6a5 5 0 0 1-5 5"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M5 3v7M11.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM4.5 13a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM11 6a5 5 0 0 1-5 5"/></svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4.5 14C5.32843 14 6 13.3284 6 12.5C6 11.6716 5.32843 11 4.5 11C3.67157 11 3 11.6716 3 12.5C3 13.3284 3.67157 14 4.5 14Z" stroke="black" stroke-width="1.5"/>
-<path d="M4.5 11V5.5" stroke="black" stroke-width="1.5"/>
-<path d="M4.5 10C4.5 10 4.875 8 6.5 8C7.29195 8 9.00787 8 9.87553 8C10.773 8 11.5 7.32843 11.5 6.5V5.5" stroke="black" stroke-width="1.5"/>
-<path d="M4.5 6C5.32843 6 6 5.32843 6 4.5C6 3.67157 5.32843 3 4.5 3C3.67157 3 3 3.67157 3 4.5C3 5.32843 3.67157 6 4.5 6Z" stroke="black" stroke-width="1.5"/>
-<path d="M11.5 6C12.3284 6 13 5.32843 13 4.5C13 3.67157 12.3284 3 11.5 3C10.6716 3 10 3.67157 10 4.5C10 5.32843 10.6716 6 11.5 6Z" stroke="black" stroke-width="1.5"/>
+<path d="M4.5 14C5.32843 14 6 13.3284 6 12.5C6 11.6716 5.32843 11 4.5 11C3.67157 11 3 11.6716 3 12.5C3 13.3284 3.67157 14 4.5 14Z" stroke="black" stroke-width="1.2"/>
+<path d="M4.5 11V5.5" stroke="black" stroke-width="1.2"/>
+<path d="M4.5 10C4.5 10 4.875 8 6.5 8C7.29195 8 9.00787 8 9.87553 8C10.773 8 11.5 7.32843 11.5 6.5V5.5" stroke="black" stroke-width="1.2"/>
+<path d="M4.5 6C5.32843 6 6 5.32843 6 4.5C6 3.67157 5.32843 3 4.5 3C3.67157 3 3 3.67157 3 4.5C3 5.32843 3.67157 6 4.5 6Z" stroke="black" stroke-width="1.2"/>
+<path d="M11.5 6C12.3284 6 13 5.32843 13 4.5C13 3.67157 12.3284 3 11.5 3C10.6716 3 10 3.67157 10 4.5C10 5.32843 10.6716 6 11.5 6Z" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.849 14.288v-2.515a3.018 3.018 0 0 0-.629-2.201c1.887 0 3.773-1.258 3.773-3.459.05-.786-.17-1.559-.629-2.2a4.65 4.65 0 0 0 0-2.201s-.629 0-1.886.943a13.533 13.533 0 0 0-5.03 0c-1.259-.943-1.887-.943-1.887-.943a4.35 4.35 0 0 0 0 2.2 3.398 3.398 0 0 0-.63 2.201c0 2.201 1.887 3.459 3.774 3.459a2.965 2.965 0 0 0-.63 2.2v2.516"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.076 11.773c-2.836 1.258-3.144-1.258-4.402-1.258"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M9.849 14.288v-2.515a3.018 3.018 0 0 0-.629-2.201c1.887 0 3.773-1.258 3.773-3.459.05-.786-.17-1.559-.629-2.2a4.65 4.65 0 0 0 0-2.201s-.629 0-1.886.943a13.533 13.533 0 0 0-5.03 0c-1.259-.943-1.887-.943-1.887-.943a4.35 4.35 0 0 0 0 2.2 3.398 3.398 0 0 0-.63 2.201c0 2.201 1.887 3.459 3.774 3.459a2.965 2.965 0 0 0-.63 2.2v2.516"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M6.076 11.773c-2.836 1.258-3.144-1.258-4.402-1.258"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-width="1.5" d="m11.748 3.015-2.893 9.573M7.161 3.424l-2.893 9.572M3.611 6.148h10M2.398 9.856h10"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-width="1.2" d="m11.748 3.015-2.893 9.573M7.161 3.424l-2.893 9.572M3.611 6.148h10M2.398 9.856h10"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M2 8C2 9.18669 2.35189 10.3467 3.01118 11.3334C3.67047 12.3201 4.60754 13.0892 5.7039 13.5433C6.80026 13.9974 8.00666 14.1162 9.17054 13.8847C10.3344 13.6532 11.4035 13.0818 12.2426 12.2426C13.0818 11.4035 13.6532 10.3344 13.8847 9.17054C14.1162 8.00666 13.9974 6.80026 13.5433 5.7039C13.0892 4.60754 12.3201 3.67047 11.3334 3.01118C10.3467 2.35189 9.18669 2 8 2C6.32263 2.00631 4.71265 2.66082 3.50667 3.82667L2 5.33333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M2 2V5.33333H5.33333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 5V8.5L10 9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2 8C2 9.18669 2.35189 10.3467 3.01118 11.3334C3.67047 12.3201 4.60754 13.0892 5.7039 13.5433C6.80026 13.9974 8.00666 14.1162 9.17054 13.8847C10.3344 13.6532 11.4035 13.0818 12.2426 12.2426C13.0818 11.4035 13.6532 10.3344 13.8847 9.17054C14.1162 8.00666 13.9974 6.80026 13.5433 5.7039C13.0892 4.60754 12.3201 3.67047 11.3334 3.01118C10.3467 2.35189 9.18669 2 8 2C6.32263 2.00631 4.71265 2.66082 3.50667 3.82667L2 5.33333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2 2V5.33333H5.33333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 5V8.5L10 9.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M11.889 3H4.11C3.497 3 3 3.497 3 4.111v7.778C3 12.503 3.497 13 4.111 13h7.778c.614 0 1.111-.498 1.111-1.111V4.11C13 3.497 12.502 3 11.889 3Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.333 7.444a1.111 1.111 0 1 0 0-2.222 1.111 1.111 0 0 0 0 2.222ZM13 9.667l-1.714-1.715a1.11 1.11 0 0 0-1.571 0L4.667 13"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M11.889 3H4.11C3.497 3 3 3.497 3 4.111v7.778C3 12.503 3.497 13 4.111 13h7.778c.614 0 1.111-.498 1.111-1.111V4.11C13 3.497 12.502 3 11.889 3Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M6.333 7.444a1.111 1.111 0 1 0 0-2.222 1.111 1.111 0 0 0 0 2.222ZM13 9.667l-1.714-1.715a1.11 1.11 0 0 0-1.571 0L4.667 13"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.5"/>
-<path d="M7 11H8M8 11H9M8 11V8.1C8 8.04477 7.95523 8 7.9 8H7" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.2"/>
+<path d="M7 11H8M8 11H9M8 11V8.1C8 8.04477 7.95523 8 7.9 8H7" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
<path d="M8 6.5C8.55228 6.5 9 6.05228 9 5.5C9 4.94772 8.55228 4.5 8 4.5C7.44772 4.5 7 4.94772 7 5.5C7 6.05228 7.44772 6.5 8 6.5Z" fill="black"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.78125 3C3.90625 3 3.90625 4.5 3.90625 5.5C3.90625 6.5 3.40625 7.50106 2.40625 8C3.40625 8.50106 3.90625 9.5 3.90625 10.5C3.90625 11.5 3.90625 13 5.78125 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.2422 3C12.1172 3 12.1172 4.5 12.1172 5.5C12.1172 6.5 12.6172 7.50106 13.6172 8C12.6172 8.50106 12.1172 9.5 12.1172 10.5C12.1172 11.5 12.1172 13 10.2422 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.78125 3C3.90625 3 3.90625 4.5 3.90625 5.5C3.90625 6.5 3.40625 7.50106 2.40625 8C3.40625 8.50106 3.90625 9.5 3.90625 10.5C3.90625 11.5 3.90625 13 5.78125 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.2422 3C12.1172 3 12.1172 4.5 12.1172 5.5C12.1172 6.5 12.6172 7.50106 13.6172 8C12.6172 8.50106 12.1172 9.5 12.1172 10.5C12.1172 11.5 12.1172 13 10.2422 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.75 5.5h.007M8 8h.007M9.25 5.5h.007M10.5 8h.007M11.75 5.5h.007M4.25 5.5h.007M4.875 10.5h6.25M5.5 8h.007M13 3H3c-.69 0-1.25.56-1.25 1.25v7.5c0 .69.56 1.25 1.25 1.25h10c.69 0 1.25-.56 1.25-1.25v-7.5C14.25 3.56 13.69 3 13 3Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M6.75 5.5h.007M8 8h.007M9.25 5.5h.007M10.5 8h.007M11.75 5.5h.007M4.25 5.5h.007M4.875 10.5h6.25M5.5 8h.007M13 3H3c-.69 0-1.25.56-1.25 1.25v7.5c0 .69.56 1.25 1.25 1.25h10c.69 0 1.25-.56 1.25-1.25v-7.5C14.25 3.56 13.69 3 13 3Z"/></svg>
@@ -1,3 +1,3 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 3L5.5 5.5M8 8L5.5 5.5M5.5 5.5L3 8M5.5 5.5L8 3" stroke="black" stroke-width="1.5"/>
+<path d="M3 3L5.5 5.5M8 8L5.5 5.5M5.5 5.5L3 8M5.5 5.5L8 3" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10.6667 4L13.3334 13.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 4V13.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.33325 5.33331V13.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M2.66675 2.66669V13.3334" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.6667 4L13.3334 13.3333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 4V13.3333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.33325 5.33331V13.3333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.66675 2.66669V13.3334" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 13.667h8M4 2.333h8M5 11l3-6 3 6M6 9h4"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M4 13.667h8M4 2.333h8M5 11l3-6 3 6M6 9h4"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.857 6.857 4.286 5.43 2.857 4M2.857 12l1.429-1.429-1.429-1.428M6.857 4.571h6.286M6.857 8h6.286M6.857 11.428h6.286"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.857 6.857 4.286 5.43 2.857 4M2.857 12l1.429-1.429-1.429-1.428M6.857 4.571h6.286M6.857 8h6.286M6.857 11.428h6.286"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5.333 3.333H2.667A.667.667 0 0 0 2 4v2.667c0 .368.298.666.667.666h2.666A.667.667 0 0 0 6 6.667V4a.667.667 0 0 0-.667-.667ZM2 11.333l1.333 1.334L6 10M8.667 4H14M8.667 8H14M8.667 12H14"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M5.333 3.333H2.667A.667.667 0 0 0 2 4v2.667c0 .368.298.666.667.666h2.666A.667.667 0 0 0 6 6.667V4a.667.667 0 0 0-.667-.667ZM2 11.333l1.333 1.334L6 10M8.667 4H14M8.667 8H14M8.667 12H14"/></svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M13.5 8H9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13.5 4L6.5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13.5 12H9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3 3.5V6.33333C3 7.25 3.72 8 4.6 8H7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3 6V10.5C3 11.325 3.72 12 4.6 12H7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.5 8H9.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.5 4L6.5 4" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.5 12H9.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 3.5V6.33333C3 7.25 3.72 8 4.6 8H7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 6V10.5C3 11.325 3.72 12 4.6 12H7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8.33333 8H3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.6667 4H3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11.6667 12H3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13.6667 6.66663L11 9.33329" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11 6.66663L13.6667 9.33329" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.33333 8H3" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.6667 4H3" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.6667 12H3" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.6667 6.66663L11 9.33329" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11 6.66663L13.6667 9.33329" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 8a5 5 0 1 1-3.455-4.755"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M13 8a5 5 0 1 1-3.455-4.755"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6.502a4.904 4.904 0 0 0-1.686-3.28 5.059 5.059 0 0 0-3.522-1.22A5.045 5.045 0 0 0 3.39 3.52 4.889 4.889 0 0 0 2 6.93c0 2.89 3.06 5.893 4.397 7.068M13.655 11.013a1.18 1.18 0 0 0-1.67-1.67l-2.227 2.23a1.112 1.112 0 0 0-.282.475l-.465 1.594a.278.278 0 0 0 .345.345l1.594-.465a1.11 1.11 0 0 0 .475-.281l2.23-2.228Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7 8.998a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M12 6.502a4.904 4.904 0 0 0-1.686-3.28 5.059 5.059 0 0 0-3.522-1.22A5.045 5.045 0 0 0 3.39 3.52 4.889 4.889 0 0 0 2 6.93c0 2.89 3.06 5.893 4.397 7.068M13.655 11.013a1.18 1.18 0 0 0-1.67-1.67l-2.227 2.23a1.112 1.112 0 0 0-.282.475l-.465 1.594a.278.278 0 0 0 .345.345l1.594-.465a1.11 1.11 0 0 0 .475-.281l2.23-2.228Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M7 8.998a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/></svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.5"/>
-<path d="M8 9V11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.2"/>
+<path d="M8 9V11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
<circle cx="8" cy="9" r="1" fill="black"/>
-<rect x="3.75" y="5.75" width="8.5" height="7.5" rx="1.25" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
+<rect x="3.75" y="5.75" width="8.5" height="7.5" rx="1.25" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 13L10.4138 10.4138ZM3 7.31034C3 4.92981 4.92981 3 7.31034 3C9.6909 3 11.6207 4.92981 11.6207 7.31034C11.6207 9.6909 9.6909 11.6207 7.31034 11.6207C4.92981 11.6207 3 9.6909 3 7.31034Z" fill="black" fill-opacity="0.15"/>
-<path d="M13 13L10.4138 10.4138M3 7.31034C3 4.92981 4.92981 3 7.31034 3C9.6909 3 11.6207 4.92981 11.6207 7.31034C11.6207 9.6909 9.6909 11.6207 7.31034 11.6207C4.92981 11.6207 3 9.6909 3 7.31034Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13 13L10.4138 10.4138M3 7.31034C3 4.92981 4.92981 3 7.31034 3C9.6909 3 11.6207 4.92981 11.6207 7.31034C11.6207 9.6909 9.6909 11.6207 7.31034 11.6207C4.92981 11.6207 3 9.6909 3 7.31034Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10 3H13V6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6 13H3V10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13 3L9.5 6.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3 13L6.5 9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10 3H13V6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6 13H3V10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13 3L9.5 6.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 13L6.5 9.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.667 8h10.666M2.667 4h10.666M2.667 12h10.666"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.667 8h10.666M2.667 4h10.666M2.667 12h10.666"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.667 8h8M2.667 4h10.666M2.667 12H8"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.667 8h8M2.667 4h10.666M2.667 12H8"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 12.2028V14.3042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.2027 6.94928V8.11672C12.2027 9.20041 11.7599 10.2397 10.9717 11.006C10.1836 11.7723 9.11457 12.2028 7.99992 12.2028C6.88527 12.2028 5.81627 11.7723 5.02809 11.006C4.23991 10.2397 3.79712 9.20041 3.79712 8.11672V6.94928" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.1015 3.63555C10.1015 2.56426 9.16065 1.6958 8.00008 1.6958C6.83951 1.6958 5.89868 2.56426 5.89868 3.63555V8.16165C5.89868 9.23294 6.83951 10.1014 8.00008 10.1014C9.16065 10.1014 10.1015 9.23294 10.1015 8.16165V3.63555Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 12.2028V14.3042" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.2027 6.94928V8.11672C12.2027 9.20041 11.7599 10.2397 10.9717 11.006C10.1836 11.7723 9.11457 12.2028 7.99992 12.2028C6.88527 12.2028 5.81627 11.7723 5.02809 11.006C4.23991 10.2397 3.79712 9.20041 3.79712 8.11672V6.94928" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.1015 3.63555C10.1015 2.56426 9.16065 1.6958 8.00008 1.6958C6.83951 1.6958 5.89868 2.56426 5.89868 3.63555V8.16165C5.89868 9.23294 6.83951 10.1014 8.00008 10.1014C9.16065 10.1014 10.1015 9.23294 10.1015 8.16165V3.63555Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,8 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 3L13 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12 9C12 8.74858 12 8.49375 12 8.23839V7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4.00043 7V8.09869C3.98856 8.86731 4.22157 9.62164 4.66938 10.2643C5.11718 10.907 5.75924 11.4085 6.51267 11.7042C7.2661 11.9999 8.09632 12.0761 8.89619 11.923C9.47851 11.8115 10.0253 11.5823 10.5 11.2539" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10 6V3.62904C9.99714 3.26103 9.8347 2.90448 9.53885 2.6168C9.24299 2.32913 8.83093 2.12707 8.36903 2.04316C7.90713 1.95926 7.42226 1.9984 6.99252 2.15427C6.56278 2.31015 6.21317 2.57369 6 2.90245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6 6V8.00088C6.00031 8.39636 6.10356 8.78287 6.29674 9.11159C6.48991 9.44031 6.76433 9.69649 7.08534 9.84779C7.40634 9.99909 7.75954 10.0387 8.10032 9.96165C8.4411 9.88459 8.75417 9.69431 9 9.41483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 12V14" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 3L13 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12 9C12 8.74858 12 8.49375 12 8.23839V7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.00043 7V8.09869C3.98856 8.86731 4.22157 9.62164 4.66938 10.2643C5.11718 10.907 5.75924 11.4085 6.51267 11.7042C7.2661 11.9999 8.09632 12.0761 8.89619 11.923C9.47851 11.8115 10.0253 11.5823 10.5 11.2539" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10 6V3.62904C9.99714 3.26103 9.8347 2.90448 9.53885 2.6168C9.24299 2.32913 8.83093 2.12707 8.36903 2.04316C7.90713 1.95926 7.42226 1.9984 6.99252 2.15427C6.56278 2.31015 6.21317 2.57369 6 2.90245" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6 6V8.00088C6.00031 8.39636 6.10356 8.78287 6.29674 9.11159C6.48991 9.44031 6.76433 9.69649 7.08534 9.84779C7.40634 9.99909 7.75954 10.0387 8.10032 9.96165C8.4411 9.88459 8.75417 9.69431 9 9.41483" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 12V14" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3.5 9.5H6.5V12.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.5 6.5H9.5V3.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.5 6.5L13 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3 13L6.5 9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.5 9.5H6.5V12.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5 6.5H9.5V3.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.5 6.5L13 3" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 13L6.5 9.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" clip-path="url(#a)"><path d="M6 8h4M6 10.5h4M3 3h10v9.565c0 .38-.158.746-.44 1.015-.28.269-.662.42-1.06.42h-7c-.398 0-.78-.151-1.06-.42A1.404 1.404 0 0 1 3 12.565V3ZM6.5 1v4M9.5 1v4"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" clip-path="url(#a)"><path d="M6 8h4M6 10.5h4M3 3h10v9.565c0 .38-.158.746-.44 1.015-.28.269-.662.42-1.06.42h-7c-.398 0-.78-.151-1.06-.42A1.404 1.404 0 0 1 3 12.565V3ZM6.5 1v4M9.5 1v4"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 3H6.33333L9.66667 13H13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.11108 3H13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 3H6.33333L9.66667 13H13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.11108 3H13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.5871 5.40582C12.8514 5.14152 13 4.78304 13 4.40922C13 4.03541 12.8516 3.67688 12.5873 3.41252C12.323 3.14816 11.9645 2.99962 11.5907 2.99957C11.2169 2.99953 10.8584 3.14798 10.594 3.41227L3.92098 10.0869C3.80488 10.2027 3.71903 10.3452 3.67097 10.5019L3.01047 12.678C2.99754 12.7212 2.99657 12.7672 3.00764 12.8109C3.01872 12.8547 3.04143 12.8946 3.07337 12.9265C3.1053 12.9584 3.14528 12.981 3.18905 12.992C3.23282 13.003 3.27875 13.002 3.32197 12.989L5.49849 12.329C5.65508 12.2813 5.79758 12.196 5.91349 12.0805L12.5871 5.40582Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9 5L11 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5871 5.40582C12.8514 5.14152 13 4.78304 13 4.40922C13 4.03541 12.8516 3.67688 12.5873 3.41252C12.323 3.14816 11.9645 2.99962 11.5907 2.99957C11.2169 2.99953 10.8584 3.14798 10.594 3.41227L3.92098 10.0869C3.80488 10.2027 3.71903 10.3452 3.67097 10.5019L3.01047 12.678C2.99754 12.7212 2.99657 12.7672 3.00764 12.8109C3.01872 12.8547 3.04143 12.8946 3.07337 12.9265C3.1053 12.9584 3.14528 12.981 3.18905 12.992C3.23282 13.003 3.27875 13.002 3.32197 12.989L5.49849 12.329C5.65508 12.2813 5.79758 12.196 5.91349 12.0805L12.5871 5.40582Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 5L11 7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12.667 14v-1.333A2.667 2.667 0 0 0 10 10H6a2.667 2.667 0 0 0-2.667 2.667V14M8 7.333A2.667 2.667 0 1 0 8 2a2.667 2.667 0 0 0 0 5.333Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M12.667 14v-1.333A2.667 2.667 0 0 0 10 10H6a2.667 2.667 0 0 0-2.667 2.667V14M8 7.333A2.667 2.667 0 1 0 8 2a2.667 2.667 0 0 0 0 5.333Z"/></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 10V13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 4L12 8L5 12V4Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 4L12 8L5 12V4Z" fill="black" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 4L12 8L5 12V4Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 4L12 8L5 12V4Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3.33325 8H12.6666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 3.33333V12.6667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.33325 8H12.6666" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 3.33333V12.6667" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 2v5M11.536 4a5.368 5.368 0 0 1 1.367 2.696 5.54 5.54 0 0 1-.28 3.042 5.223 5.223 0 0 1-1.836 2.367A4.82 4.82 0 0 1 8.016 13a4.817 4.817 0 0 1-2.777-.877 5.22 5.22 0 0 1-1.85-2.354 5.54 5.54 0 0 1-.298-3.041 5.371 5.371 0 0 1 1.35-2.705"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 2v5M11.536 4a5.368 5.368 0 0 1 1.367 2.696 5.54 5.54 0 0 1-.28 3.042 5.223 5.223 0 0 1-1.836 2.367A4.82 4.82 0 0 1 8.016 13a4.817 4.817 0 0 1-2.777-.877 5.22 5.22 0 0 1-1.85-2.354 5.54 5.54 0 0 1-.298-3.041 5.371 5.371 0 0 1 1.35-2.705"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6.375A5.625 5.625 0 0 1 9.625 12M4 10a2 2 0 0 1 2 2M4 3a9 9 0 0 1 9 9"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M4 6.375A5.625 5.625 0 0 1 9.625 12M4 10a2 2 0 0 1 2 2M4 3a9 9 0 0 1 9 9"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM4 6v7M12 13a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM10 6.5l-2-2 2-2"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8.5 4.389h2.357c.303 0 .594.117.808.325.215.209.335.491.335.786V10"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M4 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM4 6v7M12 13a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM10 6.5l-2-2 2-2"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8.5 4.389h2.357c.303 0 .594.117.808.325.215.209.335.491.335.786V10"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M11.333 4H2M14 8H5.333M12 12H5M2 8v4"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M11.333 4H2M14 8H5.333M12 12H5M2 8v4"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.8889 2H4.11111C3.49746 2 3 2.59695 3 3.33333V12.6667C3 13.403 3.49746 14 4.11111 14H11.8889C12.5025 14 13 13.403 13 12.6667V3.33333C13 2.59695 12.5025 2 11.8889 2Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9 6H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10 10H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11.8889 2H4.11111C3.49746 2 3 2.59695 3 3.33333V12.6667C3 13.403 3.49746 14 4.11111 14H11.8889C12.5025 14 13 13.403 13 12.6667V3.33333C13 2.59695 12.5025 2 11.8889 2Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 6H6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10 10H6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-width="1.5" d="M4.667 14V8m0 0H2m2.667 0h2.666"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14 12.667h-3.333V9.333"/><path fill="#000" d="M4.03 3.5a.667.667 0 0 0 .883 1l-.882-1ZM8 3.333A4.667 4.667 0 0 1 12.666 8H14a6 6 0 0 0-6-6v1.333ZM12.666 8a4.656 4.656 0 0 1-1.75 3.643l.834 1.04A5.99 5.99 0 0 0 14 8h-1.334ZM5.667 3.957A4.642 4.642 0 0 1 8 3.333V2a5.976 5.976 0 0 0-3 .803l.667 1.154Zm-.754.543c.232-.205.485-.387.754-.543l-.668-1.154c-.346.2-.67.434-.968.697l.882 1Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-width="1.2" d="M4.667 14V8m0 0H2m2.667 0h2.666"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M14 12.667h-3.333V9.333"/><path fill="#000" d="M4.03 3.5a.667.667 0 0 0 .883 1l-.882-1ZM8 3.333A4.667 4.667 0 0 1 12.666 8H14a6 6 0 0 0-6-6v1.333ZM12.666 8a4.656 4.656 0 0 1-1.75 3.643l.834 1.04A5.99 5.99 0 0 0 14 8h-1.334ZM5.667 3.957A4.642 4.642 0 0 1 8 3.333V2a5.976 5.976 0 0 0-3 .803l.667 1.154Zm-.754.543c.232-.205.485-.387.754-.543l-.668-1.154c-.346.2-.67.434-.968.697l.882 1Z"/></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.57132 13.7143C5.20251 13.7143 5.71418 13.2026 5.71418 12.5714C5.71418 11.9403 5.20251 11.4286 4.57132 11.4286C3.94014 11.4286 3.42847 11.9403 3.42847 12.5714C3.42847 13.2026 3.94014 13.7143 4.57132 13.7143Z" fill="black"/>
-<path d="M10.2856 2.85712V5.71426M10.2856 5.71426V8.5714M10.2856 5.71426H13.1428M10.2856 5.71426H7.42847M10.2856 5.71426L12.1904 3.80949M10.2856 5.71426L8.38084 7.61906M10.2856 5.71426L12.1904 7.61906M10.2856 5.71426L8.38084 3.80949" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M10.2856 2.85712V5.71426M10.2856 5.71426V8.5714M10.2856 5.71426H13.1428M10.2856 5.71426H7.42847M10.2856 5.71426L12.1904 3.80949M10.2856 5.71426L8.38084 7.61906M10.2856 5.71426L12.1904 7.61906M10.2856 5.71426L8.38084 3.80949" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.5 6C13.3284 6 14 5.32843 14 4.5C14 3.67157 13.3284 3 12.5 3C11.6716 3 11 3.67157 11 4.5C11 5.32843 11.6716 6 12.5 6Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3.5 13C4.32843 13 5 12.3284 5 11.5C5 10.6716 4.32843 10 3.5 10C2.67157 10 2 10.6716 2 11.5C2 12.3284 2.67157 13 3.5 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.00391 13.9293C8.16208 14.1122 9.35055 13.9659 10.4234 13.5085C11.4962 13.0511 12.4066 12.3024 13.0426 11.3545C13.6787 10.4066 14.0128 9.30075 14.0037 8.17293C13.9977 7.42342 13.8404 6.68587 13.5444 6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.00391 2.07233C7.83928 1.90288 6.64809 2.05936 5.57504 2.52278C4.502 2.9862 3.59331 3.73659 2.95939 4.68279C2.32547 5.62899 1.99362 6.73024 2.00415 7.85274C2.0111 8.59299 2.16675 9.32147 2.45883 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5 6C13.3284 6 14 5.32843 14 4.5C14 3.67157 13.3284 3 12.5 3C11.6716 3 11 3.67157 11 4.5C11 5.32843 11.6716 6 12.5 6Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.5 13C4.32843 13 5 12.3284 5 11.5C5 10.6716 4.32843 10 3.5 10C2.67157 10 2 10.6716 2 11.5C2 12.3284 2.67157 13 3.5 13Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.00391 13.9293C8.16208 14.1122 9.35055 13.9659 10.4234 13.5085C11.4962 13.0511 12.4066 12.3024 13.0426 11.3545C13.6787 10.4066 14.0128 9.30075 14.0037 8.17293C13.9977 7.42342 13.8404 6.68587 13.5444 6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.00391 2.07233C7.83928 1.90288 6.64809 2.05936 5.57504 2.52278C4.502 2.9862 3.59331 3.73659 2.95939 4.68279C2.32547 5.62899 1.99362 6.73024 2.00415 7.85274C2.0111 8.59299 2.16675 9.32147 2.45883 10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,11 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M14.0039 8.01361C13.9981 7.31989 13.8495 6.63699 13.5698 6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.00391 2.01361C7.74152 2.01361 8.2084 2.01361 9.00391 2.01361" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13.0039 11.0136C12.4719 11.8034 11.7928 12.4825 11.0039 13.0136" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.00391 14.0136C8.28937 14.0136 7.71844 14.0136 7.00391 14.0136" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3.00391 5.01361C3.53595 4.22382 4.21507 3.5447 5.00391 3.01361" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8.00391 9.01361C8.55621 9.01361 9.00391 8.56591 9.00391 8.01361C9.00391 7.46131 8.55621 7.01361 8.00391 7.01361C7.45161 7.01361 7.00391 7.46131 7.00391 8.01361C7.00391 8.56591 7.45161 9.01361 8.00391 9.01361Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.5 6C13.3284 6 14 5.32843 14 4.5C14 3.67157 13.3284 3 12.5 3C11.6716 3 11 3.67157 11 4.5C11 5.32843 11.6716 6 12.5 6Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3.5 13C4.32843 13 5 12.3284 5 11.5C5 10.6716 4.32843 10 3.5 10C2.67157 10 2 10.6716 2 11.5C2 12.3284 2.67157 13 3.5 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M2.00391 8.01361C2.01055 8.69737 2.15535 9.37058 2.42723 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M14.0039 8.01361C13.9981 7.31989 13.8495 6.63699 13.5698 6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.00391 2.01361C7.74152 2.01361 8.2084 2.01361 9.00391 2.01361" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.0039 11.0136C12.4719 11.8034 11.7928 12.4825 11.0039 13.0136" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.00391 14.0136C8.28937 14.0136 7.71844 14.0136 7.00391 14.0136" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.00391 5.01361C3.53595 4.22382 4.21507 3.5447 5.00391 3.01361" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.00391 9.01361C8.55621 9.01361 9.00391 8.56591 9.00391 8.01361C9.00391 7.46131 8.55621 7.01361 8.00391 7.01361C7.45161 7.01361 7.00391 7.46131 7.00391 8.01361C7.00391 8.56591 7.45161 9.01361 8.00391 9.01361Z" fill="black" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5 6C13.3284 6 14 5.32843 14 4.5C14 3.67157 13.3284 3 12.5 3C11.6716 3 11 3.67157 11 4.5C11 5.32843 11.6716 6 12.5 6Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.5 13C4.32843 13 5 12.3284 5 11.5C5 10.6716 4.32843 10 3.5 10C2.67157 10 2 10.6716 2 11.5C2 12.3284 2.67157 13 3.5 13Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2.00391 8.01361C2.01055 8.69737 2.15535 9.37058 2.42723 10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,8 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.5 6C13.3284 6 14 5.32843 14 4.5C14 3.67157 13.3284 3 12.5 3C11.6716 3 11 3.67157 11 4.5C11 5.32843 11.6716 6 12.5 6Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3.5 13C4.32843 13 5 12.3284 5 11.5C5 10.6716 4.32843 10 3.5 10C2.67157 10 2 10.6716 2 11.5C2 12.3284 2.67157 13 3.5 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.00391 13.9293C8.16208 14.1122 9.35055 13.9659 10.4234 13.5085C11.4962 13.0511 12.4066 12.3024 13.0426 11.3545C13.6787 10.4066 14.0128 9.30075 14.0037 8.17293C13.9977 7.42342 13.8404 6.68587 13.5444 6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.00391 2.07233C7.83928 1.90288 6.64809 2.05936 5.57504 2.52278C4.502 2.9862 3.59331 3.73659 2.95939 4.68279C2.32547 5.62899 1.99362 6.73024 2.00415 7.85274C2.0111 8.59299 2.16675 9.32147 2.45883 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.00391 10.0059V6.00592" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.00391 10.0059V6.00592" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5 6C13.3284 6 14 5.32843 14 4.5C14 3.67157 13.3284 3 12.5 3C11.6716 3 11 3.67157 11 4.5C11 5.32843 11.6716 6 12.5 6Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.5 13C4.32843 13 5 12.3284 5 11.5C5 10.6716 4.32843 10 3.5 10C2.67157 10 2 10.6716 2 11.5C2 12.3284 2.67157 13 3.5 13Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.00391 13.9293C8.16208 14.1122 9.35055 13.9659 10.4234 13.5085C11.4962 13.0511 12.4066 12.3024 13.0426 11.3545C13.6787 10.4066 14.0128 9.30075 14.0037 8.17293C13.9977 7.42342 13.8404 6.68587 13.5444 6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.00391 2.07233C7.83928 1.90288 6.64809 2.05936 5.57504 2.52278C4.502 2.9862 3.59331 3.73659 2.95939 4.68279C2.32547 5.62899 1.99362 6.73024 2.00415 7.85274C2.0111 8.59299 2.16675 9.32147 2.45883 10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.00391 10.0059V6.00592" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.00391 10.0059V6.00592" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.00366 6.16662C7.00366 6.03849 7.14274 5.96206 7.24661 6.03313L9.93408 7.87243C10.0269 7.93591 10.0269 8.07591 9.93408 8.13939L7.24661 9.97871C7.14274 10.0498 7.00366 9.97336 7.00366 9.84518V6.16662Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.5 6C13.3284 6 14 5.32843 14 4.5C14 3.67157 13.3284 3 12.5 3C11.6716 3 11 3.67157 11 4.5C11 5.32843 11.6716 6 12.5 6Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3.5 13C4.32843 13 5 12.3284 5 11.5C5 10.6716 4.32843 10 3.5 10C2.67157 10 2 10.6716 2 11.5C2 12.3284 2.67157 13 3.5 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.00391 13.9293C8.16208 14.1122 9.35055 13.9659 10.4234 13.5085C11.4962 13.0511 12.4066 12.3024 13.0426 11.3545C13.6787 10.4066 14.0128 9.30075 14.0037 8.17293C13.9977 7.42342 13.8404 6.68587 13.5444 6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.00391 2.07233C7.83928 1.90288 6.64809 2.05936 5.57504 2.52278C4.502 2.9862 3.59331 3.73659 2.95939 4.68279C2.32547 5.62899 1.99362 6.73024 2.00415 7.85274C2.0111 8.59299 2.16675 9.32147 2.45883 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.00366 6.16662C7.00366 6.03849 7.14274 5.96206 7.24661 6.03313L9.93408 7.87243C10.0269 7.93591 10.0269 8.07591 9.93408 8.13939L7.24661 9.97871C7.14274 10.0498 7.00366 9.97336 7.00366 9.84518V6.16662Z" fill="black" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5 6C13.3284 6 14 5.32843 14 4.5C14 3.67157 13.3284 3 12.5 3C11.6716 3 11 3.67157 11 4.5C11 5.32843 11.6716 6 12.5 6Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.5 13C4.32843 13 5 12.3284 5 11.5C5 10.6716 4.32843 10 3.5 10C2.67157 10 2 10.6716 2 11.5C2 12.3284 2.67157 13 3.5 13Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.00391 13.9293C8.16208 14.1122 9.35055 13.9659 10.4234 13.5085C11.4962 13.0511 12.4066 12.3024 13.0426 11.3545C13.6787 10.4066 14.0128 9.30075 14.0037 8.17293C13.9977 7.42342 13.8404 6.68587 13.5444 6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.00391 2.07233C7.83928 1.90288 6.64809 2.05936 5.57504 2.52278C4.502 2.9862 3.59331 3.73659 2.95939 4.68279C2.32547 5.62899 1.99362 6.73024 2.00415 7.85274C2.0111 8.59299 2.16675 9.32147 2.45883 10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.99988 13C5.97267 13 4.22723 11.7936 3.44238 10.0595M7.99988 3C10.1122 3 11.9185 4.30981 12.6511 6.16152" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.99988 13C5.97267 13 4.22723 11.7936 3.44238 10.0595M7.99988 3C10.1122 3 11.9185 4.30981 12.6511 6.16152" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.65625 3.29688C3.00143 3.29688 3.28125 3.01705 3.28125 2.67188C3.28125 2.3267 3.00143 2.04688 2.65625 2.04688C2.31107 2.04688 2.03125 2.3267 2.03125 2.67188C2.03125 3.01705 2.31107 3.29688 2.65625 3.29688Z" fill="black"/>
<path d="M4.71094 3.29688C5.05612 3.29688 5.33594 3.01705 5.33594 2.67188C5.33594 2.3267 5.05612 2.04688 4.71094 2.04688C4.36576 2.04688 4.08594 2.3267 4.08594 2.67188C4.08594 3.01705 4.36576 3.29688 4.71094 3.29688Z" fill="black"/>
<path d="M5.96094 4.99219C6.30612 4.99219 6.58594 4.71237 6.58594 4.36719C6.58594 4.02201 6.30612 3.74219 5.96094 3.74219C5.61576 3.74219 5.33594 4.02201 5.33594 4.36719C5.33594 4.71237 5.61576 4.99219 5.96094 4.99219Z" fill="black"/>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M8.5 6.88672C10.433 6.88672 12 8.45372 12 10.3867V13M12 13L14 11M12 13L10 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+ <path d="M8.5 6.88672C10.433 6.88672 12 8.45372 12 10.3867V13M12 13L14 11M12 13L10 11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2 8a6 6 0 0 1 6-6 6.5 6.5 0 0 1 4.493 1.827L14 5.333"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14 2v3.333h-3.333M14 8a6 6 0 0 1-6 6 6.5 6.5 0 0 1-4.493-1.827L2 10.667"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5.333 10.667H2V14"/><path fill="#000" d="M6.667 6.247c0-.257.279-.418.501-.288l3.005 1.753c.22.129.22.447 0 .576L7.168 10.04a.333.333 0 0 1-.501-.288V6.247Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2 8a6 6 0 0 1 6-6 6.5 6.5 0 0 1 4.493 1.827L14 5.333"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M14 2v3.333h-3.333M14 8a6 6 0 0 1-6 6 6.5 6.5 0 0 1-4.493-1.827L2 10.667"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M5.333 10.667H2V14"/><path fill="#000" d="M6.667 6.247c0-.257.279-.418.501-.288l3.005 1.753c.22.129.22.447 0 .576L7.168 10.04a.333.333 0 0 1-.501-.288V6.247Z"/></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.00008 6.66669L2.66675 10L6.00008 13.3334" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13.3334 2.66669V7.33335C13.3334 8.0406 13.0525 8.71888 12.5524 9.21897C12.0523 9.71907 11.374 10 10.6667 10H2.66675" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.00008 6.66669L2.66675 10L6.00008 13.3334" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.3334 2.66669V7.33335C13.3334 8.0406 13.0525 8.71888 12.5524 9.21897C12.0523 9.71907 11.374 10 10.6667 10H2.66675" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m3 5.778 1.256-1.256A5.417 5.417 0 0 1 8 3a5 5 0 1 1-4.583 7"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 3v3h3"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="m3 5.778 1.256-1.256A5.417 5.417 0 0 1 8 3a5 5 0 1 1-4.583 7"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3 3v3h3"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m13 5.778-1.256-1.256A5.416 5.416 0 0 0 8 3a5 5 0 1 0 4.583 7"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 3v3h-3"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="m13 5.778-1.256-1.256A5.416 5.416 0 0 0 8 3a5 5 0 1 0 4.583 7"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M13 3v3h-3"/></svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.03641 5.53641L8.33797 7.83797M13.0825 3.0934L6.03641 10.1395M9.99896 9.49896L13.0825 12.5825M4.77932 6.05864C5.25123 6.05864 5.7038 5.87118 6.03749 5.53749C6.37118 5.2038 6.55864 4.75123 6.55864 4.27932C6.55864 3.80742 6.37118 3.35484 6.03749 3.02115C5.7038 2.68746 5.25123 2.5 4.77932 2.5C4.30742 2.5 3.85484 2.68746 3.52115 3.02115C3.18746 3.35484 3 3.80742 3 4.27932C3 4.75123 3.18746 5.2038 3.52115 5.53749C3.85484 5.87118 4.30742 6.05864 4.77932 6.05864ZM4.77932 13.1759C5.25123 13.1759 5.7038 12.9885 6.03749 12.6548C6.37118 12.3211 6.55864 11.8685 6.55864 11.3966C6.55864 10.9247 6.37118 10.4721 6.03749 10.1384C5.7038 9.80475 5.25123 9.61729 4.77932 9.61729C4.30742 9.61729 3.85484 9.80475 3.52115 10.1384C3.18746 10.4721 3 10.9247 3 11.3966C3 11.8685 3.18746 12.3211 3.52115 12.6548C3.85484 12.9885 4.30742 13.1759 4.77932 13.1759Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.03641 5.53641L8.33797 7.83797M13.0825 3.0934L6.03641 10.1395M9.99896 9.49896L13.0825 12.5825M4.77932 6.05864C5.25123 6.05864 5.7038 5.87118 6.03749 5.53749C6.37118 5.2038 6.55864 4.75123 6.55864 4.27932C6.55864 3.80742 6.37118 3.35484 6.03749 3.02115C5.7038 2.68746 5.25123 2.5 4.77932 2.5C4.30742 2.5 3.85484 2.68746 3.52115 3.02115C3.18746 3.35484 3 3.80742 3 4.27932C3 4.75123 3.18746 5.2038 3.52115 5.53749C3.85484 5.87118 4.30742 6.05864 4.77932 6.05864ZM4.77932 13.1759C5.25123 13.1759 5.7038 12.9885 6.03749 12.6548C6.37118 12.3211 6.55864 11.8685 6.55864 11.3966C6.55864 10.9247 6.37118 10.4721 6.03749 10.1384C5.7038 9.80475 5.25123 9.61729 4.77932 9.61729C4.30742 9.61729 3.85484 9.80475 3.52115 10.1384C3.18746 10.4721 3 10.9247 3 11.3966C3 11.8685 3.18746 12.3211 3.52115 12.6548C3.85484 12.9885 4.30742 13.1759 4.77932 13.1759Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.8 3H3.2C2.53726 3 2 3.51167 2 4.14286V9.85714C2 10.4883 2.53726 11 3.2 11H12.8C13.4627 11 14 10.4883 14 9.85714V4.14286C14 3.51167 13.4627 3 12.8 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.33325 14H10.6666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 11.3333V14" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.8 3H3.2C2.53726 3 2 3.51167 2 4.14286V9.85714C2 10.4883 2.53726 11 3.2 11H12.8C13.4627 11 14 10.4883 14 9.85714V4.14286C14 3.51167 13.4627 3 12.8 3Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.33325 14H10.6666" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 11.3333V14" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3.377 3.028a.25.25 0 0 0-.292.045.291.291 0 0 0-.067.303l1.496 4.236c.088.25.088.526 0 .776l-1.496 4.236a.291.291 0 0 0 .067.303.25.25 0 0 0 .292.045l9.472-4.72a.267.267 0 0 0 .11-.103.288.288 0 0 0-.11-.4L3.377 3.028ZM5 8h8"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.377 3.028a.25.25 0 0 0-.292.045.291.291 0 0 0-.067.303l1.496 4.236c.088.25.088.526 0 .776l-1.496 4.236a.291.291 0 0 0 .067.303.25.25 0 0 0 .292.045l9.472-4.72a.267.267 0 0 0 .11-.103.288.288 0 0 0-.11-.4L3.377 3.028ZM5 8h8"/></svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.8 9H3.2C2.53726 9 2 9.44772 2 10V12C2 12.5523 2.53726 13 3.2 13H12.8C13.4627 13 14 12.5523 14 12V10C14 9.44772 13.4627 9 12.8 9Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.8 3H3.2C2.53726 3 2 3.44772 2 4V6C2 6.55228 2.53726 7 3.2 7H12.8C13.4627 7 14 6.55228 14 6V4C14 3.44772 13.4627 3 12.8 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4 11H4.00667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4 5H4.00667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.8 9H3.2C2.53726 9 2 9.44772 2 10V12C2 12.5523 2.53726 13 3.2 13H12.8C13.4627 13 14 12.5523 14 12V10C14 9.44772 13.4627 9 12.8 9Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.8 3H3.2C2.53726 3 2 3.44772 2 4V6C2 6.55228 2.53726 7 3.2 7H12.8C13.4627 7 14 6.55228 14 6V4C14 3.44772 13.4627 3 12.8 3Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4 11H4.00667" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4 5H4.00667" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M13.0001 8.62505C13.0001 11.75 10.8126 13.3125 8.21266 14.2187C8.07651 14.2648 7.92862 14.2626 7.79392 14.2125C5.18771 13.3125 3.00024 11.75 3.00024 8.62505V4.25012C3.00024 4.08436 3.06609 3.92539 3.1833 3.80818C3.30051 3.69098 3.45948 3.62513 3.62523 3.62513C4.87521 3.62513 6.43769 2.87514 7.52517 1.92516C7.65758 1.81203 7.82601 1.74988 8.00016 1.74988C8.17431 1.74988 8.34275 1.81203 8.47515 1.92516C9.56889 2.88139 11.1251 3.62513 12.3751 3.62513C12.5408 3.62513 12.6998 3.69098 12.817 3.80818C12.9342 3.92539 13.0001 4.08436 13.0001 4.25012V8.62505Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6 8.00002L7.33333 9.33335L10 6.66669" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.0001 8.62505C13.0001 11.75 10.8126 13.3125 8.21266 14.2187C8.07651 14.2648 7.92862 14.2626 7.79392 14.2125C5.18771 13.3125 3.00024 11.75 3.00024 8.62505V4.25012C3.00024 4.08436 3.06609 3.92539 3.1833 3.80818C3.30051 3.69098 3.45948 3.62513 3.62523 3.62513C4.87521 3.62513 6.43769 2.87514 7.52517 1.92516C7.65758 1.81203 7.82601 1.74988 8.00016 1.74988C8.17431 1.74988 8.34275 1.81203 8.47515 1.92516C9.56889 2.88139 11.1251 3.62513 12.3751 3.62513C12.5408 3.62513 12.6998 3.69098 12.817 3.80818C12.9342 3.92539 13.0001 4.08436 13.0001 4.25012V8.62505Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6 8.00002L7.33333 9.33335L10 6.66669" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3.07136 7.95724L7.86916 3.05405C7.93967 2.98199 8.06036 2.98198 8.13087 3.05405L12.9286 7.95724C13.0866 8.11865 12.9652 8.38015 12.7324 8.38015H10.226V12.748C10.226 12.8872 10.1065 13 9.95892 13H6.04111C5.89358 13 5.77399 12.8872 5.77399 12.748V8.38015H3.26765C3.03479 8.38015 2.91342 8.11865 3.07136 7.95724Z" stroke="black" stroke-width="1.5"/>
+<path d="M3.07136 7.95724L7.86916 3.05405C7.93967 2.98199 8.06036 2.98198 8.13087 3.05405L12.9286 7.95724C13.0866 8.11865 12.9652 8.38015 12.7324 8.38015H10.226V12.748C10.226 12.8872 10.1065 13 9.95892 13H6.04111C5.89358 13 5.77399 12.8872 5.77399 12.748V8.38015H3.26765C3.03479 8.38015 2.91342 8.11865 3.07136 7.95724Z" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.9999 2.99988L2.99976 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.9999 2.99988L2.99976 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,8 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M2 5H4" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M8 5L14 5" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M12 11L14 11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<path d="M2 11H8" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<circle cx="6" cy="5" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
-<circle cx="10" cy="11" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M2 5H4" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M8 5L14 5" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M12 11L14 11" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<path d="M2 11H8" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<circle cx="6" cy="5" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
+<circle cx="10" cy="11" r="2" fill="black" fill-opacity="0.1" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M13.9999 11.4V12C13.9999 12.3 13.6999 12.6 13.3999 12.6H2.59976C2.29976 12.6 1.99976 12.3 1.99976 12V11.4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.9999 11.4V12C13.9999 12.3 13.6999 12.6 13.3999 12.6H2.59976C2.29976 12.6 1.99976 12.3 1.99976 12V11.4" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.762 10.1a1.2 1.2 0 0 0-.862-.862l-3.68-.95a.3.3 0 0 1 0-.577l3.68-.95a1.2 1.2 0 0 0 .862-.86l.95-3.682a.3.3 0 0 1 .577 0L9.238 5.9a1.2 1.2 0 0 0 .862.862l3.68.949a.3.3 0 0 1 0 .578l-3.68.949a1.2 1.2 0 0 0-.862.862l-.95 3.68a.3.3 0 0 1-.577 0l-.949-3.68Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M6.762 10.1a1.2 1.2 0 0 0-.862-.862l-3.68-.95a.3.3 0 0 1 0-.577l3.68-.95a1.2 1.2 0 0 0 .862-.86l.95-3.682a.3.3 0 0 1 .577 0L9.238 5.9a1.2 1.2 0 0 0 .862.862l3.68.949a.3.3 0 0 1 0 .578l-3.68.949a1.2 1.2 0 0 0-.862.862l-.95 3.68a.3.3 0 0 1-.577 0l-.949-3.68Z"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="8" y="2" width="6" height="12" fill="black" fill-opacity="0.25"/>
-<path d="M13.4 2H2.6C2.26863 2 2 2.26863 2 2.6V13.4C2 13.7314 2.26863 14 2.6 14H13.4C13.7314 14 14 13.7314 14 13.4V2.6C14 2.26863 13.7314 2 13.4 2Z" stroke="black" stroke-width="1.5"/>
-<path d="M8 2L8 14" stroke="black" stroke-width="1.5"/>
+<path d="M13.4 2H2.6C2.26863 2 2 2.26863 2 2.6V13.4C2 13.7314 2.26863 14 2.6 14H13.4C13.7314 14 14 13.7314 14 13.4V2.6C14 2.26863 13.7314 2 13.4 2Z" stroke="black" stroke-width="1.2"/>
+<path d="M8 2L8 14" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10 2.833h3v3M6 2.833H3v3"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 13.833V9.028a2.401 2.401 0 0 0-.165-.9 2.325 2.325 0 0 0-.486-.763L3 2.833M10 5.833l3-3"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M10 2.833h3v3M6 2.833H3v3"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 13.833V9.028a2.401 2.401 0 0 0-.165-.9 2.325 2.325 0 0 0-.486-.763L3 2.833M10 5.833l3-3"/></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="8" cy="8" r="1.25" fill="black" stroke="black" stroke-width="0.5"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6 8H10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6 8H10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6 8H10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 6V10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6 8H10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 6V10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 10.8V5.2C5 5.08954 5.08954 5 5.2 5H10.8C10.9105 5 11 5.08954 11 5.2V10.8C11 10.9105 10.9105 11 10.8 11H5.2C5.08954 11 5 10.9105 5 10.8Z" fill="black" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
+<path d="M5 10.8V5.2C5 5.08954 5.08954 5 5.2 5H10.8C10.9105 5 11 5.08954 11 5.2V10.8C11 10.9105 10.9105 11 10.8 11H5.2C5.08954 11 5 10.9105 5 10.8Z" fill="black" stroke="black" stroke-width="1.2" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7.333 11.333a2.667 2.667 0 1 1-5.333 0v-8A1.333 1.333 0 0 1 3.333 2H6a1.333 1.333 0 0 1 1.333 1.333v8Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M11.133 8.667h1.534A1.333 1.333 0 0 1 14 10v2.667A1.334 1.334 0 0 1 12.667 14h-8M4.667 11.333h.006"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7.333 5.333 8.867 3.8a1.6 1.6 0 0 1 2.269.003L12.4 5.067a1.6 1.6 0 0 1 .017 2.289L6.6 13.2"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M7.333 11.333a2.667 2.667 0 1 1-5.333 0v-8A1.333 1.333 0 0 1 3.333 2H6a1.333 1.333 0 0 1 1.333 1.333v8Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M11.133 8.667h1.534A1.333 1.333 0 0 1 14 10v2.667A1.334 1.334 0 0 1 12.667 14h-8M4.667 11.333h.006"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M7.333 5.333 8.867 3.8a1.6 1.6 0 0 1 2.269.003L12.4 5.067a1.6 1.6 0 0 1 .017 2.289L6.6 13.2"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10.3333 8H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6.33325 12L10.3333 8L6.33325 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13 3.33331V12.6666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.3333 8H2" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.33325 12L10.3333 8L6.33325 4" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13 3.33331V12.6666" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.2782 2.49951H3.72184C3.04677 2.49951 2.49951 3.04677 2.49951 3.72184V12.2782C2.49951 12.9532 3.04677 13.5005 3.72184 13.5005H12.2782C12.9532 13.5005 13.5005 12.9532 13.5005 12.2782V3.72184C13.5005 3.04677 12.9532 2.49951 12.2782 2.49951Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 10.7502H10.7502" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.24976 9.21777L7.08325 7.38428L5.24976 5.55078" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.2782 2.49951H3.72184C3.04677 2.49951 2.49951 3.04677 2.49951 3.72184V12.2782C2.49951 12.9532 3.04677 13.5005 3.72184 13.5005H12.2782C12.9532 13.5005 13.5005 12.9532 13.5005 12.2782V3.72184C13.5005 3.04677 12.9532 2.49951 12.2782 2.49951Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 10.7502H10.7502" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.24976 9.21777L7.08325 7.38428L5.24976 5.55078" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3.333 2A1.333 1.333 0 0 0 2 3.333M12.667 2A1.334 1.334 0 0 1 14 3.333M14 12.667A1.334 1.334 0 0 1 12.667 14M3.333 14A1.334 1.334 0 0 1 2 12.667M6 2h.667M6 14h.667M9.333 2H10M9.333 14H10M2 6v.667M14 6v.667M2 9.333V10M14 9.333V10M4.667 5.333H10M4.667 8h6.666M4.667 10.667h4"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M3.333 2A1.333 1.333 0 0 0 2 3.333M12.667 2A1.334 1.334 0 0 1 14 3.333M14 12.667A1.334 1.334 0 0 1 12.667 14M3.333 14A1.334 1.334 0 0 1 2 12.667M6 2h.667M6 14h.667M9.333 2H10M9.333 14H10M2 6v.667M14 6v.667M2 9.333V10M14 9.333V10M4.667 5.333H10M4.667 8h6.666M4.667 10.667h4"/></svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.33333 8H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.6667 5H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9 11H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12 7V11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M14 9H10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.33333 8H2" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.6667 5H2" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 11H2" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12 7V11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M14 9H10" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.31254 12.549C7.3841 13.0987 8.61676 13.2476 9.78839 12.9688C10.96 12.6901 11.9936 12.0021 12.7028 11.0287C13.412 10.0554 13.7503 8.8607 13.6566 7.66002C13.5629 6.45934 13.0435 5.33159 12.1919 4.48C11.3403 3.62841 10.2126 3.10898 9.01188 3.01531C7.8112 2.92164 6.61655 3.2599 5.64319 3.96912C4.66984 4.67834 3.9818 5.71188 3.70306 6.88351C3.42432 8.05514 3.5732 9.2878 4.12289 10.3594L3 13.6719L6.31254 12.549Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.31254 12.549C7.3841 13.0987 8.61676 13.2476 9.78839 12.9688C10.96 12.6901 11.9936 12.0021 12.7028 11.0287C13.412 10.0554 13.7503 8.8607 13.6566 7.66002C13.5629 6.45934 13.0435 5.33159 12.1919 4.48C11.3403 3.62841 10.2126 3.10898 9.01188 3.01531C7.8112 2.92164 6.61655 3.2599 5.64319 3.96912C4.66984 4.67834 3.9818 5.71188 3.70306 6.88351C3.42432 8.05514 3.5732 9.2878 4.12289 10.3594L3 13.6719L6.31254 12.549Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,6 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8.98795 10.4323C9.40771 10.9919 9.99294 11.4054 10.6607 11.614C11.3285 11.8226 12.045 11.8158 12.7087 11.5945C13.3724 11.3733 13.9497 10.9488 14.3588 10.3813C14.7678 9.81373 14.9879 9.13186 14.9879 8.43225C14.9879 7.6366 14.6719 6.87354 14.1093 6.31093C13.5467 5.74832 12.7836 5.43225 11.9879 5.43225C11.6685 5.43225 11.3595 5.47897 11.0677 5.56586C10.0571 5.86681 9.46945 6.84992 8.98796 7.78806V7.78806" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8.01318 5.93652V8.60319H10.6799" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8.01318 5.93652V8.60319H10.6799" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.00558 12.4263C6.16246 12.4494 5.3211 12.2612 4.56083 11.8712L1.24829 12.994L2.37119 9.68151C1.8215 8.60995 1.67261 7.37729 1.95135 6.20566C2.23009 5.03403 2.91813 4.00048 3.89148 3.29126C4.86484 2.58204 6.05949 2.24379 7.26018 2.33746C7.86645 2.38475 8.45413 2.54061 8.99705 2.79296" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.98795 10.4323C9.40771 10.9919 9.99294 11.4054 10.6607 11.614C11.3285 11.8226 12.045 11.8158 12.7087 11.5945C13.3724 11.3733 13.9497 10.9488 14.3588 10.3813C14.7678 9.81373 14.9879 9.13186 14.9879 8.43225C14.9879 7.6366 14.6719 6.87354 14.1093 6.31093C13.5467 5.74832 12.7836 5.43225 11.9879 5.43225C11.6685 5.43225 11.3595 5.47897 11.0677 5.56586C10.0571 5.86681 9.46945 6.84992 8.98796 7.78806V7.78806" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.01318 5.93652V8.60319H10.6799" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.01318 5.93652V8.60319H10.6799" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.00558 12.4263C6.16246 12.4494 5.3211 12.2612 4.56083 11.8712L1.24829 12.994L2.37119 9.68151C1.8215 8.60995 1.67261 7.37729 1.95135 6.20566C2.23009 5.03403 2.91813 4.00048 3.89148 3.29126C4.86484 2.58204 6.05949 2.24379 7.26018 2.33746C7.86645 2.38475 8.45413 2.54061 8.99705 2.79296" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" clip-path="url(#a)"><path d="M3.013 8.57h2.478V3.2H3.013a.413.413 0 0 0-.413.413v4.544a.413.413 0 0 0 .413.413ZM5.491 8.57l2.066 4.13a1.652 1.652 0 0 0 1.652-1.652v-1.24h3.304a.826.826 0 0 0 .82-.929l-.62-4.956a.827.827 0 0 0-.82-.723H5.492"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" clip-path="url(#a)"><path d="M3.013 8.57h2.478V3.2H3.013a.413.413 0 0 0-.413.413v4.544a.413.413 0 0 0 .413.413ZM5.491 8.57l2.066 4.13a1.652 1.652 0 0 0 1.652-1.652v-1.24h3.304a.826.826 0 0 0 .82-.929l-.62-4.956a.827.827 0 0 0-.82-.723H5.492"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" clip-path="url(#a)"><path d="M3.013 7.33h2.478v5.37H3.013a.413.413 0 0 1-.413-.413V7.743a.413.413 0 0 1 .413-.413ZM5.491 7.33 7.557 3.2a1.652 1.652 0 0 1 1.652 1.652v1.24h3.304a.826.826 0 0 1 .82.929l-.62 4.956a.827.827 0 0 1-.82.723H5.492"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" clip-path="url(#a)"><path d="M3.013 7.33h2.478v5.37H3.013a.413.413 0 0 1-.413-.413V7.743a.413.413 0 0 1 .413-.413ZM5.491 7.33 7.557 3.2a1.652 1.652 0 0 1 1.652 1.652v1.24h3.304a.826.826 0 0 1 .82.929l-.62 4.956a.827.827 0 0 1-.82.723H5.492"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 13A5 5 0 1 0 8 3a5 5 0 0 0 0 10Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m5.949 9.026 1.538 1.025 2.564-3.59"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M8 13A5 5 0 1 0 8 3a5 5 0 0 0 0 10Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="m5.949 9.026 1.538 1.025 2.564-3.59"/></svg>
@@ -1,10 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,11 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8.00016 8.66665C8.36835 8.66665 8.66683 8.36817 8.66683 7.99998C8.66683 7.63179 8.36835 7.33331 8.00016 7.33331C7.63197 7.33331 7.3335 7.63179 7.3335 7.99998C7.3335 8.36817 7.63197 8.66665 8.00016 8.66665Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8.00016 8.66665C8.36835 8.66665 8.66683 8.36817 8.66683 7.99998C8.66683 7.63179 8.36835 7.33331 8.00016 7.33331C7.63197 7.33331 7.3335 7.63179 7.3335 7.99998C7.3335 8.36817 7.63197 8.66665 8.00016 8.66665Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M9.5 2.5H6.5C6.22386 2.5 6 2.83579 6 3.25V4.75C6 5.16421 6.22386 5.5 6.5 5.5H9.5C9.77614 5.5 10 5.16421 10 4.75V3.25C10 2.83579 9.77614 2.5 9.5 2.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10 3.5H11C11.2652 3.5 11.5196 3.61706 11.7071 3.82544C11.8946 4.03381 12 4.31643 12 4.61111V12.3889C12 12.6836 11.8946 12.9662 11.7071 13.1746C11.5196 13.3829 11.2652 13.5 11 13.5H5C4.73478 13.5 4.48043 13.3829 4.29289 13.1746C4.10536 12.9662 4 12.6836 4 12.3889V4.61111C4 4.31643 4.10536 4.03381 4.29289 3.82544C4.48043 3.61706 4.73478 3.5 5 3.5H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.5 2.5H6.5C6.22386 2.5 6 2.83579 6 3.25V4.75C6 5.16421 6.22386 5.5 6.5 5.5H9.5C9.77614 5.5 10 5.16421 10 4.75V3.25C10 2.83579 9.77614 2.5 9.5 2.5Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10 3.5H11C11.2652 3.5 11.5196 3.61706 11.7071 3.82544C11.8946 4.03381 12 4.31643 12 4.61111V12.3889C12 12.6836 11.8946 12.9662 11.7071 13.1746C11.5196 13.3829 11.2652 13.5 11 13.5H5C4.73478 13.5 4.48043 13.3829 4.29289 13.1746C4.10536 12.9662 4 12.6836 4 12.3889V4.61111C4 4.31643 4.10536 4.03381 4.29289 3.82544C4.48043 3.61706 4.73478 3.5 5 3.5H6" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M9.50002 2.5H5C4.73478 2.5 4.48043 2.6159 4.29289 2.82219C4.10535 3.02848 4 3.30826 4 3.6V12.3999C4 12.6917 4.10535 12.9715 4.29289 13.1778C4.48043 13.3841 4.73478 13.5 5 13.5H11C11.2652 13.5 11.5195 13.3841 11.7071 13.1778C11.8946 12.9715 12 12.6917 12 12.3999V5.25L9.50002 2.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9.3427 6.82379L6.65698 9.5095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6.65698 6.82379L9.3427 9.5095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.50002 2.5H5C4.73478 2.5 4.48043 2.6159 4.29289 2.82219C4.10535 3.02848 4 3.30826 4 3.6V12.3999C4 12.6917 4.10535 12.9715 4.29289 13.1778C4.48043 13.3841 4.73478 13.5 5 13.5H11C11.2652 13.5 11.5195 13.3841 11.7071 13.1778C11.8946 12.9715 12 12.6917 12 12.3999V5.25L9.50002 2.5Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.3427 6.82379L6.65698 9.5095" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.65698 6.82379L9.3427 9.5095" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M13.7244 11.5299L9.01711 3.2922C8.91447 3.11109 8.76562 2.96045 8.58576 2.85564C8.4059 2.75084 8.20145 2.69562 7.99328 2.69562C7.7851 2.69562 7.58066 2.75084 7.40079 2.85564C7.22093 2.96045 7.07209 3.11109 6.96945 3.2922L2.26218 11.5299C2.15844 11.7096 2.10404 11.9135 2.1045 12.121C2.10495 12.3285 2.16026 12.5321 2.2648 12.7113C2.36934 12.8905 2.5194 13.0389 2.69978 13.1415C2.88015 13.244 3.08443 13.297 3.2919 13.2951H12.7064C12.9129 13.2949 13.1157 13.2404 13.2944 13.137C13.4731 13.0336 13.6215 12.8851 13.7247 12.7062C13.8278 12.5273 13.8821 12.3245 13.882 12.118C13.882 11.9115 13.8276 11.7087 13.7244 11.5299Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.99927 6.23425V8.58788" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.99927 10.9415H8.00492" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13.7244 11.5299L9.01711 3.2922C8.91447 3.11109 8.76562 2.96045 8.58576 2.85564C8.4059 2.75084 8.20145 2.69562 7.99328 2.69562C7.7851 2.69562 7.58066 2.75084 7.40079 2.85564C7.22093 2.96045 7.07209 3.11109 6.96945 3.2922L2.26218 11.5299C2.15844 11.7096 2.10404 11.9135 2.1045 12.121C2.10495 12.3285 2.16026 12.5321 2.2648 12.7113C2.36934 12.8905 2.5194 13.0389 2.69978 13.1415C2.88015 13.244 3.08443 13.297 3.2919 13.2951H12.7064C12.9129 13.2949 13.1157 13.2404 13.2944 13.137C13.4731 13.0336 13.6215 12.8851 13.7247 12.7062C13.8278 12.5273 13.8821 12.3245 13.882 12.118C13.882 11.9115 13.8276 11.7087 13.7244 11.5299Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.99927 6.23425V8.58788" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.99927 10.9415H8.00492" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.8 13C13.1183 13 13.4235 12.8761 13.6486 12.6554C13.8735 12.4349 14 12.1356 14 11.8236V5.94118C14 5.62916 13.8735 5.32992 13.6486 5.10929C13.4235 4.88866 13.1183 4.76471 12.8 4.76471H8.06C7.8593 4.76664 7.66133 4.71919 7.48418 4.6267C7.30703 4.53421 7.15637 4.39964 7.046 4.2353L6.56 3.52941C6.45073 3.36675 6.30199 3.23322 6.1271 3.14082C5.95221 3.04842 5.75666 3.00004 5.558 3H3.2C2.88174 3 2.57651 3.12395 2.35148 3.34458C2.12643 3.56521 2 3.86445 2 4.17647V11.8236C2 12.1356 2.12643 12.4349 2.35148 12.6554C2.57651 12.8761 2.88174 13 3.2 13H12.8Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.8 13C13.1183 13 13.4235 12.8761 13.6486 12.6554C13.8735 12.4349 14 12.1356 14 11.8236V5.94118C14 5.62916 13.8735 5.32992 13.6486 5.10929C13.4235 4.88866 13.1183 4.76471 12.8 4.76471H8.06C7.8593 4.76664 7.66133 4.71919 7.48418 4.6267C7.30703 4.53421 7.15637 4.39964 7.046 4.2353L6.56 3.52941C6.45073 3.36675 6.30199 3.23322 6.1271 3.14082C5.95221 3.04842 5.75666 3.00004 5.558 3H3.2C2.88174 3 2.57651 3.12395 2.35148 3.34458C2.12643 3.56521 2 3.86445 2 4.17647V11.8236C2 12.1356 2.12643 12.4349 2.35148 12.6554C2.57651 12.8761 2.88174 13 3.2 13H12.8Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M9 8.5L4.94864 12.6222C4.71647 12.8544 4.40157 12.9848 4.07323 12.9848C3.74488 12.9848 3.42999 12.8544 3.19781 12.6222C2.96564 12.39 2.83521 12.0751 2.83521 11.7468C2.83521 11.4185 2.96564 11.1036 3.19781 10.8714L7.5 6.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.8352 9.98474L13.8352 6.98474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.8352 7.42495L11.7634 6.4298C11.5533 6.23484 11.4353 5.97039 11.4352 5.69462V5.08526L10.1696 3.91022C9.54495 3.33059 8.69961 3.00261 7.81649 2.99722L5.83521 2.98474L6.35041 3.41108C6.71634 3.71233 7.00935 4.08216 7.21013 4.4962C7.4109 4.91024 7.51488 5.35909 7.51521 5.81316L7.5 6.5L9 8.5L9.5 8C9.5 8 9.87337 7.79457 10.0834 7.98959L11.1552 8.98474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 8.5L4.94864 12.6222C4.71647 12.8544 4.40157 12.9848 4.07323 12.9848C3.74488 12.9848 3.42999 12.8544 3.19781 12.6222C2.96564 12.39 2.83521 12.0751 2.83521 11.7468C2.83521 11.4185 2.96564 11.1036 3.19781 10.8714L7.5 6.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.8352 9.98474L13.8352 6.98474" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.8352 7.42495L11.7634 6.4298C11.5533 6.23484 11.4353 5.97039 11.4352 5.69462V5.08526L10.1696 3.91022C9.54495 3.33059 8.69961 3.00261 7.81649 2.99722L5.83521 2.98474L6.35041 3.41108C6.71634 3.71233 7.00935 4.08216 7.21013 4.4962C7.4109 4.91024 7.51488 5.35909 7.51521 5.81316L7.5 6.5L9 8.5L9.5 8C9.5 8 9.87337 7.79457 10.0834 7.98959L11.1552 8.98474" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.5 12C6.65203 12.304 6.87068 12.5565 7.13399 12.7321C7.39729 12.9076 7.69597 13 8 13C8.30403 13 8.60271 12.9076 8.86601 12.7321C9.12932 12.5565 9.34797 12.304 9.5 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3.63088 9.21874C3.56556 9.28556 3.52246 9.36865 3.50681 9.45791C3.49116 9.54718 3.50364 9.63876 3.54273 9.72152C3.58183 9.80429 3.64585 9.87467 3.72701 9.92409C3.80817 9.97352 3.90298 9.99987 3.99989 9.99994H12.0001C12.097 9.99997 12.1918 9.97372 12.273 9.92439C12.3542 9.87505 12.4183 9.80476 12.4575 9.72205C12.4967 9.63934 12.5093 9.54778 12.4938 9.45851C12.4783 9.36924 12.4353 9.2861 12.3701 9.21921C11.705 8.57941 11 7.89947 11 5.79994C11 5.05733 10.684 4.34514 10.1213 3.82004C9.55872 3.29494 8.79564 2.99994 7.99997 2.99994C7.20431 2.99994 6.44123 3.29494 5.87861 3.82004C5.31599 4.34514 4.99991 5.05733 4.99991 5.79994C4.99991 7.89947 4.2944 8.57941 3.63088 9.21874Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.5 12C6.65203 12.304 6.87068 12.5565 7.13399 12.7321C7.39729 12.9076 7.69597 13 8 13C8.30403 13 8.60271 12.9076 8.86601 12.7321C9.12932 12.5565 9.34797 12.304 9.5 12" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.63088 9.21874C3.56556 9.28556 3.52246 9.36865 3.50681 9.45791C3.49116 9.54718 3.50364 9.63876 3.54273 9.72152C3.58183 9.80429 3.64585 9.87467 3.72701 9.92409C3.80817 9.97352 3.90298 9.99987 3.99989 9.99994H12.0001C12.097 9.99997 12.1918 9.97372 12.273 9.92439C12.3542 9.87505 12.4183 9.80476 12.4575 9.72205C12.4967 9.63934 12.5093 9.54778 12.4938 9.45851C12.4783 9.36924 12.4353 9.2861 12.3701 9.21921C11.705 8.57941 11 7.89947 11 5.79994C11 5.05733 10.684 4.34514 10.1213 3.82004C9.55872 3.29494 8.79564 2.99994 7.99997 2.99994C7.20431 2.99994 6.44123 3.29494 5.87861 3.82004C5.31599 4.34514 4.99991 5.05733 4.99991 5.79994C4.99991 7.89947 4.2944 8.57941 3.63088 9.21874Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.5871 5.40582C12.8514 5.14152 13 4.78304 13 4.40922C13 4.03541 12.8516 3.67688 12.5873 3.41252C12.323 3.14816 11.9645 2.99962 11.5907 2.99957C11.2169 2.99953 10.8584 3.14798 10.594 3.41227L3.92098 10.0869C3.80488 10.2027 3.71903 10.3452 3.67097 10.5019L3.01047 12.678C2.99754 12.7212 2.99657 12.7672 3.00764 12.8109C3.01872 12.8547 3.04143 12.8946 3.07337 12.9265C3.1053 12.9584 3.14528 12.981 3.18905 12.992C3.23282 13.003 3.27875 13.002 3.32197 12.989L5.49849 12.329C5.65508 12.2813 5.79758 12.196 5.91349 12.0805L12.5871 5.40582Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M9 5L11 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.5871 5.40582C12.8514 5.14152 13 4.78304 13 4.40922C13 4.03541 12.8516 3.67688 12.5873 3.41252C12.323 3.14816 11.9645 2.99962 11.5907 2.99957C11.2169 2.99953 10.8584 3.14798 10.594 3.41227L3.92098 10.0869C3.80488 10.2027 3.71903 10.3452 3.67097 10.5019L3.01047 12.678C2.99754 12.7212 2.99657 12.7672 3.00764 12.8109C3.01872 12.8547 3.04143 12.8946 3.07337 12.9265C3.1053 12.9584 3.14528 12.981 3.18905 12.992C3.23282 13.003 3.27875 13.002 3.32197 12.989L5.49849 12.329C5.65508 12.2813 5.79758 12.196 5.91349 12.0805L12.5871 5.40582Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9 5L11 7" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,7 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 5.66667V4.33333C3 3.97971 3.14048 3.64057 3.39052 3.39052C3.64057 3.14048 3.97971 3 4.33333 3H5.66667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10.3333 3H11.6666C12.0202 3 12.3593 3.14048 12.6094 3.39052C12.8594 3.64057 12.9999 3.97971 12.9999 4.33333V5.66667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12.9999 10.3333V11.6666C12.9999 12.0203 12.8594 12.3594 12.6094 12.6095C12.3593 12.8595 12.0202 13 11.6666 13H10.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.66667 13H4.33333C3.97971 13 3.64057 12.8595 3.39052 12.6095C3.14048 12.3594 3 12.0203 3 11.6666V10.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.5 8H10.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 5.66667V4.33333C3 3.97971 3.14048 3.64057 3.39052 3.39052C3.64057 3.14048 3.97971 3 4.33333 3H5.66667" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10.3333 3H11.6666C12.0202 3 12.3593 3.14048 12.6094 3.39052C12.8594 3.64057 12.9999 3.97971 12.9999 4.33333V5.66667" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.9999 10.3333V11.6666C12.9999 12.0203 12.8594 12.3594 12.6094 12.6095C12.3593 12.8595 12.0202 13 11.6666 13H10.3333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.66667 13H4.33333C3.97971 13 3.64057 12.8595 3.39052 12.6095C3.14048 12.3594 3 12.0203 3 11.6666V10.3333" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.5 8H10.5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.57132 13.7143C5.20251 13.7143 5.71418 13.2026 5.71418 12.5714C5.71418 11.9403 5.20251 11.4286 4.57132 11.4286C3.94014 11.4286 3.42847 11.9403 3.42847 12.5714C3.42847 13.2026 3.94014 13.7143 4.57132 13.7143Z" fill="black"/>
-<path d="M10.2856 2.85712V5.71426M10.2856 5.71426V8.5714M10.2856 5.71426H13.1428M10.2856 5.71426H7.42847M10.2856 5.71426L12.1904 3.80949M10.2856 5.71426L8.38084 7.61906M10.2856 5.71426L12.1904 7.61906M10.2856 5.71426L8.38084 3.80949" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M10.2856 2.85712V5.71426M10.2856 5.71426V8.5714M10.2856 5.71426H13.1428M10.2856 5.71426H7.42847M10.2856 5.71426L12.1904 3.80949M10.2856 5.71426L8.38084 7.61906M10.2856 5.71426L12.1904 7.61906M10.2856 5.71426L8.38084 3.80949" stroke="black" stroke-width="1.2" stroke-linecap="round"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M13 13L11 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M7.5 12C9.98528 12 12 9.98528 12 7.5C12 5.01472 9.98528 3 7.5 3C5.01472 3 3 5.01472 3 7.5C3 9.98528 5.01472 12 7.5 12Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M13 13L11 11" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.5 12C9.98528 12 12 9.98528 12 7.5C12 5.01472 9.98528 3 7.5 3C5.01472 3 3 5.01472 3 7.5C3 9.98528 5.01472 12 7.5 12Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.2782 2.49951H3.72184C3.04677 2.49951 2.49951 3.04677 2.49951 3.72184V12.2782C2.49951 12.9532 3.04677 13.5005 3.72184 13.5005H12.2782C12.9532 13.5005 13.5005 12.9532 13.5005 12.2782V3.72184C13.5005 3.04677 12.9532 2.49951 12.2782 2.49951Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 10.7502H10.7502" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M5.24976 9.21777L7.08325 7.38428L5.24976 5.55078" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12.2782 2.49951H3.72184C3.04677 2.49951 2.49951 3.04677 2.49951 3.72184V12.2782C2.49951 12.9532 3.04677 13.5005 3.72184 13.5005H12.2782C12.9532 13.5005 13.5005 12.9532 13.5005 12.2782V3.72184C13.5005 3.04677 12.9532 2.49951 12.2782 2.49951Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 10.7502H10.7502" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.24976 9.21777L7.08325 7.38428L5.24976 5.55078" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M9.95231 10.2159C10.0803 9.58974 9.95231 9.57261 10.9111 8.46959C11.4686 7.82822 11.8699 7.09214 11.8699 6.27818C11.8699 5.28184 11.4658 4.32631 10.7467 3.62179C10.0275 2.91728 9.05201 2.52148 8.03492 2.52148C7.01782 2.52148 6.04239 2.91728 5.32319 3.62179C4.604 4.32631 4.19995 5.28184 4.19995 6.27818C4.19995 6.9043 4.32779 7.65565 5.1587 8.46959C6.11744 9.59098 5.98965 9.58974 6.11748 10.2159M9.95231 10.2159V12.2989C9.95231 12.9504 9.41327 13.4786 8.7482 13.4786H7.32165C6.65658 13.4786 6.11744 12.9504 6.11744 12.2989L6.11748 10.2159M9.95231 10.2159H8.03492H6.11748" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M9.95231 10.2159C10.0803 9.58974 9.95231 9.57261 10.9111 8.46959C11.4686 7.82822 11.8699 7.09214 11.8699 6.27818C11.8699 5.28184 11.4658 4.32631 10.7467 3.62179C10.0275 2.91728 9.05201 2.52148 8.03492 2.52148C7.01782 2.52148 6.04239 2.91728 5.32319 3.62179C4.604 4.32631 4.19995 5.28184 4.19995 6.27818C4.19995 6.9043 4.32779 7.65565 5.1587 8.46959C6.11744 9.59098 5.98965 9.58974 6.11748 10.2159M9.95231 10.2159V12.2989C9.95231 12.9504 9.41327 13.4786 8.7482 13.4786H7.32165C6.65658 13.4786 6.11744 12.9504 6.11744 12.2989L6.11748 10.2159M9.95231 10.2159H8.03492H6.11748" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.99993 13.4804C11.0267 13.4804 13.4803 11.0267 13.4803 7.99999C13.4803 4.97325 11.0267 2.51959 7.99993 2.51959C4.97319 2.51959 2.51953 4.97325 2.51953 7.99999C2.51953 11.0267 4.97319 13.4804 7.99993 13.4804Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M8 3C6.71611 4.34807 6 6.13836 6 7.99999C6 9.86163 6.71611 11.6519 8 13C9.28387 11.6519 10 9.86163 10 7.99999C10 6.13836 9.28387 4.34807 8 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M3.24121 7.04827C4.52425 8.27022 6.22817 8.95178 7.99999 8.95178C9.77182 8.95178 11.4757 8.27022 12.7588 7.04827" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.99993 13.4804C11.0267 13.4804 13.4803 11.0267 13.4803 7.99999C13.4803 4.97325 11.0267 2.51959 7.99993 2.51959C4.97319 2.51959 2.51953 4.97325 2.51953 7.99999C2.51953 11.0267 4.97319 13.4804 7.99993 13.4804Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 3C6.71611 4.34807 6 6.13836 6 7.99999C6 9.86163 6.71611 11.6519 8 13C9.28387 11.6519 10 9.86163 10 7.99999C10 6.13836 9.28387 4.34807 8 3Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3.24121 7.04827C4.52425 8.27022 6.22817 8.95178 7.99999 8.95178C9.77182 8.95178 11.4757 8.27022 12.7588 7.04827" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M3 5L13 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M12 5V12.875C12 13.4375 11.4286 14 10.8571 14H5.14286C4.57143 14 4 13.4375 4 12.875V5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M10 5V3C10 2.44772 9.55228 2 9 2H7C6.44772 2 6 2.44772 6 3V5" stroke="black" stroke-width="1.5"/>
+<path d="M3 5L13 5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M12 5V12.875C12 13.4375 11.4286 14 10.8571 14H5.14286C4.57143 14 4 13.4375 4 12.875V5" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10 5V3C10 2.44772 9.55228 2 9 2H7C6.44772 2 6 2.44772 6 3V5" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.6 5v3.6h3.6"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.4 11A5.4 5.4 0 0 0 8 5.6a5.4 5.4 0 0 0-3.6 1.38L2.6 8.6"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.6 5v3.6h3.6"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M13.4 11A5.4 5.4 0 0 0 8 5.6a5.4 5.4 0 0 0-3.6 1.38L2.6 8.6"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2 13.4a4.8 4.8 0 0 1 7.975-3.6"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.8 8.6a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM10.4 12.2l1.2 1.2L14 11"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2 13.4a4.8 4.8 0 0 1 7.975-3.6"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M6.8 8.6a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM10.4 12.2l1.2 1.2L14 11"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.79118 8.27005C8.27568 8.27005 9.4791 7.06663 9.4791 5.58214C9.4791 4.09765 8.27568 2.89423 6.79118 2.89423C5.30669 2.89423 4.10327 4.09765 4.10327 5.58214C4.10327 7.06663 5.30669 8.27005 6.79118 8.27005Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M6.79112 8.60443C4.19441 8.60443 2.08936 10.7095 2.08936 13.3062H11.4929C11.4929 10.7095 9.38784 8.60443 6.79112 8.60443Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-<path d="M14.6984 12.9263C14.6984 10.8893 13.4895 8.99736 12.2806 8.09067C12.6779 7.79254 12.9957 7.40104 13.2057 6.95083C13.4157 6.50062 13.5115 6.00558 13.4846 5.50952C13.4577 5.01346 13.309 4.53168 13.0515 4.10681C12.7941 3.68194 12.4358 3.3271 12.0085 3.07367" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.79118 8.27005C8.27568 8.27005 9.4791 7.06663 9.4791 5.58214C9.4791 4.09765 8.27568 2.89423 6.79118 2.89423C5.30669 2.89423 4.10327 4.09765 4.10327 5.58214C4.10327 7.06663 5.30669 8.27005 6.79118 8.27005Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6.79112 8.60443C4.19441 8.60443 2.08936 10.7095 2.08936 13.3062H11.4929C11.4929 10.7095 9.38784 8.60443 6.79112 8.60443Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M14.6984 12.9263C14.6984 10.8893 13.4895 8.99736 12.2806 8.09067C12.6779 7.79254 12.9957 7.40104 13.2057 6.95083C13.4157 6.50062 13.5115 6.00558 13.4846 5.50952C13.4577 5.01346 13.309 4.53168 13.0515 4.10681C12.7941 3.68194 12.4358 3.3271 12.0085 3.07367" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2 13.433a4.8 4.8 0 0 1 6.493-4.492M13.627 10.809a1.275 1.275 0 0 0-1.803-1.803l-2.406 2.408a1.2 1.2 0 0 0-.303.512l-.502 1.722a.3.3 0 0 0 .372.372l1.722-.502c.193-.057.37-.161.512-.304l2.408-2.405Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M6.8 8.633a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2 13.433a4.8 4.8 0 0 1 6.493-4.492M13.627 10.809a1.275 1.275 0 0 0-1.803-1.803l-2.406 2.408a1.2 1.2 0 0 0-.303.512l-.502 1.722a.3.3 0 0 0 .372.372l1.722-.502c.193-.057.37-.161.512-.304l2.408-2.405Z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M6.8 8.633a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"/></svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.84 11.6 9.037 3.199a1.2 1.2 0 0 0-2.089 0l-4.802 8.403a1.2 1.2 0 0 0 1.05 1.8h9.604a1.201 1.201 0 0 0 1.038-1.8ZM8 6v2.667M8 11.333h.007"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M13.84 11.6 9.037 3.199a1.2 1.2 0 0 0-2.089 0l-4.802 8.403a1.2 1.2 0 0 0 1.05 1.8h9.604a1.201 1.201 0 0 0 1.038-1.8ZM8 6v2.667M8 11.333h.007"/></svg>
@@ -1 +1 @@
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path fill="#000" fill-opacity=".157" stroke="#000" stroke-linejoin="round" stroke-width="1.5" d="M9.864 3a.5.5 0 0 1 .353.146l2.636 2.636a.5.5 0 0 1 .147.354v3.728a.5.5 0 0 1-.146.353l-2.637 2.637a.5.5 0 0 1-.353.146H6.136a.5.5 0 0 1-.354-.146l-2.636-2.636A.5.5 0 0 1 3 9.864V6.136a.5.5 0 0 1 .146-.354l2.636-2.636A.5.5 0 0 1 6.136 3h3.728Z"/><path stroke="#000" stroke-linecap="round" stroke-width="1.5" d="m9.5 6.5-3 3m3 0-3-3"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path fill="#000" fill-opacity=".157" stroke="#000" stroke-linejoin="round" stroke-width="1.2" d="M9.864 3a.5.5 0 0 1 .353.146l2.636 2.636a.5.5 0 0 1 .147.354v3.728a.5.5 0 0 1-.146.353l-2.637 2.637a.5.5 0 0 1-.353.146H6.136a.5.5 0 0 1-.354-.146l-2.636-2.636A.5.5 0 0 1 3 9.864V6.136a.5.5 0 0 1 .146-.354l2.636-2.636A.5.5 0 0 1 6.136 3h3.728Z"/><path stroke="#000" stroke-linecap="round" stroke-width="1.2" d="m9.5 6.5-3 3m3 0-3-3"/></svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M8 2.93652L6.9243 6.20697C6.86924 6.37435 6.77565 6.52646 6.65105 6.65105C6.52646 6.77565 6.37435 6.86924 6.20697 6.9243L2.93652 8L6.20697 9.0757C6.37435 9.13076 6.52646 9.22435 6.65105 9.34895C6.77565 9.47354 6.86924 9.62565 6.9243 9.79306L8 13.0635L9.0757 9.79306C9.13076 9.62565 9.22435 9.47354 9.34895 9.34895C9.47354 9.22435 9.62565 9.13076 9.79306 9.0757L13.0635 8L9.79306 6.9243C9.62565 6.86924 9.47354 6.77565 9.34895 6.65105C9.22435 6.52646 9.13076 6.37435 9.0757 6.20697L8 2.93652Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 2.93652L6.9243 6.20697C6.86924 6.37435 6.77565 6.52646 6.65105 6.65105C6.52646 6.77565 6.37435 6.86924 6.20697 6.9243L2.93652 8L6.20697 9.0757C6.37435 9.13076 6.52646 9.22435 6.65105 9.34895C6.77565 9.47354 6.86924 9.62565 6.9243 9.79306L8 13.0635L9.0757 9.79306C9.13076 9.62565 9.22435 9.47354 9.34895 9.34895C9.47354 9.22435 9.62565 9.13076 9.79306 9.0757L13.0635 8L9.79306 6.9243C9.62565 6.86924 9.47354 6.77565 9.34895 6.65105C9.22435 6.52646 9.13076 6.37435 9.0757 6.20697L8 2.93652Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.33334 2V4.66666M2 3.33334H4.66666" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.6665 11.3333V14M11.3333 12.6666H13.9999" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.70519 9.31137C6.13992 9.31137 6.55683 9.13868 6.86423 8.83128C7.17163 8.52389 7.34432 8.10696 7.34432 7.67224C7.34432 6.76744 7.01649 6.36094 6.68868 5.70528C5.98581 4.30022 6.54181 3.04726 7.99998 1.77136C8.32781 3.41049 9.31128 4.98406 10.6226 6.03311C11.9339 7.08215 12.5896 8.3279 12.5896 9.6392C12.5896 10.2419 12.4708 10.8387 12.2402 11.3956C12.0096 11.9524 11.6715 12.4583 11.2453 12.8845C10.8191 13.3107 10.3132 13.6487 9.75633 13.8794C9.1995 14.1101 8.60269 14.2287 7.99998 14.2287C7.39727 14.2287 6.80046 14.1101 6.24362 13.8794C5.68679 13.6487 5.18083 13.3107 4.75465 12.8845C4.32848 12.4583 3.99041 11.9524 3.75976 11.3956C3.52911 10.8387 3.4104 10.2419 3.4104 9.6392C3.4104 8.88324 3.6943 8.13513 4.06606 7.67224C4.06606 8.10696 4.23875 8.52389 4.54615 8.83128C4.85354 9.13868 5.27047 9.31137 5.70519 9.31137Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5.70519 9.31137C6.13992 9.31137 6.55683 9.13868 6.86423 8.83128C7.17163 8.52389 7.34432 8.10696 7.34432 7.67224C7.34432 6.76744 7.01649 6.36094 6.68868 5.70528C5.98581 4.30022 6.54181 3.04726 7.99998 1.77136C8.32781 3.41049 9.31128 4.98406 10.6226 6.03311C11.9339 7.08215 12.5896 8.3279 12.5896 9.6392C12.5896 10.2419 12.4708 10.8387 12.2402 11.3956C12.0096 11.9524 11.6715 12.4583 11.2453 12.8845C10.8191 13.3107 10.3132 13.6487 9.75633 13.8794C9.1995 14.1101 8.60269 14.2287 7.99998 14.2287C7.39727 14.2287 6.80046 14.1101 6.24362 13.8794C5.68679 13.6487 5.18083 13.3107 4.75465 12.8845C4.32848 12.4583 3.99041 11.9524 3.75976 11.3956C3.52911 10.8387 3.4104 10.2419 3.4104 9.6392C3.4104 8.88324 3.6943 8.13513 4.06606 7.67224C4.06606 8.10696 4.23875 8.52389 4.54615 8.83128C4.85354 9.13868 5.27047 9.31137 5.70519 9.31137Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g fill="#000" clip-path="url(#a)"><path fill-opacity=".5" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M5.705 9.311a1.64 1.64 0 0 0 1.64-1.639c0-.905-.328-1.311-.656-1.967C5.986 4.3 6.542 3.047 8 1.771c.328 1.64 1.312 3.213 2.623 4.262 1.311 1.05 1.967 2.295 1.967 3.606a4.59 4.59 0 1 1-9.18 0c0-.756.285-1.504.656-1.967a1.64 1.64 0 0 0 1.64 1.64Z"/><path d="M2.286 4.571a1.143 1.143 0 1 0 0-2.285 1.143 1.143 0 0 0 0 2.285ZM11.429 2.286a1.143 1.143 0 1 0 0-2.286 1.143 1.143 0 0 0 0 2.286ZM14.857 5.714a1.143 1.143 0 1 0 0-2.286 1.143 1.143 0 0 0 0 2.286Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g fill="#000" clip-path="url(#a)"><path fill-opacity=".5" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M5.705 9.311a1.64 1.64 0 0 0 1.64-1.639c0-.905-.328-1.311-.656-1.967C5.986 4.3 6.542 3.047 8 1.771c.328 1.64 1.312 3.213 2.623 4.262 1.311 1.05 1.967 2.295 1.967 3.606a4.59 4.59 0 1 1-9.18 0c0-.756.285-1.504.656-1.967a1.64 1.64 0 0 0 1.64 1.64Z"/><path d="M2.286 4.571a1.143 1.143 0 1 0 0-2.285 1.143 1.143 0 0 0 0 2.285ZM11.429 2.286a1.143 1.143 0 1 0 0-2.286 1.143 1.143 0 0 0 0 2.286ZM14.857 5.714a1.143 1.143 0 1 0 0-2.286 1.143 1.143 0 0 0 0 2.286Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<rect opacity="0.3" x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.5"/>
+<rect opacity="0.3" x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.2"/>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<rect opacity="0.3" x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.5"/>
+<rect opacity="0.3" x="2" y="2" width="12" height="12" rx="2" stroke="black" stroke-width="1.2"/>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
-<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>
-<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
+<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.2"/>
+<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.2"/>
+<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M15 9.33333L14.5 9.66667L12.5 11L10.5 9.66667L10 9.33333" stroke="black" stroke-width="1.5"/>
-<path d="M12.5 11V4.5" stroke="black" stroke-width="1.5"/>
-<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
+<path d="M15 9.33333L14.5 9.66667L12.5 11L10.5 9.66667L10 9.33333" stroke="black" stroke-width="1.2"/>
+<path d="M12.5 11V4.5" stroke="black" stroke-width="1.2"/>
+<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path opacity="0.6" d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
-<path d="M14 8L10 12M14 12L10 8" stroke="black" stroke-width="1.5"/>
+<path opacity="0.6" d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.2"/>
+<path d="M14 8L10 12M14 12L10 8" stroke="black" stroke-width="1.2"/>
</svg>
@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10 6.66667L10.5 6.33333L12.5 5L14.5 6.33333L15 6.66667" stroke="black" stroke-width="1.5"/>
-<path d="M12.5 11V5" stroke="black" stroke-width="1.5"/>
-<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
+<path d="M10 6.66667L10.5 6.33333L12.5 5L14.5 6.33333L15 6.66667" stroke="black" stroke-width="1.2"/>
+<path d="M12.5 11V5" stroke="black" stroke-width="1.2"/>
+<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.2"/>
</svg>
@@ -71,8 +71,8 @@
"ui_font_weight": 400,
// The default font size for text in the UI
"ui_font_size": 16,
- // The default font size for text in the agent panel
- "agent_font_size": 16,
+ // The default font size for text in the agent panel. Falls back to the UI font size if unset.
+ "agent_font_size": null,
// How much to fade out unused code.
"unnecessary_code_fade": 0.3,
// Active pane styling settings.
@@ -887,11 +887,6 @@
},
// The settings for slash commands.
"slash_commands": {
- // Settings for the `/docs` slash command.
- "docs": {
- // Whether `/docs` is enabled.
- "enabled": false
- },
// Settings for the `/project` slash command.
"project": {
// Whether `/project` is enabled.
@@ -1256,7 +1251,9 @@
// Status bar-related settings.
"status_bar": {
// Whether to show the active language button in the status bar.
- "active_language_button": true
+ "active_language_button": true,
+ // Whether to show the cursor position button in the status bar.
+ "cursor_position_button": true
},
// Settings specific to the terminal
"terminal": {
@@ -111,7 +111,7 @@ pub enum AgentThreadEntry {
}
impl AgentThreadEntry {
- fn to_markdown(&self, cx: &App) -> String {
+ pub fn to_markdown(&self, cx: &App) -> String {
match self {
Self::UserMessage(message) => message.to_markdown(cx),
Self::AssistantMessage(message) => message.to_markdown(cx),
@@ -119,6 +119,14 @@ impl AgentThreadEntry {
}
}
+ pub fn user_message(&self) -> Option<&UserMessage> {
+ if let AgentThreadEntry::UserMessage(message) = self {
+ Some(message)
+ } else {
+ None
+ }
+ }
+
pub fn diffs(&self) -> impl Iterator<Item = &Entity<Diff>> {
if let AgentThreadEntry::ToolCall(call) = self {
itertools::Either::Left(call.diffs())
@@ -217,7 +225,7 @@ impl ToolCall {
}
if let Some(status) = status {
- self.status = ToolCallStatus::Allowed { status };
+ self.status = status.into();
}
if let Some(title) = title {
@@ -338,30 +346,48 @@ impl ToolCall {
#[derive(Debug)]
pub enum ToolCallStatus {
+ /// The tool call hasn't started running yet, but we start showing it to
+ /// the user.
+ Pending,
+ /// The tool call is waiting for confirmation from the user.
WaitingForConfirmation {
options: Vec<acp::PermissionOption>,
respond_tx: oneshot::Sender<acp::PermissionOptionId>,
},
- Allowed {
- status: acp::ToolCallStatus,
- },
+ /// The tool call is currently running.
+ InProgress,
+ /// The tool call completed successfully.
+ Completed,
+ /// The tool call failed.
+ Failed,
+ /// The user rejected the tool call.
Rejected,
+ /// The user canceled generation so the tool call was canceled.
Canceled,
}
+impl From<acp::ToolCallStatus> for ToolCallStatus {
+ fn from(status: acp::ToolCallStatus) -> Self {
+ match status {
+ acp::ToolCallStatus::Pending => Self::Pending,
+ acp::ToolCallStatus::InProgress => Self::InProgress,
+ acp::ToolCallStatus::Completed => Self::Completed,
+ acp::ToolCallStatus::Failed => Self::Failed,
+ }
+ }
+}
+
impl Display for ToolCallStatus {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
+ ToolCallStatus::Pending => "Pending",
ToolCallStatus::WaitingForConfirmation { .. } => "Waiting for confirmation",
- ToolCallStatus::Allowed { status } => match status {
- acp::ToolCallStatus::Pending => "Pending",
- acp::ToolCallStatus::InProgress => "In Progress",
- acp::ToolCallStatus::Completed => "Completed",
- acp::ToolCallStatus::Failed => "Failed",
- },
+ ToolCallStatus::InProgress => "In Progress",
+ ToolCallStatus::Completed => "Completed",
+ ToolCallStatus::Failed => "Failed",
ToolCallStatus::Rejected => "Rejected",
ToolCallStatus::Canceled => "Canceled",
}
@@ -764,11 +790,7 @@ impl AcpThread {
AgentThreadEntry::UserMessage(_) => return false,
AgentThreadEntry::ToolCall(
call @ ToolCall {
- status:
- ToolCallStatus::Allowed {
- status:
- acp::ToolCallStatus::InProgress | acp::ToolCallStatus::Pending,
- },
+ status: ToolCallStatus::InProgress | ToolCallStatus::Pending,
..
},
) if call.diffs().next().is_some() => {
@@ -797,7 +819,7 @@ impl AcpThread {
&mut self,
update: acp::SessionUpdate,
cx: &mut Context<Self>,
- ) -> Result<()> {
+ ) -> Result<(), acp::Error> {
match update {
acp::SessionUpdate::UserMessageChunk { content } => {
self.push_user_content_block(None, content, cx);
@@ -809,7 +831,7 @@ impl AcpThread {
self.push_assistant_content_block(content, true, cx);
}
acp::SessionUpdate::ToolCall(tool_call) => {
- self.upsert_tool_call(tool_call, cx);
+ self.upsert_tool_call(tool_call, cx)?;
}
acp::SessionUpdate::ToolCallUpdate(tool_call_update) => {
self.update_tool_call(tool_call_update, cx)?;
@@ -945,32 +967,38 @@ impl AcpThread {
}
/// Updates a tool call if id matches an existing entry, otherwise inserts a new one.
- pub fn upsert_tool_call(&mut self, tool_call: acp::ToolCall, cx: &mut Context<Self>) {
- let status = ToolCallStatus::Allowed {
- status: tool_call.status,
- };
- self.upsert_tool_call_inner(tool_call, status, cx)
+ pub fn upsert_tool_call(
+ &mut self,
+ tool_call: acp::ToolCall,
+ cx: &mut Context<Self>,
+ ) -> Result<(), acp::Error> {
+ let status = tool_call.status.into();
+ self.upsert_tool_call_inner(tool_call.into(), status, cx)
}
+ /// Fails if id does not match an existing entry.
pub fn upsert_tool_call_inner(
&mut self,
- tool_call: acp::ToolCall,
+ tool_call_update: acp::ToolCallUpdate,
status: ToolCallStatus,
cx: &mut Context<Self>,
- ) {
+ ) -> Result<(), acp::Error> {
let language_registry = self.project.read(cx).languages().clone();
- let call = ToolCall::from_acp(tool_call, status, language_registry, cx);
- let id = call.id.clone();
+ let id = tool_call_update.id.clone();
- if let Some((ix, current_call)) = self.tool_call_mut(&call.id) {
- *current_call = call;
+ if let Some((ix, current_call)) = self.tool_call_mut(&id) {
+ current_call.update_fields(tool_call_update.fields, language_registry, cx);
+ current_call.status = status;
cx.emit(AcpThreadEvent::EntryUpdated(ix));
} else {
+ let call =
+ ToolCall::from_acp(tool_call_update.try_into()?, status, language_registry, cx);
self.push_entry(AgentThreadEntry::ToolCall(call), cx);
};
self.resolve_locations(id, cx);
+ Ok(())
}
fn tool_call_mut(&mut self, id: &acp::ToolCallId) -> Option<(usize, &mut ToolCall)> {
@@ -1039,10 +1067,10 @@ impl AcpThread {
pub fn request_tool_call_authorization(
&mut self,
- tool_call: acp::ToolCall,
+ tool_call: acp::ToolCallUpdate,
options: Vec<acp::PermissionOption>,
cx: &mut Context<Self>,
- ) -> oneshot::Receiver<acp::PermissionOptionId> {
+ ) -> Result<oneshot::Receiver<acp::PermissionOptionId>, acp::Error> {
let (tx, rx) = oneshot::channel();
let status = ToolCallStatus::WaitingForConfirmation {
@@ -1050,9 +1078,9 @@ impl AcpThread {
respond_tx: tx,
};
- self.upsert_tool_call_inner(tool_call, status, cx);
+ self.upsert_tool_call_inner(tool_call, status, cx)?;
cx.emit(AcpThreadEvent::ToolAuthorizationRequired);
- rx
+ Ok(rx)
}
pub fn authorize_tool_call(
@@ -1071,9 +1099,7 @@ impl AcpThread {
ToolCallStatus::Rejected
}
acp::PermissionOptionKind::AllowOnce | acp::PermissionOptionKind::AllowAlways => {
- ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::InProgress,
- }
+ ToolCallStatus::InProgress
}
};
@@ -1094,7 +1120,10 @@ impl AcpThread {
match &entry {
AgentThreadEntry::ToolCall(call) => match call.status {
ToolCallStatus::WaitingForConfirmation { .. } => return true,
- ToolCallStatus::Allowed { .. }
+ ToolCallStatus::Pending
+ | ToolCallStatus::InProgress
+ | ToolCallStatus::Completed
+ | ToolCallStatus::Failed
| ToolCallStatus::Rejected
| ToolCallStatus::Canceled => continue,
},
@@ -1253,19 +1282,19 @@ impl AcpThread {
Err(e)
}
result => {
- let cancelled = matches!(
+ let canceled = matches!(
result,
Ok(Ok(acp::PromptResponse {
- stop_reason: acp::StopReason::Cancelled
+ stop_reason: acp::StopReason::Canceled
}))
);
- // We only take the task if the current prompt wasn't cancelled.
+ // We only take the task if the current prompt wasn't canceled.
//
- // This prompt may have been cancelled because another one was sent
+ // This prompt may have been canceled because another one was sent
// while it was still generating. In these cases, dropping `send_task`
- // would cause the next generation to be cancelled.
- if !cancelled {
+ // would cause the next generation to be canceled.
+ if !canceled {
this.send_task.take();
}
@@ -1287,10 +1316,9 @@ impl AcpThread {
if let AgentThreadEntry::ToolCall(call) = entry {
let cancel = matches!(
call.status,
- ToolCallStatus::WaitingForConfirmation { .. }
- | ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::InProgress
- }
+ ToolCallStatus::Pending
+ | ToolCallStatus::WaitingForConfirmation { .. }
+ | ToolCallStatus::InProgress
);
if cancel {
@@ -1936,10 +1964,7 @@ mod tests {
assert!(matches!(
thread.entries[1],
AgentThreadEntry::ToolCall(ToolCall {
- status: ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::InProgress,
- ..
- },
+ status: ToolCallStatus::InProgress,
..
})
));
@@ -1978,10 +2003,7 @@ mod tests {
assert!(matches!(
thread.entries[1],
AgentThreadEntry::ToolCall(ToolCall {
- status: ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::Completed,
- ..
- },
+ status: ToolCallStatus::Completed,
..
})
));
@@ -302,12 +302,12 @@ mod test_support {
if let Some((tool_call, options)) = permission_request {
let permission = thread.update(cx, |thread, cx| {
thread.request_tool_call_authorization(
- tool_call.clone(),
+ tool_call.clone().into(),
options.clone(),
cx,
)
})?;
- permission.await?;
+ permission?.await?;
}
thread.update(cx, |thread, cx| {
thread.handle_session_update(update.clone(), cx).unwrap();
@@ -844,11 +844,17 @@ impl Thread {
.await
.unwrap_or(false);
- if !equal {
- this.update(cx, |this, cx| {
- this.insert_checkpoint(pending_checkpoint, cx)
- })?;
- }
+ this.update(cx, |this, cx| {
+ this.pending_checkpoint = if equal {
+ Some(pending_checkpoint)
+ } else {
+ this.insert_checkpoint(pending_checkpoint, cx);
+ Some(ThreadCheckpoint {
+ message_id: this.next_message_id,
+ git_checkpoint: final_checkpoint,
+ })
+ }
+ })?;
Ok(())
}
@@ -5331,7 +5337,7 @@ fn main() {{
}
#[gpui::test]
- async fn test_retry_cancelled_on_stop(cx: &mut TestAppContext) {
+ async fn test_retry_canceled_on_stop(cx: &mut TestAppContext) {
init_test_settings(cx);
let project = create_test_project(cx, json!({})).await;
@@ -5387,7 +5393,7 @@ fn main() {{
"Should have no pending completions after cancellation"
);
- // Verify the retry was cancelled by checking retry state
+ // Verify the retry was canceled by checking retry state
thread.read_with(cx, |thread, _| {
if let Some(retry_state) = &thread.retry_state {
panic!(
@@ -137,7 +137,7 @@ impl ToolUseState {
}
pub fn cancel_pending(&mut self) -> Vec<PendingToolUse> {
- let mut cancelled_tool_uses = Vec::new();
+ let mut canceled_tool_uses = Vec::new();
self.pending_tool_uses_by_id
.retain(|tool_use_id, tool_use| {
if matches!(tool_use.status, PendingToolUseStatus::Error { .. }) {
@@ -155,10 +155,10 @@ impl ToolUseState {
is_error: true,
},
);
- cancelled_tool_uses.push(tool_use.clone());
+ canceled_tool_uses.push(tool_use.clone());
false
});
- cancelled_tool_uses
+ canceled_tool_uses
}
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
@@ -521,10 +521,11 @@ impl NativeAgentConnection {
thread.request_tool_call_authorization(tool_call, options, cx)
})?;
cx.background_spawn(async move {
- if let Some(option) = recv
- .await
- .context("authorization sender was dropped")
- .log_err()
+ if let Some(recv) = recv.log_err()
+ && let Some(option) = recv
+ .await
+ .context("authorization sender was dropped")
+ .log_err()
{
response
.send(option)
@@ -537,7 +538,7 @@ impl NativeAgentConnection {
AgentResponseEvent::ToolCall(tool_call) => {
acp_thread.update(cx, |thread, cx| {
thread.upsert_tool_call(tool_call, cx)
- })?;
+ })??;
}
AgentResponseEvent::ToolCallUpdate(update) => {
acp_thread.update(cx, |thread, cx| {
@@ -16,6 +16,7 @@ use language_model::{
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse, MessageContent,
Role, StopReason, fake_provider::FakeLanguageModel,
};
+use pretty_assertions::assert_eq;
use project::Project;
use prompt_store::ProjectContext;
use reqwest_client::ReqwestClient;
@@ -129,6 +130,134 @@ async fn test_system_prompt(cx: &mut TestAppContext) {
);
}
+#[gpui::test]
+async fn test_prompt_caching(cx: &mut TestAppContext) {
+ let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
+ let fake_model = model.as_fake();
+
+ // Send initial user message and verify it's cached
+ thread.update(cx, |thread, cx| {
+ thread.send(UserMessageId::new(), ["Message 1"], cx)
+ });
+ cx.run_until_parked();
+
+ let completion = fake_model.pending_completions().pop().unwrap();
+ assert_eq!(
+ completion.messages[1..],
+ vec![LanguageModelRequestMessage {
+ role: Role::User,
+ content: vec!["Message 1".into()],
+ cache: true
+ }]
+ );
+ fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::Text(
+ "Response to Message 1".into(),
+ ));
+ fake_model.end_last_completion_stream();
+ cx.run_until_parked();
+
+ // Send another user message and verify only the latest is cached
+ thread.update(cx, |thread, cx| {
+ thread.send(UserMessageId::new(), ["Message 2"], cx)
+ });
+ cx.run_until_parked();
+
+ let completion = fake_model.pending_completions().pop().unwrap();
+ assert_eq!(
+ completion.messages[1..],
+ vec![
+ LanguageModelRequestMessage {
+ role: Role::User,
+ content: vec!["Message 1".into()],
+ cache: false
+ },
+ LanguageModelRequestMessage {
+ role: Role::Assistant,
+ content: vec!["Response to Message 1".into()],
+ cache: false
+ },
+ LanguageModelRequestMessage {
+ role: Role::User,
+ content: vec!["Message 2".into()],
+ cache: true
+ }
+ ]
+ );
+ fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::Text(
+ "Response to Message 2".into(),
+ ));
+ fake_model.end_last_completion_stream();
+ cx.run_until_parked();
+
+ // Simulate a tool call and verify that the latest tool result is cached
+ thread.update(cx, |thread, _| thread.add_tool(EchoTool));
+ thread.update(cx, |thread, cx| {
+ thread.send(UserMessageId::new(), ["Use the echo tool"], cx)
+ });
+ cx.run_until_parked();
+
+ let tool_use = LanguageModelToolUse {
+ id: "tool_1".into(),
+ name: EchoTool.name().into(),
+ raw_input: json!({"text": "test"}).to_string(),
+ input: json!({"text": "test"}),
+ is_input_complete: true,
+ };
+ fake_model
+ .send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(tool_use.clone()));
+ fake_model.end_last_completion_stream();
+ cx.run_until_parked();
+
+ let completion = fake_model.pending_completions().pop().unwrap();
+ let tool_result = LanguageModelToolResult {
+ tool_use_id: "tool_1".into(),
+ tool_name: EchoTool.name().into(),
+ is_error: false,
+ content: "test".into(),
+ output: Some("test".into()),
+ };
+ assert_eq!(
+ completion.messages[1..],
+ vec![
+ LanguageModelRequestMessage {
+ role: Role::User,
+ content: vec!["Message 1".into()],
+ cache: false
+ },
+ LanguageModelRequestMessage {
+ role: Role::Assistant,
+ content: vec!["Response to Message 1".into()],
+ cache: false
+ },
+ LanguageModelRequestMessage {
+ role: Role::User,
+ content: vec!["Message 2".into()],
+ cache: false
+ },
+ LanguageModelRequestMessage {
+ role: Role::Assistant,
+ content: vec!["Response to Message 2".into()],
+ cache: false
+ },
+ LanguageModelRequestMessage {
+ role: Role::User,
+ content: vec!["Use the echo tool".into()],
+ cache: false
+ },
+ LanguageModelRequestMessage {
+ role: Role::Assistant,
+ content: vec![MessageContent::ToolUse(tool_use)],
+ cache: false
+ },
+ LanguageModelRequestMessage {
+ role: Role::User,
+ content: vec![MessageContent::ToolResult(tool_result)],
+ cache: true
+ }
+ ]
+ );
+}
+
#[gpui::test]
#[ignore = "can't run on CI yet"]
async fn test_basic_tool_calls(cx: &mut TestAppContext) {
@@ -440,7 +569,7 @@ async fn test_resume_after_tool_use_limit(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::ToolResult(tool_result.clone())],
- cache: false
+ cache: true
},
]
);
@@ -481,7 +610,7 @@ async fn test_resume_after_tool_use_limit(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["Continue where you left off".into()],
- cache: false
+ cache: true
}
]
);
@@ -574,7 +703,7 @@ async fn test_send_after_tool_use_limit(cx: &mut TestAppContext) {
LanguageModelRequestMessage {
role: Role::User,
content: vec!["ghi".into()],
- cache: false
+ cache: true
}
]
);
@@ -812,7 +941,15 @@ async fn test_cancellation(cx: &mut TestAppContext) {
// Cancel the current send and ensure that the event stream is closed, even
// if one of the tools is still running.
thread.update(cx, |thread, _cx| thread.cancel());
- events.collect::<Vec<_>>().await;
+ let events = events.collect::<Vec<_>>().await;
+ let last_event = events.last();
+ assert!(
+ matches!(
+ last_event,
+ Some(Ok(AgentResponseEvent::Stop(acp::StopReason::Canceled)))
+ ),
+ "unexpected event {last_event:?}"
+ );
// Ensure we can still send a new message after cancellation.
let events = thread
@@ -836,6 +973,62 @@ async fn test_cancellation(cx: &mut TestAppContext) {
assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
}
+#[gpui::test]
+async fn test_in_progress_send_canceled_by_next_send(cx: &mut TestAppContext) {
+ let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
+ let fake_model = model.as_fake();
+
+ let events_1 = thread.update(cx, |thread, cx| {
+ thread.send(UserMessageId::new(), ["Hello 1"], cx)
+ });
+ cx.run_until_parked();
+ fake_model.send_last_completion_stream_text_chunk("Hey 1!");
+ cx.run_until_parked();
+
+ let events_2 = thread.update(cx, |thread, cx| {
+ thread.send(UserMessageId::new(), ["Hello 2"], cx)
+ });
+ cx.run_until_parked();
+ fake_model.send_last_completion_stream_text_chunk("Hey 2!");
+ fake_model
+ .send_last_completion_stream_event(LanguageModelCompletionEvent::Stop(StopReason::EndTurn));
+ fake_model.end_last_completion_stream();
+
+ let events_1 = events_1.collect::<Vec<_>>().await;
+ assert_eq!(stop_events(events_1), vec![acp::StopReason::Canceled]);
+ let events_2 = events_2.collect::<Vec<_>>().await;
+ assert_eq!(stop_events(events_2), vec![acp::StopReason::EndTurn]);
+}
+
+#[gpui::test]
+async fn test_subsequent_successful_sends_dont_cancel(cx: &mut TestAppContext) {
+ let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
+ let fake_model = model.as_fake();
+
+ let events_1 = thread.update(cx, |thread, cx| {
+ thread.send(UserMessageId::new(), ["Hello 1"], cx)
+ });
+ cx.run_until_parked();
+ fake_model.send_last_completion_stream_text_chunk("Hey 1!");
+ fake_model
+ .send_last_completion_stream_event(LanguageModelCompletionEvent::Stop(StopReason::EndTurn));
+ fake_model.end_last_completion_stream();
+ let events_1 = events_1.collect::<Vec<_>>().await;
+
+ let events_2 = thread.update(cx, |thread, cx| {
+ thread.send(UserMessageId::new(), ["Hello 2"], cx)
+ });
+ cx.run_until_parked();
+ fake_model.send_last_completion_stream_text_chunk("Hey 2!");
+ fake_model
+ .send_last_completion_stream_event(LanguageModelCompletionEvent::Stop(StopReason::EndTurn));
+ fake_model.end_last_completion_stream();
+ let events_2 = events_2.collect::<Vec<_>>().await;
+
+ assert_eq!(stop_events(events_1), vec![acp::StopReason::EndTurn]);
+ assert_eq!(stop_events(events_2), vec![acp::StopReason::EndTurn]);
+}
+
#[gpui::test]
async fn test_refusal(cx: &mut TestAppContext) {
let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
@@ -28,6 +28,48 @@ use smol::stream::StreamExt;
use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc, sync::Arc};
use std::{fmt::Write, ops::Range};
use util::{ResultExt, markdown::MarkdownCodeBlock};
+use uuid::Uuid;
+
+#[derive(
+ Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema,
+)]
+pub struct ThreadId(Arc<str>);
+
+impl ThreadId {
+ pub fn new() -> Self {
+ Self(Uuid::new_v4().to_string().into())
+ }
+}
+
+impl std::fmt::Display for ThreadId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl From<&str> for ThreadId {
+ fn from(value: &str) -> Self {
+ Self(value.into())
+ }
+}
+
+/// The ID of the user prompt that initiated a request.
+///
+/// This equates to the user physically submitting a message to the model (e.g., by pressing the Enter key).
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
+pub struct PromptId(Arc<str>);
+
+impl PromptId {
+ pub fn new() -> Self {
+ Self(Uuid::new_v4().to_string().into())
+ }
+}
+
+impl std::fmt::Display for PromptId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Message {
@@ -406,18 +448,20 @@ pub enum AgentResponseEvent {
#[derive(Debug)]
pub struct ToolCallAuthorization {
- pub tool_call: acp::ToolCall,
+ pub tool_call: acp::ToolCallUpdate,
pub options: Vec<acp::PermissionOption>,
pub response: oneshot::Sender<acp::PermissionOptionId>,
}
pub struct Thread {
+ id: ThreadId,
+ prompt_id: PromptId,
messages: Vec<Message>,
completion_mode: CompletionMode,
/// Holds the task that handles agent interaction until the end of the turn.
/// Survives across multiple requests as the model performs tool calls and
/// we run tools, report their results.
- running_turn: Option<Task<()>>,
+ running_turn: Option<RunningTurn>,
pending_message: Option<AgentMessage>,
tools: BTreeMap<SharedString, Arc<dyn AnyAgentTool>>,
tool_use_limit_reached: bool,
@@ -442,6 +486,8 @@ impl Thread {
) -> Self {
let profile_id = AgentSettings::get_global(cx).default_profile.clone();
Self {
+ id: ThreadId::new(),
+ prompt_id: PromptId::new(),
messages: Vec::new(),
completion_mode: CompletionMode::Normal,
running_turn: None,
@@ -499,13 +545,18 @@ impl Thread {
self.tools.remove(name).is_some()
}
+ pub fn profile(&self) -> &AgentProfileId {
+ &self.profile_id
+ }
+
pub fn set_profile(&mut self, profile_id: AgentProfileId) {
self.profile_id = profile_id;
}
pub fn cancel(&mut self) {
- // TODO: do we need to emit a stop::cancel for ACP?
- self.running_turn.take();
+ if let Some(running_turn) = self.running_turn.take() {
+ running_turn.cancel();
+ }
self.flush_pending_message();
}
@@ -549,6 +600,7 @@ impl Thread {
T: Into<UserMessageContent>,
{
log::info!("Thread::send called with model: {:?}", self.model.name());
+ self.advance_prompt_id();
let content = content.into_iter().map(Into::into).collect::<Vec<_>>();
log::debug!("Thread::send content: {:?}", content);
@@ -565,108 +617,118 @@ impl Thread {
&mut self,
cx: &mut Context<Self>,
) -> mpsc::UnboundedReceiver<Result<AgentResponseEvent>> {
+ self.cancel();
+
let model = self.model.clone();
let (events_tx, events_rx) = mpsc::unbounded::<Result<AgentResponseEvent>>();
let event_stream = AgentResponseEventStream(events_tx);
let message_ix = self.messages.len().saturating_sub(1);
self.tool_use_limit_reached = false;
- self.running_turn = Some(cx.spawn(async move |this, cx| {
- log::info!("Starting agent turn execution");
- let turn_result: Result<()> = async {
- let mut completion_intent = CompletionIntent::UserPrompt;
- loop {
- log::debug!(
- "Building completion request with intent: {:?}",
- completion_intent
- );
- let request = this.update(cx, |this, cx| {
- this.build_completion_request(completion_intent, cx)
- })?;
-
- log::info!("Calling model.stream_completion");
- let mut events = model.stream_completion(request, cx).await?;
- log::debug!("Stream completion started successfully");
-
- let mut tool_use_limit_reached = false;
- let mut tool_uses = FuturesUnordered::new();
- while let Some(event) = events.next().await {
- match event? {
- LanguageModelCompletionEvent::StatusUpdate(
- CompletionRequestStatus::ToolUseLimitReached,
- ) => {
- tool_use_limit_reached = true;
- }
- LanguageModelCompletionEvent::Stop(reason) => {
- event_stream.send_stop(reason);
- if reason == StopReason::Refusal {
- this.update(cx, |this, _cx| {
- this.flush_pending_message();
- this.messages.truncate(message_ix);
- })?;
- return Ok(());
+ self.running_turn = Some(RunningTurn {
+ event_stream: event_stream.clone(),
+ _task: cx.spawn(async move |this, cx| {
+ log::info!("Starting agent turn execution");
+ let turn_result: Result<()> = async {
+ let mut completion_intent = CompletionIntent::UserPrompt;
+ loop {
+ log::debug!(
+ "Building completion request with intent: {:?}",
+ completion_intent
+ );
+ let request = this.update(cx, |this, cx| {
+ this.build_completion_request(completion_intent, cx)
+ })?;
+
+ log::info!("Calling model.stream_completion");
+ let mut events = model.stream_completion(request, cx).await?;
+ log::debug!("Stream completion started successfully");
+
+ let mut tool_use_limit_reached = false;
+ let mut tool_uses = FuturesUnordered::new();
+ while let Some(event) = events.next().await {
+ match event? {
+ LanguageModelCompletionEvent::StatusUpdate(
+ CompletionRequestStatus::ToolUseLimitReached,
+ ) => {
+ tool_use_limit_reached = true;
+ }
+ LanguageModelCompletionEvent::Stop(reason) => {
+ event_stream.send_stop(reason);
+ if reason == StopReason::Refusal {
+ this.update(cx, |this, _cx| {
+ this.flush_pending_message();
+ this.messages.truncate(message_ix);
+ })?;
+ return Ok(());
+ }
+ }
+ event => {
+ log::trace!("Received completion event: {:?}", event);
+ this.update(cx, |this, cx| {
+ tool_uses.extend(this.handle_streamed_completion_event(
+ event,
+ &event_stream,
+ cx,
+ ));
+ })
+ .ok();
}
- }
- event => {
- log::trace!("Received completion event: {:?}", event);
- this.update(cx, |this, cx| {
- tool_uses.extend(this.handle_streamed_completion_event(
- event,
- &event_stream,
- cx,
- ));
- })
- .ok();
}
}
- }
- let used_tools = tool_uses.is_empty();
- while let Some(tool_result) = tool_uses.next().await {
- log::info!("Tool finished {:?}", tool_result);
-
- event_stream.update_tool_call_fields(
- &tool_result.tool_use_id,
- acp::ToolCallUpdateFields {
- status: Some(if tool_result.is_error {
- acp::ToolCallStatus::Failed
- } else {
- acp::ToolCallStatus::Completed
- }),
- raw_output: tool_result.output.clone(),
- ..Default::default()
- },
- );
- this.update(cx, |this, _cx| {
- this.pending_message()
- .tool_results
- .insert(tool_result.tool_use_id.clone(), tool_result);
- })
- .ok();
- }
+ let used_tools = tool_uses.is_empty();
+ while let Some(tool_result) = tool_uses.next().await {
+ log::info!("Tool finished {:?}", tool_result);
+
+ event_stream.update_tool_call_fields(
+ &tool_result.tool_use_id,
+ acp::ToolCallUpdateFields {
+ status: Some(if tool_result.is_error {
+ acp::ToolCallStatus::Failed
+ } else {
+ acp::ToolCallStatus::Completed
+ }),
+ raw_output: tool_result.output.clone(),
+ ..Default::default()
+ },
+ );
+ this.update(cx, |this, _cx| {
+ this.pending_message()
+ .tool_results
+ .insert(tool_result.tool_use_id.clone(), tool_result);
+ })
+ .ok();
+ }
- if tool_use_limit_reached {
- log::info!("Tool use limit reached, completing turn");
- this.update(cx, |this, _cx| this.tool_use_limit_reached = true)?;
- return Err(language_model::ToolUseLimitReachedError.into());
- } else if used_tools {
- log::info!("No tool uses found, completing turn");
- return Ok(());
- } else {
- this.update(cx, |this, _| this.flush_pending_message())?;
- completion_intent = CompletionIntent::ToolResults;
+ if tool_use_limit_reached {
+ log::info!("Tool use limit reached, completing turn");
+ this.update(cx, |this, _cx| this.tool_use_limit_reached = true)?;
+ return Err(language_model::ToolUseLimitReachedError.into());
+ } else if used_tools {
+ log::info!("No tool uses found, completing turn");
+ return Ok(());
+ } else {
+ this.update(cx, |this, _| this.flush_pending_message())?;
+ completion_intent = CompletionIntent::ToolResults;
+ }
}
}
- }
- .await;
+ .await;
- this.update(cx, |this, _| this.flush_pending_message()).ok();
- if let Err(error) = turn_result {
- log::error!("Turn execution failed: {:?}", error);
- event_stream.send_error(error);
- } else {
- log::info!("Turn execution completed successfully");
- }
- }));
+ if let Err(error) = turn_result {
+ log::error!("Turn execution failed: {:?}", error);
+ event_stream.send_error(error);
+ } else {
+ log::info!("Turn execution completed successfully");
+ }
+
+ this.update(cx, |this, _| {
+ this.flush_pending_message();
+ this.running_turn.take();
+ })
+ .ok();
+ }),
+ });
events_rx
}
@@ -850,7 +912,7 @@ impl Thread {
let fs = self.project.read(cx).fs().clone();
let tool_event_stream =
- ToolCallEventStream::new(&tool_use, tool.kind(), event_stream.clone(), Some(fs));
+ ToolCallEventStream::new(tool_use.id.clone(), event_stream.clone(), Some(fs));
tool_event_stream.update_fields(acp::ToolCallUpdateFields {
status: Some(acp::ToolCallStatus::InProgress),
..Default::default()
@@ -972,15 +1034,15 @@ impl Thread {
log::info!("Request includes {} tools", tools.len());
let request = LanguageModelRequest {
- thread_id: None,
- prompt_id: None,
+ thread_id: Some(self.id.to_string()),
+ prompt_id: Some(self.prompt_id.to_string()),
intent: Some(completion_intent),
mode: Some(self.completion_mode.into()),
messages,
tools,
tool_choice: None,
stop: Vec::new(),
- temperature: None,
+ temperature: AgentSettings::temperature_for_model(self.model(), cx),
thinking_allowed: true,
};
@@ -1041,6 +1103,14 @@ impl Thread {
messages.extend(message.to_request());
}
+ if let Some(last_user_message) = messages
+ .iter_mut()
+ .rev()
+ .find(|message| message.role == Role::User)
+ {
+ last_user_message.cache = true;
+ }
+
messages
}
@@ -1060,6 +1130,27 @@ impl Thread {
markdown
}
+
+ fn advance_prompt_id(&mut self) {
+ self.prompt_id = PromptId::new();
+ }
+}
+
+struct RunningTurn {
+ /// Holds the task that handles agent interaction until the end of the turn.
+ /// Survives across multiple requests as the model performs tool calls and
+ /// we run tools, report their results.
+ _task: Task<()>,
+ /// The current event stream for the running turn. Used to report a final
+ /// cancellation event if we cancel the turn.
+ event_stream: AgentResponseEventStream,
+}
+
+impl RunningTurn {
+ fn cancel(self) {
+ log::debug!("Cancelling in progress turn");
+ self.event_stream.send_canceled();
+ }
}
pub trait AgentTool
@@ -1273,6 +1364,12 @@ impl AgentResponseEventStream {
}
}
+ fn send_canceled(&self) {
+ self.0
+ .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::Canceled)))
+ .ok();
+ }
+
fn send_error(&self, error: impl Into<anyhow::Error>) {
self.0.unbounded_send(Err(error.into())).ok();
}
@@ -1281,8 +1378,6 @@ impl AgentResponseEventStream {
#[derive(Clone)]
pub struct ToolCallEventStream {
tool_use_id: LanguageModelToolUseId,
- kind: acp::ToolKind,
- input: serde_json::Value,
stream: AgentResponseEventStream,
fs: Option<Arc<dyn Fs>>,
}
@@ -1292,32 +1387,19 @@ impl ToolCallEventStream {
pub fn test() -> (Self, ToolCallEventStreamReceiver) {
let (events_tx, events_rx) = mpsc::unbounded::<Result<AgentResponseEvent>>();
- let stream = ToolCallEventStream::new(
- &LanguageModelToolUse {
- id: "test_id".into(),
- name: "test_tool".into(),
- raw_input: String::new(),
- input: serde_json::Value::Null,
- is_input_complete: true,
- },
- acp::ToolKind::Other,
- AgentResponseEventStream(events_tx),
- None,
- );
+ let stream =
+ ToolCallEventStream::new("test_id".into(), AgentResponseEventStream(events_tx), None);
(stream, ToolCallEventStreamReceiver(events_rx))
}
fn new(
- tool_use: &LanguageModelToolUse,
- kind: acp::ToolKind,
+ tool_use_id: LanguageModelToolUseId,
stream: AgentResponseEventStream,
fs: Option<Arc<dyn Fs>>,
) -> Self {
Self {
- tool_use_id: tool_use.id.clone(),
- kind,
- input: tool_use.input.clone(),
+ tool_use_id,
stream,
fs,
}
@@ -1364,12 +1446,13 @@ impl ToolCallEventStream {
.0
.unbounded_send(Ok(AgentResponseEvent::ToolCallAuthorization(
ToolCallAuthorization {
- tool_call: AgentResponseEventStream::initial_tool_call(
- &self.tool_use_id,
- title.into(),
- self.kind.clone(),
- self.input.clone(),
- ),
+ tool_call: acp::ToolCallUpdate {
+ id: acp::ToolCallId(self.tool_use_id.to_string().into()),
+ fields: acp::ToolCallUpdateFields {
+ title: Some(title.into()),
+ ..Default::default()
+ },
+ },
options: vec![
acp::PermissionOption {
id: acp::PermissionOptionId("always_allow".into()),
@@ -1018,7 +1018,10 @@ mod tests {
});
let event = stream_rx.expect_authorization().await;
- assert_eq!(event.tool_call.title, "test 1 (local settings)");
+ assert_eq!(
+ event.tool_call.fields.title,
+ Some("test 1 (local settings)".into())
+ );
// Test 2: Path outside project should require confirmation
let (stream_tx, mut stream_rx) = ToolCallEventStream::test();
@@ -1035,7 +1038,7 @@ mod tests {
});
let event = stream_rx.expect_authorization().await;
- assert_eq!(event.tool_call.title, "test 2");
+ assert_eq!(event.tool_call.fields.title, Some("test 2".into()));
// Test 3: Relative path without .zed should not require confirmation
let (stream_tx, mut stream_rx) = ToolCallEventStream::test();
@@ -1068,7 +1071,10 @@ mod tests {
)
});
let event = stream_rx.expect_authorization().await;
- assert_eq!(event.tool_call.title, "test 4 (local settings)");
+ assert_eq!(
+ event.tool_call.fields.title,
+ Some("test 4 (local settings)".into())
+ );
// Test 5: When always_allow_tool_actions is enabled, no confirmation needed
cx.update(|cx| {
@@ -135,9 +135,9 @@ impl acp_old::Client for OldAcpClientDelegate {
let response = cx
.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
- thread.request_tool_call_authorization(tool_call, acp_options, cx)
+ thread.request_tool_call_authorization(tool_call.into(), acp_options, cx)
})
- })?
+ })??
.context("Failed to update thread")?
.await;
@@ -168,7 +168,7 @@ impl acp_old::Client for OldAcpClientDelegate {
cx,
)
})
- })?
+ })??
.context("Failed to update thread")?;
Ok(acp_old::PushToolCallResponse {
@@ -1,7 +1,9 @@
use agent_client_protocol::{self as acp, Agent as _};
use anyhow::anyhow;
use collections::HashMap;
+use futures::AsyncBufReadExt as _;
use futures::channel::oneshot;
+use futures::io::BufReader;
use project::Project;
use std::path::Path;
use std::rc::Rc;
@@ -40,12 +42,13 @@ impl AcpConnection {
.current_dir(root_dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
- .stderr(std::process::Stdio::inherit())
+ .stderr(std::process::Stdio::piped())
.kill_on_drop(true)
.spawn()?;
- let stdout = child.stdout.take().expect("Failed to take stdout");
- let stdin = child.stdin.take().expect("Failed to take stdin");
+ let stdout = child.stdout.take().context("Failed to take stdout")?;
+ let stdin = child.stdin.take().context("Failed to take stdin")?;
+ let stderr = child.stderr.take().context("Failed to take stderr")?;
log::trace!("Spawned (pid: {})", child.id());
let sessions = Rc::new(RefCell::new(HashMap::default()));
@@ -63,6 +66,18 @@ impl AcpConnection {
let io_task = cx.background_spawn(io_task);
+ cx.background_spawn(async move {
+ let mut stderr = BufReader::new(stderr);
+ let mut line = String::new();
+ while let Ok(n) = stderr.read_line(&mut line).await
+ && n > 0
+ {
+ log::warn!("agent stderr: {}", &line);
+ line.clear();
+ }
+ })
+ .detach();
+
cx.spawn({
let sessions = sessions.clone();
async move |cx| {
@@ -218,11 +233,11 @@ impl acp::Client for ClientDelegate {
thread.request_tool_call_authorization(arguments.tool_call, arguments.options, cx)
})?;
- let result = rx.await;
+ let result = rx?.await;
let outcome = match result {
Ok(option) => acp::RequestPermissionOutcome::Selected { option_id: option },
- Err(oneshot::Canceled) => acp::RequestPermissionOutcome::Cancelled,
+ Err(oneshot::Canceled) => acp::RequestPermissionOutcome::Canceled,
};
Ok(acp::RequestPermissionResponse { outcome })
@@ -14,7 +14,7 @@ use std::rc::Rc;
use uuid::Uuid;
use agent_client_protocol as acp;
-use anyhow::{Result, anyhow};
+use anyhow::{Context as _, Result, anyhow};
use futures::channel::oneshot;
use futures::{AsyncBufReadExt, AsyncWriteExt};
use futures::{
@@ -130,12 +130,25 @@ impl AgentConnection for ClaudeAgentConnection {
&cwd,
)?;
- let stdin = child.stdin.take().unwrap();
- let stdout = child.stdout.take().unwrap();
+ let stdout = child.stdout.take().context("Failed to take stdout")?;
+ let stdin = child.stdin.take().context("Failed to take stdin")?;
+ let stderr = child.stderr.take().context("Failed to take stderr")?;
let pid = child.id();
log::trace!("Spawned (pid: {})", pid);
+ cx.background_spawn(async move {
+ let mut stderr = BufReader::new(stderr);
+ let mut line = String::new();
+ while let Ok(n) = stderr.read_line(&mut line).await
+ && n > 0
+ {
+ log::warn!("agent stderr: {}", &line);
+ line.clear();
+ }
+ })
+ .detach();
+
cx.background_spawn(async move {
let mut outgoing_rx = Some(outgoing_rx);
@@ -272,7 +285,7 @@ impl AgentConnection for ClaudeAgentConnection {
let turn_state = session.turn_state.take();
let TurnState::InProgress { end_tx } = turn_state else {
- // Already cancelled or idle, put it back
+ // Already canceled or idle, put it back
session.turn_state.replace(turn_state);
return;
};
@@ -345,7 +358,7 @@ fn spawn_claude(
.current_dir(root_dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
- .stderr(std::process::Stdio::inherit())
+ .stderr(std::process::Stdio::piped())
.kill_on_drop(true)
.spawn()?;
@@ -376,7 +389,7 @@ enum TurnState {
}
impl TurnState {
- fn is_cancelled(&self) -> bool {
+ fn is_canceled(&self) -> bool {
matches!(self, TurnState::CancelConfirmed { .. })
}
@@ -426,7 +439,7 @@ impl ClaudeAgentSession {
for chunk in message.content.chunks() {
match chunk {
ContentChunk::Text { text } | ContentChunk::UntaggedText(text) => {
- if !turn_state.borrow().is_cancelled() {
+ if !turn_state.borrow().is_canceled() {
thread
.update(cx, |thread, cx| {
thread.push_user_content_block(None, text.into(), cx)
@@ -445,8 +458,8 @@ impl ClaudeAgentSession {
acp::ToolCallUpdate {
id: acp::ToolCallId(tool_use_id.into()),
fields: acp::ToolCallUpdateFields {
- status: if turn_state.borrow().is_cancelled() {
- // Do not set to completed if turn was cancelled
+ status: if turn_state.borrow().is_canceled() {
+ // Do not set to completed if turn was canceled
None
} else {
Some(acp::ToolCallStatus::Completed)
@@ -547,8 +560,9 @@ impl ClaudeAgentSession {
thread.upsert_tool_call(
claude_tool.as_acp(acp::ToolCallId(id.into())),
cx,
- );
+ )?;
}
+ anyhow::Ok(())
})
.log_err();
}
@@ -578,14 +592,13 @@ impl ClaudeAgentSession {
..
} => {
let turn_state = turn_state.take();
- let was_cancelled = turn_state.is_cancelled();
+ let was_canceled = turn_state.is_canceled();
let Some(end_turn_tx) = turn_state.end_tx() else {
debug_panic!("Received `SdkMessage::Result` but there wasn't an active turn");
return;
};
- if is_error || (!was_cancelled && subtype == ResultErrorType::ErrorDuringExecution)
- {
+ if is_error || (!was_canceled && subtype == ResultErrorType::ErrorDuringExecution) {
end_turn_tx
.send(Err(anyhow!(
"Error: {}",
@@ -596,7 +609,7 @@ impl ClaudeAgentSession {
let stop_reason = match subtype {
ResultErrorType::Success => acp::StopReason::EndTurn,
ResultErrorType::ErrorMaxTurns => acp::StopReason::MaxTurnRequests,
- ResultErrorType::ErrorDuringExecution => acp::StopReason::Cancelled,
+ ResultErrorType::ErrorDuringExecution => acp::StopReason::Canceled,
};
end_turn_tx
.send(Ok(acp::PromptResponse { stop_reason }))
@@ -154,7 +154,7 @@ impl McpServerTool for PermissionTool {
let chosen_option = thread
.update(cx, |thread, cx| {
thread.request_tool_call_authorization(
- claude_tool.as_acp(tool_call_id),
+ claude_tool.as_acp(tool_call_id).into(),
vec![
acp::PermissionOption {
id: allow_option_id.clone(),
@@ -169,7 +169,7 @@ impl McpServerTool for PermissionTool {
],
cx,
)
- })?
+ })??
.await?;
let response = if chosen_option == allow_option_id {
@@ -134,7 +134,9 @@ pub async fn test_tool_call(server: impl AgentServer + 'static, cx: &mut TestApp
matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
- status: ToolCallStatus::Allowed { .. },
+ status: ToolCallStatus::Pending
+ | ToolCallStatus::InProgress
+ | ToolCallStatus::Completed,
..
})
)
@@ -212,7 +214,9 @@ pub async fn test_tool_call_with_permission(
assert!(thread.entries().iter().any(|entry| matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
- status: ToolCallStatus::Allowed { .. },
+ status: ToolCallStatus::Pending
+ | ToolCallStatus::InProgress
+ | ToolCallStatus::Completed,
..
})
)));
@@ -223,7 +227,9 @@ pub async fn test_tool_call_with_permission(
thread.read_with(cx, |thread, cx| {
let AgentThreadEntry::ToolCall(ToolCall {
content,
- status: ToolCallStatus::Allowed { .. },
+ status: ToolCallStatus::Pending
+ | ToolCallStatus::InProgress
+ | ToolCallStatus::Completed,
..
}) = thread
.entries()
@@ -309,7 +309,7 @@ pub struct AgentSettingsContent {
///
/// Default: true
expand_terminal_card: Option<bool>,
- /// Whether to always use cmd-enter (or ctrl-enter on Linux) to send messages in the agent panel.
+ /// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel.
///
/// Default: false
use_modifier_to_send: Option<bool>,
@@ -50,7 +50,6 @@ fuzzy.workspace = true
gpui.workspace = true
html_to_markdown.workspace = true
http_client.workspace = true
-indexed_docs.workspace = true
indoc.workspace = true
inventory.workspace = true
itertools.workspace = true
@@ -1,38 +1,26 @@
-use std::ffi::OsStr;
use std::ops::Range;
-use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
-use acp_thread::{MentionUri, selection_name};
-use anyhow::{Context as _, Result, anyhow};
-use collections::{HashMap, HashSet};
-use editor::display_map::CreaseId;
-use editor::{CompletionProvider, Editor, ExcerptId, ToOffset as _};
-use futures::future::{Shared, try_join_all};
-use futures::{FutureExt, TryFutureExt};
+use acp_thread::MentionUri;
+use anyhow::Result;
+use editor::{CompletionProvider, Editor, ExcerptId};
use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{App, Entity, ImageFormat, Img, Task, WeakEntity};
-use http_client::HttpClientWithUrl;
-use itertools::Itertools as _;
+use gpui::{App, Entity, Task, WeakEntity};
use language::{Buffer, CodeLabel, HighlightId};
-use language_model::LanguageModelImage;
use lsp::CompletionContext;
-use parking_lot::Mutex;
use project::{
Completion, CompletionIntent, CompletionResponse, Project, ProjectPath, Symbol, WorktreeId,
};
use prompt_store::PromptStore;
use rope::Point;
-use text::{Anchor, OffsetRangeExt as _, ToPoint as _};
+use text::{Anchor, ToPoint as _};
use ui::prelude::*;
-use url::Url;
use workspace::Workspace;
-use workspace::notifications::NotifyResultExt;
use agent::thread_store::{TextThreadStore, ThreadStore};
-use crate::context_picker::fetch_context_picker::fetch_url_content;
+use crate::acp::message_editor::MessageEditor;
use crate::context_picker::file_context_picker::{FileMatch, search_files};
use crate::context_picker::rules_context_picker::{RulesContextEntry, search_rules};
use crate::context_picker::symbol_context_picker::SymbolMatch;
@@ -45,237 +33,6 @@ use crate::context_picker::{
available_context_picker_entries, recent_context_picker_entries, selection_ranges,
};
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct MentionImage {
- pub abs_path: Option<Arc<Path>>,
- pub data: SharedString,
- pub format: ImageFormat,
-}
-
-#[derive(Default)]
-pub struct MentionSet {
- uri_by_crease_id: HashMap<CreaseId, MentionUri>,
- fetch_results: HashMap<Url, Shared<Task<Result<String, String>>>>,
- images: HashMap<CreaseId, Shared<Task<Result<MentionImage, String>>>>,
-}
-
-impl MentionSet {
- pub fn insert_uri(&mut self, crease_id: CreaseId, uri: MentionUri) {
- self.uri_by_crease_id.insert(crease_id, uri);
- }
-
- pub fn add_fetch_result(&mut self, url: Url, content: Shared<Task<Result<String, String>>>) {
- self.fetch_results.insert(url, content);
- }
-
- pub fn insert_image(
- &mut self,
- crease_id: CreaseId,
- task: Shared<Task<Result<MentionImage, String>>>,
- ) {
- self.images.insert(crease_id, task);
- }
-
- pub fn drain(&mut self) -> impl Iterator<Item = CreaseId> {
- self.fetch_results.clear();
- self.uri_by_crease_id
- .drain()
- .map(|(id, _)| id)
- .chain(self.images.drain().map(|(id, _)| id))
- }
-
- pub fn clear(&mut self) {
- self.fetch_results.clear();
- self.uri_by_crease_id.clear();
- }
-
- pub fn contents(
- &self,
- project: Entity<Project>,
- thread_store: Entity<ThreadStore>,
- text_thread_store: Entity<TextThreadStore>,
- window: &mut Window,
- cx: &mut App,
- ) -> Task<Result<HashMap<CreaseId, Mention>>> {
- let mut contents = self
- .uri_by_crease_id
- .iter()
- .map(|(&crease_id, uri)| {
- match uri {
- MentionUri::File { abs_path, .. } => {
- // TODO directories
- let uri = uri.clone();
- let abs_path = abs_path.to_path_buf();
- let extension = abs_path.extension().and_then(OsStr::to_str).unwrap_or("");
-
- if Img::extensions().contains(&extension) && !extension.contains("svg") {
- let open_image_task = project.update(cx, |project, cx| {
- let path = project
- .find_project_path(&abs_path, cx)
- .context("Failed to find project path")?;
- anyhow::Ok(project.open_image(path, cx))
- });
-
- cx.spawn(async move |cx| {
- let image_item = open_image_task?.await?;
- let (data, format) = image_item.update(cx, |image_item, cx| {
- let format = image_item.image.format;
- (
- LanguageModelImage::from_image(
- image_item.image.clone(),
- cx,
- ),
- format,
- )
- })?;
- let data = cx.spawn(async move |_| {
- if let Some(data) = data.await {
- Ok(data.source)
- } else {
- anyhow::bail!("Failed to convert image")
- }
- });
-
- anyhow::Ok((
- crease_id,
- Mention::Image(MentionImage {
- abs_path: Some(abs_path.as_path().into()),
- data: data.await?,
- format,
- }),
- ))
- })
- } else {
- let buffer_task = project.update(cx, |project, cx| {
- let path = project
- .find_project_path(abs_path, cx)
- .context("Failed to find project path")?;
- anyhow::Ok(project.open_buffer(path, cx))
- });
- cx.spawn(async move |cx| {
- let buffer = buffer_task?.await?;
- let content = buffer.read_with(cx, |buffer, _cx| buffer.text())?;
-
- anyhow::Ok((crease_id, Mention::Text { uri, content }))
- })
- }
- }
- MentionUri::Symbol {
- path, line_range, ..
- }
- | MentionUri::Selection {
- path, line_range, ..
- } => {
- let uri = uri.clone();
- let path_buf = path.clone();
- let line_range = line_range.clone();
-
- let buffer_task = project.update(cx, |project, cx| {
- let path = project
- .find_project_path(&path_buf, cx)
- .context("Failed to find project path")?;
- anyhow::Ok(project.open_buffer(path, cx))
- });
-
- cx.spawn(async move |cx| {
- let buffer = buffer_task?.await?;
- let content = buffer.read_with(cx, |buffer, _cx| {
- buffer
- .text_for_range(
- Point::new(line_range.start, 0)
- ..Point::new(
- line_range.end,
- buffer.line_len(line_range.end),
- ),
- )
- .collect()
- })?;
-
- anyhow::Ok((crease_id, Mention::Text { uri, content }))
- })
- }
- MentionUri::Thread { id: thread_id, .. } => {
- let open_task = thread_store.update(cx, |thread_store, cx| {
- thread_store.open_thread(&thread_id, window, cx)
- });
-
- let uri = uri.clone();
- cx.spawn(async move |cx| {
- let thread = open_task.await?;
- let content = thread.read_with(cx, |thread, _cx| {
- thread.latest_detailed_summary_or_text().to_string()
- })?;
-
- anyhow::Ok((crease_id, Mention::Text { uri, content }))
- })
- }
- MentionUri::TextThread { path, .. } => {
- let context = text_thread_store.update(cx, |text_thread_store, cx| {
- text_thread_store.open_local_context(path.as_path().into(), cx)
- });
- let uri = uri.clone();
- cx.spawn(async move |cx| {
- let context = context.await?;
- let xml = context.update(cx, |context, cx| context.to_xml(cx))?;
- anyhow::Ok((crease_id, Mention::Text { uri, content: xml }))
- })
- }
- MentionUri::Rule { id: prompt_id, .. } => {
- let Some(prompt_store) = thread_store.read(cx).prompt_store().clone()
- else {
- return Task::ready(Err(anyhow!("missing prompt store")));
- };
- let text_task = prompt_store.read(cx).load(*prompt_id, cx);
- let uri = uri.clone();
- cx.spawn(async move |_| {
- // TODO: report load errors instead of just logging
- let text = text_task.await?;
- anyhow::Ok((crease_id, Mention::Text { uri, content: text }))
- })
- }
- MentionUri::Fetch { url } => {
- let Some(content) = self.fetch_results.get(&url).cloned() else {
- return Task::ready(Err(anyhow!("missing fetch result")));
- };
- let uri = uri.clone();
- cx.spawn(async move |_| {
- Ok((
- crease_id,
- Mention::Text {
- uri,
- content: content.await.map_err(|e| anyhow::anyhow!("{e}"))?,
- },
- ))
- })
- }
- }
- })
- .collect::<Vec<_>>();
-
- contents.extend(self.images.iter().map(|(crease_id, image)| {
- let crease_id = *crease_id;
- let image = image.clone();
- cx.spawn(async move |_| {
- Ok((
- crease_id,
- Mention::Image(image.await.map_err(|e| anyhow::anyhow!("{e}"))?),
- ))
- })
- }));
-
- cx.spawn(async move |_cx| {
- let contents = try_join_all(contents).await?.into_iter().collect();
- anyhow::Ok(contents)
- })
- }
-}
-
-#[derive(Debug, Eq, PartialEq)]
-pub enum Mention {
- Text { uri: MentionUri, content: String },
- Image(MentionImage),
-}
-
pub(crate) enum Match {
File(FileMatch),
Symbol(SymbolMatch),
@@ -488,36 +245,31 @@ fn search(
}
pub struct ContextPickerCompletionProvider {
- mention_set: Arc<Mutex<MentionSet>>,
workspace: WeakEntity<Workspace>,
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeakEntity<TextThreadStore>,
- editor: WeakEntity<Editor>,
+ message_editor: WeakEntity<MessageEditor>,
}
impl ContextPickerCompletionProvider {
pub fn new(
- mention_set: Arc<Mutex<MentionSet>>,
workspace: WeakEntity<Workspace>,
thread_store: WeakEntity<ThreadStore>,
text_thread_store: WeakEntity<TextThreadStore>,
- editor: WeakEntity<Editor>,
+ message_editor: WeakEntity<MessageEditor>,
) -> Self {
Self {
- mention_set,
workspace,
thread_store,
text_thread_store,
- editor,
+ message_editor,
}
}
fn completion_for_entry(
entry: ContextPickerEntry,
- excerpt_id: ExcerptId,
source_range: Range<Anchor>,
- editor: Entity<Editor>,
- mention_set: Arc<Mutex<MentionSet>>,
+ message_editor: WeakEntity<MessageEditor>,
workspace: &Entity<Workspace>,
cx: &mut App,
) -> Option<Completion> {
@@ -538,88 +290,39 @@ impl ContextPickerCompletionProvider {
ContextPickerEntry::Action(action) => {
let (new_text, on_action) = match action {
ContextPickerAction::AddSelections => {
- let selections = selection_ranges(workspace, cx);
-
const PLACEHOLDER: &str = "selection ";
+ let selections = selection_ranges(workspace, cx)
+ .into_iter()
+ .enumerate()
+ .map(|(ix, (buffer, range))| {
+ (
+ buffer,
+ range,
+ (PLACEHOLDER.len() * ix)..(PLACEHOLDER.len() * (ix + 1) - 1),
+ )
+ })
+ .collect::<Vec<_>>();
- let new_text = std::iter::repeat(PLACEHOLDER)
- .take(selections.len())
- .chain(std::iter::once(""))
- .join(" ");
+ let new_text: String = PLACEHOLDER.repeat(selections.len());
let callback = Arc::new({
- let mention_set = mention_set.clone();
- let selections = selections.clone();
+ let source_range = source_range.clone();
move |_, window: &mut Window, cx: &mut App| {
- let editor = editor.clone();
- let mention_set = mention_set.clone();
let selections = selections.clone();
+ let message_editor = message_editor.clone();
+ let source_range = source_range.clone();
window.defer(cx, move |window, cx| {
- let mut current_offset = 0;
-
- for (buffer, selection_range) in selections {
- let snapshot =
- editor.read(cx).buffer().read(cx).snapshot(cx);
- let Some(start) = snapshot
- .anchor_in_excerpt(excerpt_id, source_range.start)
- else {
- return;
- };
-
- let offset = start.to_offset(&snapshot) + current_offset;
- let text_len = PLACEHOLDER.len() - 1;
-
- let range = snapshot.anchor_after(offset)
- ..snapshot.anchor_after(offset + text_len);
-
- let path = buffer
- .read(cx)
- .file()
- .map_or(PathBuf::from("untitled"), |file| {
- file.path().to_path_buf()
- });
-
- let point_range = snapshot
- .as_singleton()
- .map(|(_, _, snapshot)| {
- selection_range.to_point(&snapshot)
- })
- .unwrap_or_default();
- let line_range = point_range.start.row..point_range.end.row;
-
- let uri = MentionUri::Selection {
- path: path.clone(),
- line_range: line_range.clone(),
- };
- let crease = crate::context_picker::crease_for_mention(
- selection_name(&path, &line_range).into(),
- uri.icon_path(cx),
- range,
- editor.downgrade(),
- );
-
- let [crease_id]: [_; 1] =
- editor.update(cx, |editor, cx| {
- let crease_ids =
- editor.insert_creases(vec![crease.clone()], cx);
- editor.fold_creases(
- vec![crease],
- false,
- window,
- cx,
- );
- crease_ids.try_into().unwrap()
- });
-
- mention_set.lock().insert_uri(
- crease_id,
- MentionUri::Selection { path, line_range },
- );
-
- current_offset += text_len + 1;
- }
+ message_editor
+ .update(cx, |message_editor, cx| {
+ message_editor.confirm_mention_for_selection(
+ source_range,
+ selections,
+ window,
+ cx,
+ )
+ })
+ .ok();
});
-
false
}
});
@@ -647,11 +350,9 @@ impl ContextPickerCompletionProvider {
fn completion_for_thread(
thread_entry: ThreadContextEntry,
- excerpt_id: ExcerptId,
source_range: Range<Anchor>,
recent: bool,
- editor: Entity<Editor>,
- mention_set: Arc<Mutex<MentionSet>>,
+ editor: WeakEntity<MessageEditor>,
cx: &mut App,
) -> Completion {
let uri = match &thread_entry {
@@ -683,13 +384,10 @@ impl ContextPickerCompletionProvider {
source: project::CompletionSource::Custom,
icon_path: Some(icon_for_completion.clone()),
confirm: Some(confirm_completion_callback(
- uri.icon_path(cx),
thread_entry.title().clone(),
- excerpt_id,
source_range.start,
new_text_len - 1,
- editor.clone(),
- mention_set,
+ editor,
uri,
)),
}
@@ -697,10 +395,8 @@ impl ContextPickerCompletionProvider {
fn completion_for_rules(
rule: RulesContextEntry,
- excerpt_id: ExcerptId,
source_range: Range<Anchor>,
- editor: Entity<Editor>,
- mention_set: Arc<Mutex<MentionSet>>,
+ editor: WeakEntity<MessageEditor>,
cx: &mut App,
) -> Completion {
let uri = MentionUri::Rule {
@@ -719,13 +415,10 @@ impl ContextPickerCompletionProvider {
source: project::CompletionSource::Custom,
icon_path: Some(icon_path.clone()),
confirm: Some(confirm_completion_callback(
- icon_path,
rule.title.clone(),
- excerpt_id,
source_range.start,
new_text_len - 1,
- editor.clone(),
- mention_set,
+ editor,
uri,
)),
}
@@ -736,10 +429,8 @@ impl ContextPickerCompletionProvider {
path_prefix: &str,
is_recent: bool,
is_directory: bool,
- excerpt_id: ExcerptId,
source_range: Range<Anchor>,
- editor: Entity<Editor>,
- mention_set: Arc<Mutex<MentionSet>>,
+ message_editor: WeakEntity<MessageEditor>,
project: Entity<Project>,
cx: &mut App,
) -> Option<Completion> {
@@ -777,13 +468,10 @@ impl ContextPickerCompletionProvider {
icon_path: Some(completion_icon_path),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
- crease_icon_path,
file_name,
- excerpt_id,
source_range.start,
new_text_len - 1,
- editor,
- mention_set.clone(),
+ message_editor,
file_uri,
)),
})
@@ -791,10 +479,8 @@ impl ContextPickerCompletionProvider {
fn completion_for_symbol(
symbol: Symbol,
- excerpt_id: ExcerptId,
source_range: Range<Anchor>,
- editor: Entity<Editor>,
- mention_set: Arc<Mutex<MentionSet>>,
+ message_editor: WeakEntity<MessageEditor>,
workspace: Entity<Workspace>,
cx: &mut App,
) -> Option<Completion> {
@@ -820,13 +506,10 @@ impl ContextPickerCompletionProvider {
icon_path: Some(icon_path.clone()),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
- icon_path,
symbol.name.clone().into(),
- excerpt_id,
source_range.start,
new_text_len - 1,
- editor.clone(),
- mention_set.clone(),
+ message_editor,
uri,
)),
})
@@ -835,116 +518,32 @@ impl ContextPickerCompletionProvider {
fn completion_for_fetch(
source_range: Range<Anchor>,
url_to_fetch: SharedString,
- excerpt_id: ExcerptId,
- editor: Entity<Editor>,
- mention_set: Arc<Mutex<MentionSet>>,
- http_client: Arc<HttpClientWithUrl>,
+ message_editor: WeakEntity<MessageEditor>,
cx: &mut App,
) -> Option<Completion> {
let new_text = format!("@fetch {} ", url_to_fetch.clone());
- let new_text_len = new_text.len();
+ let url_to_fetch = url::Url::parse(url_to_fetch.as_ref())
+ .or_else(|_| url::Url::parse(&format!("https://{url_to_fetch}")))
+ .ok()?;
let mention_uri = MentionUri::Fetch {
- url: url::Url::parse(url_to_fetch.as_ref())
- .or_else(|_| url::Url::parse(&format!("https://{url_to_fetch}")))
- .ok()?,
+ url: url_to_fetch.clone(),
};
let icon_path = mention_uri.icon_path(cx);
Some(Completion {
replace_range: source_range.clone(),
- new_text,
+ new_text: new_text.clone(),
label: CodeLabel::plain(url_to_fetch.to_string(), None),
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(icon_path.clone()),
insert_text_mode: None,
- confirm: Some({
- let start = source_range.start;
- let content_len = new_text_len - 1;
- let editor = editor.clone();
- let url_to_fetch = url_to_fetch.clone();
- let source_range = source_range.clone();
- let icon_path = icon_path.clone();
- let mention_uri = mention_uri.clone();
- Arc::new(move |_, window, cx| {
- let Some(url) = url::Url::parse(url_to_fetch.as_ref())
- .or_else(|_| url::Url::parse(&format!("https://{url_to_fetch}")))
- .notify_app_err(cx)
- else {
- return false;
- };
-
- let editor = editor.clone();
- let mention_set = mention_set.clone();
- let http_client = http_client.clone();
- let source_range = source_range.clone();
- let icon_path = icon_path.clone();
- let mention_uri = mention_uri.clone();
- window.defer(cx, move |window, cx| {
- let url = url.clone();
-
- let Some(crease_id) = crate::context_picker::insert_crease_for_mention(
- excerpt_id,
- start,
- content_len,
- url.to_string().into(),
- icon_path,
- editor.clone(),
- window,
- cx,
- ) else {
- return;
- };
-
- let editor = editor.clone();
- let mention_set = mention_set.clone();
- let http_client = http_client.clone();
- let source_range = source_range.clone();
-
- let url_string = url.to_string();
- let fetch = cx
- .background_executor()
- .spawn(async move {
- fetch_url_content(http_client, url_string)
- .map_err(|e| e.to_string())
- .await
- })
- .shared();
- mention_set.lock().add_fetch_result(url, fetch.clone());
-
- window
- .spawn(cx, async move |cx| {
- if fetch.await.notify_async_err(cx).is_some() {
- mention_set
- .lock()
- .insert_uri(crease_id, mention_uri.clone());
- } else {
- // Remove crease if we failed to fetch
- editor
- .update(cx, |editor, cx| {
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- let Some(anchor) = snapshot
- .anchor_in_excerpt(excerpt_id, source_range.start)
- else {
- return;
- };
- editor.display_map.update(cx, |display_map, cx| {
- display_map.unfold_intersecting(
- vec![anchor..anchor],
- true,
- cx,
- );
- });
- editor.remove_creases([crease_id], cx);
- })
- .ok();
- }
- Some(())
- })
- .detach();
- });
- false
- })
- }),
+ confirm: Some(confirm_completion_callback(
+ url_to_fetch.to_string().into(),
+ source_range.start,
+ new_text.len() - 1,
+ message_editor,
+ mention_uri,
+ )),
})
}
}
@@ -968,7 +567,7 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx:
impl CompletionProvider for ContextPickerCompletionProvider {
fn completions(
&self,
- excerpt_id: ExcerptId,
+ _excerpt_id: ExcerptId,
buffer: &Entity<Buffer>,
buffer_position: Anchor,
_trigger: CompletionContext,
@@ -992,39 +591,24 @@ impl CompletionProvider for ContextPickerCompletionProvider {
};
let project = workspace.read(cx).project().clone();
- let http_client = workspace.read(cx).client().http_client();
let snapshot = buffer.read(cx).snapshot();
let source_range = snapshot.anchor_before(state.source_range.start)
..snapshot.anchor_after(state.source_range.end);
let thread_store = self.thread_store.clone();
let text_thread_store = self.text_thread_store.clone();
- let editor = self.editor.clone();
+ let editor = self.message_editor.clone();
+ let Ok((exclude_paths, exclude_threads)) =
+ self.message_editor.update(cx, |message_editor, _cx| {
+ message_editor.mentioned_path_and_threads()
+ })
+ else {
+ return Task::ready(Ok(Vec::new()));
+ };
let MentionCompletion { mode, argument, .. } = state;
let query = argument.unwrap_or_else(|| "".to_string());
- let (exclude_paths, exclude_threads) = {
- let mention_set = self.mention_set.lock();
-
- let mut excluded_paths = HashSet::default();
- let mut excluded_threads = HashSet::default();
-
- for uri in mention_set.uri_by_crease_id.values() {
- match uri {
- MentionUri::File { abs_path, .. } => {
- excluded_paths.insert(abs_path.clone());
- }
- MentionUri::Thread { id, .. } => {
- excluded_threads.insert(id.clone());
- }
- _ => {}
- }
- }
-
- (excluded_paths, excluded_threads)
- };
-
let recent_entries = recent_context_picker_entries(
Some(thread_store.clone()),
Some(text_thread_store.clone()),
@@ -1051,13 +635,8 @@ impl CompletionProvider for ContextPickerCompletionProvider {
cx,
);
- let mention_set = self.mention_set.clone();
-
cx.spawn(async move |_, cx| {
let matches = search_task.await;
- let Some(editor) = editor.upgrade() else {
- return Ok(Vec::new());
- };
let completions = cx.update(|cx| {
matches
@@ -1074,10 +653,8 @@ impl CompletionProvider for ContextPickerCompletionProvider {
&mat.path_prefix,
is_recent,
mat.is_dir,
- excerpt_id,
source_range.clone(),
editor.clone(),
- mention_set.clone(),
project.clone(),
cx,
)
@@ -1085,10 +662,8 @@ impl CompletionProvider for ContextPickerCompletionProvider {
Match::Symbol(SymbolMatch { symbol, .. }) => Self::completion_for_symbol(
symbol,
- excerpt_id,
source_range.clone(),
editor.clone(),
- mention_set.clone(),
workspace.clone(),
cx,
),
@@ -1097,39 +672,30 @@ impl CompletionProvider for ContextPickerCompletionProvider {
thread, is_recent, ..
}) => Some(Self::completion_for_thread(
thread,
- excerpt_id,
source_range.clone(),
is_recent,
editor.clone(),
- mention_set.clone(),
cx,
)),
Match::Rules(user_rules) => Some(Self::completion_for_rules(
user_rules,
- excerpt_id,
source_range.clone(),
editor.clone(),
- mention_set.clone(),
cx,
)),
Match::Fetch(url) => Self::completion_for_fetch(
source_range.clone(),
url,
- excerpt_id,
editor.clone(),
- mention_set.clone(),
- http_client.clone(),
cx,
),
Match::Entry(EntryMatch { entry, .. }) => Self::completion_for_entry(
entry,
- excerpt_id,
source_range.clone(),
editor.clone(),
- mention_set.clone(),
&workspace,
cx,
),
@@ -1182,36 +748,30 @@ impl CompletionProvider for ContextPickerCompletionProvider {
}
fn confirm_completion_callback(
- crease_icon_path: SharedString,
crease_text: SharedString,
- excerpt_id: ExcerptId,
start: Anchor,
content_len: usize,
- editor: Entity<Editor>,
- mention_set: Arc<Mutex<MentionSet>>,
+ message_editor: WeakEntity<MessageEditor>,
mention_uri: MentionUri,
) -> Arc<dyn Fn(CompletionIntent, &mut Window, &mut App) -> bool + Send + Sync> {
Arc::new(move |_, window, cx| {
+ let message_editor = message_editor.clone();
let crease_text = crease_text.clone();
- let crease_icon_path = crease_icon_path.clone();
- let editor = editor.clone();
- let mention_set = mention_set.clone();
let mention_uri = mention_uri.clone();
window.defer(cx, move |window, cx| {
- if let Some(crease_id) = crate::context_picker::insert_crease_for_mention(
- excerpt_id,
- start,
- content_len,
- crease_text.clone(),
- crease_icon_path,
- editor.clone(),
- window,
- cx,
- ) {
- mention_set
- .lock()
- .insert_uri(crease_id, mention_uri.clone());
- }
+ message_editor
+ .clone()
+ .update(cx, |message_editor, cx| {
+ message_editor.confirm_completion(
+ crease_text,
+ start,
+ content_len,
+ mention_uri,
+ window,
+ cx,
+ )
+ })
+ .ok();
});
false
})
@@ -1279,15 +839,6 @@ impl MentionCompletion {
#[cfg(test)]
mod tests {
use super::*;
- use editor::AnchorRangeExt;
- use gpui::{EventEmitter, FocusHandle, Focusable, TestAppContext, VisualTestContext};
- use project::{Project, ProjectPath};
- use serde_json::json;
- use settings::SettingsStore;
- use smol::stream::StreamExt as _;
- use std::{ops::Deref, path::Path, rc::Rc};
- use util::path;
- use workspace::{AppState, Item};
#[test]
fn test_mention_completion_parse() {
@@ -1358,478 +909,4 @@ mod tests {
assert_eq!(MentionCompletion::try_parse("test@", 0), None);
}
-
- struct AtMentionEditor(Entity<Editor>);
-
- impl Item for AtMentionEditor {
- type Event = ();
-
- fn include_in_nav_history() -> bool {
- false
- }
-
- fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
- "Test".into()
- }
- }
-
- impl EventEmitter<()> for AtMentionEditor {}
-
- impl Focusable for AtMentionEditor {
- fn focus_handle(&self, cx: &App) -> FocusHandle {
- self.0.read(cx).focus_handle(cx).clone()
- }
- }
-
- impl Render for AtMentionEditor {
- fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
- self.0.clone().into_any_element()
- }
- }
-
- #[gpui::test]
- async fn test_context_completion_provider(cx: &mut TestAppContext) {
- init_test(cx);
-
- let app_state = cx.update(AppState::test);
-
- cx.update(|cx| {
- language::init(cx);
- editor::init(cx);
- workspace::init(app_state.clone(), cx);
- Project::init_settings(cx);
- });
-
- app_state
- .fs
- .as_fake()
- .insert_tree(
- path!("/dir"),
- json!({
- "editor": "",
- "a": {
- "one.txt": "1",
- "two.txt": "2",
- "three.txt": "3",
- "four.txt": "4"
- },
- "b": {
- "five.txt": "5",
- "six.txt": "6",
- "seven.txt": "7",
- "eight.txt": "8",
- }
- }),
- )
- .await;
-
- let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await;
- let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
- let workspace = window.root(cx).unwrap();
-
- let worktree = project.update(cx, |project, cx| {
- let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
- assert_eq!(worktrees.len(), 1);
- worktrees.pop().unwrap()
- });
- let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
-
- let mut cx = VisualTestContext::from_window(*window.deref(), cx);
-
- let paths = vec![
- path!("a/one.txt"),
- path!("a/two.txt"),
- path!("a/three.txt"),
- path!("a/four.txt"),
- path!("b/five.txt"),
- path!("b/six.txt"),
- path!("b/seven.txt"),
- path!("b/eight.txt"),
- ];
-
- let mut opened_editors = Vec::new();
- for path in paths {
- let buffer = workspace
- .update_in(&mut cx, |workspace, window, cx| {
- workspace.open_path(
- ProjectPath {
- worktree_id,
- path: Path::new(path).into(),
- },
- None,
- false,
- window,
- cx,
- )
- })
- .await
- .unwrap();
- opened_editors.push(buffer);
- }
-
- let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
- let editor = cx.new(|cx| {
- Editor::new(
- editor::EditorMode::full(),
- multi_buffer::MultiBuffer::build_simple("", cx),
- None,
- window,
- cx,
- )
- });
- workspace.active_pane().update(cx, |pane, cx| {
- pane.add_item(
- Box::new(cx.new(|_| AtMentionEditor(editor.clone()))),
- true,
- true,
- None,
- window,
- cx,
- );
- });
- editor
- });
-
- let mention_set = Arc::new(Mutex::new(MentionSet::default()));
-
- let thread_store = cx.new(|cx| ThreadStore::fake(project.clone(), cx));
- let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
-
- let editor_entity = editor.downgrade();
- editor.update_in(&mut cx, |editor, window, cx| {
- window.focus(&editor.focus_handle(cx));
- editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
- mention_set.clone(),
- workspace.downgrade(),
- thread_store.downgrade(),
- text_thread_store.downgrade(),
- editor_entity,
- ))));
- });
-
- cx.simulate_input("Lorem ");
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(editor.text(cx), "Lorem ");
- assert!(!editor.has_visible_completions_menu());
- });
-
- cx.simulate_input("@");
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(editor.text(cx), "Lorem @");
- assert!(editor.has_visible_completions_menu());
- assert_eq!(
- current_completion_labels(editor),
- &[
- "eight.txt dir/b/",
- "seven.txt dir/b/",
- "six.txt dir/b/",
- "five.txt dir/b/",
- "Files & Directories",
- "Symbols",
- "Threads",
- "Fetch"
- ]
- );
- });
-
- // Select and confirm "File"
- editor.update_in(&mut cx, |editor, window, cx| {
- assert!(editor.has_visible_completions_menu());
- editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
- editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
- editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
- editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
- editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
- });
-
- cx.run_until_parked();
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(editor.text(cx), "Lorem @file ");
- assert!(editor.has_visible_completions_menu());
- });
-
- cx.simulate_input("one");
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(editor.text(cx), "Lorem @file one");
- assert!(editor.has_visible_completions_menu());
- assert_eq!(current_completion_labels(editor), vec!["one.txt dir/a/"]);
- });
-
- editor.update_in(&mut cx, |editor, window, cx| {
- assert!(editor.has_visible_completions_menu());
- editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
- });
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(editor.text(cx), "Lorem [@one.txt](file:///dir/a/one.txt) ");
- assert!(!editor.has_visible_completions_menu());
- assert_eq!(
- fold_ranges(editor, cx),
- vec![Point::new(0, 6)..Point::new(0, 39)]
- );
- });
-
- let contents = cx
- .update(|window, cx| {
- mention_set.lock().contents(
- project.clone(),
- thread_store.clone(),
- text_thread_store.clone(),
- window,
- cx,
- )
- })
- .await
- .unwrap()
- .into_values()
- .collect::<Vec<_>>();
-
- pretty_assertions::assert_eq!(
- contents,
- [Mention::Text {
- content: "1".into(),
- uri: "file:///dir/a/one.txt".parse().unwrap()
- }]
- );
-
- cx.simulate_input(" ");
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(editor.text(cx), "Lorem [@one.txt](file:///dir/a/one.txt) ");
- assert!(!editor.has_visible_completions_menu());
- assert_eq!(
- fold_ranges(editor, cx),
- vec![Point::new(0, 6)..Point::new(0, 39)]
- );
- });
-
- cx.simulate_input("Ipsum ");
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(
- editor.text(cx),
- "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum ",
- );
- assert!(!editor.has_visible_completions_menu());
- assert_eq!(
- fold_ranges(editor, cx),
- vec![Point::new(0, 6)..Point::new(0, 39)]
- );
- });
-
- cx.simulate_input("@file ");
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(
- editor.text(cx),
- "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum @file ",
- );
- assert!(editor.has_visible_completions_menu());
- assert_eq!(
- fold_ranges(editor, cx),
- vec![Point::new(0, 6)..Point::new(0, 39)]
- );
- });
-
- editor.update_in(&mut cx, |editor, window, cx| {
- editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
- });
-
- cx.run_until_parked();
-
- let contents = cx
- .update(|window, cx| {
- mention_set.lock().contents(
- project.clone(),
- thread_store.clone(),
- text_thread_store.clone(),
- window,
- cx,
- )
- })
- .await
- .unwrap()
- .into_values()
- .collect::<Vec<_>>();
-
- assert_eq!(contents.len(), 2);
- pretty_assertions::assert_eq!(
- contents[1],
- Mention::Text {
- content: "8".to_string(),
- uri: "file:///dir/b/eight.txt".parse().unwrap(),
- }
- );
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(
- editor.text(cx),
- "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum [@eight.txt](file:///dir/b/eight.txt) "
- );
- assert!(!editor.has_visible_completions_menu());
- assert_eq!(
- fold_ranges(editor, cx),
- vec![
- Point::new(0, 6)..Point::new(0, 39),
- Point::new(0, 47)..Point::new(0, 84)
- ]
- );
- });
-
- let plain_text_language = Arc::new(language::Language::new(
- language::LanguageConfig {
- name: "Plain Text".into(),
- matcher: language::LanguageMatcher {
- path_suffixes: vec!["txt".to_string()],
- ..Default::default()
- },
- ..Default::default()
- },
- None,
- ));
-
- // Register the language and fake LSP
- let language_registry = project.read_with(&cx, |project, _| project.languages().clone());
- language_registry.add(plain_text_language);
-
- let mut fake_language_servers = language_registry.register_fake_lsp(
- "Plain Text",
- language::FakeLspAdapter {
- capabilities: lsp::ServerCapabilities {
- workspace_symbol_provider: Some(lsp::OneOf::Left(true)),
- ..Default::default()
- },
- ..Default::default()
- },
- );
-
- // Open the buffer to trigger LSP initialization
- let buffer = project
- .update(&mut cx, |project, cx| {
- project.open_local_buffer(path!("/dir/a/one.txt"), cx)
- })
- .await
- .unwrap();
-
- // Register the buffer with language servers
- let _handle = project.update(&mut cx, |project, cx| {
- project.register_buffer_with_language_servers(&buffer, cx)
- });
-
- cx.run_until_parked();
-
- let fake_language_server = fake_language_servers.next().await.unwrap();
- fake_language_server.set_request_handler::<lsp::WorkspaceSymbolRequest, _, _>(
- |_, _| async move {
- Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
- #[allow(deprecated)]
- lsp::SymbolInformation {
- name: "MySymbol".into(),
- location: lsp::Location {
- uri: lsp::Url::from_file_path(path!("/dir/a/one.txt")).unwrap(),
- range: lsp::Range::new(
- lsp::Position::new(0, 0),
- lsp::Position::new(0, 1),
- ),
- },
- kind: lsp::SymbolKind::CONSTANT,
- tags: None,
- container_name: None,
- deprecated: None,
- },
- ])))
- },
- );
-
- cx.simulate_input("@symbol ");
-
- editor.update(&mut cx, |editor, cx| {
- assert_eq!(
- editor.text(cx),
- "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum [@eight.txt](file:///dir/b/eight.txt) @symbol "
- );
- assert!(editor.has_visible_completions_menu());
- assert_eq!(
- current_completion_labels(editor),
- &[
- "MySymbol",
- ]
- );
- });
-
- editor.update_in(&mut cx, |editor, window, cx| {
- editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
- });
-
- let contents = cx
- .update(|window, cx| {
- mention_set.lock().contents(
- project.clone(),
- thread_store,
- text_thread_store,
- window,
- cx,
- )
- })
- .await
- .unwrap()
- .into_values()
- .collect::<Vec<_>>();
-
- assert_eq!(contents.len(), 3);
- pretty_assertions::assert_eq!(
- contents[2],
- Mention::Text {
- content: "1".into(),
- uri: "file:///dir/a/one.txt?symbol=MySymbol#L1:1"
- .parse()
- .unwrap(),
- }
- );
-
- cx.run_until_parked();
-
- editor.read_with(&mut cx, |editor, cx| {
- assert_eq!(
- editor.text(cx),
- "Lorem [@one.txt](file:///dir/a/one.txt) Ipsum [@eight.txt](file:///dir/b/eight.txt) [@MySymbol](file:///dir/a/one.txt?symbol=MySymbol#L1:1) "
- );
- });
- }
-
- fn fold_ranges(editor: &Editor, cx: &mut App) -> Vec<Range<Point>> {
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- editor.display_map.update(cx, |display_map, cx| {
- display_map
- .snapshot(cx)
- .folds_in_range(0..snapshot.len())
- .map(|fold| fold.range.to_point(&snapshot))
- .collect()
- })
- }
-
- fn current_completion_labels(editor: &Editor) -> Vec<String> {
- let completions = editor.current_completions().expect("Missing completions");
- completions
- .into_iter()
- .map(|completion| completion.label.text.to_string())
- .collect::<Vec<_>>()
- }
-
- pub(crate) fn init_test(cx: &mut TestAppContext) {
- cx.update(|cx| {
- let store = SettingsStore::test(cx);
- cx.set_global(store);
- theme::init(theme::LoadThemes::JustBase, cx);
- client::init_settings(cx);
- language::init(cx);
- Project::init_settings(cx);
- workspace::init_settings(cx);
- editor::init_settings(cx);
- });
- }
}
@@ -1,45 +1,141 @@
-use std::{collections::HashMap, ops::Range};
+use std::ops::Range;
-use acp_thread::AcpThread;
-use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer};
+use acp_thread::{AcpThread, AgentThreadEntry};
+use agent::{TextThreadStore, ThreadStore};
+use collections::HashMap;
+use editor::{Editor, EditorMode, MinimapVisibility};
use gpui::{
- AnyEntity, App, AppContext as _, Entity, EntityId, TextStyleRefinement, WeakEntity, Window,
+ AnyEntity, App, AppContext as _, Entity, EntityId, EventEmitter, TextStyleRefinement,
+ WeakEntity, Window,
};
use language::language_settings::SoftWrap;
+use project::Project;
use settings::Settings as _;
use terminal_view::TerminalView;
use theme::ThemeSettings;
-use ui::TextSize;
+use ui::{Context, TextSize};
use workspace::Workspace;
-#[derive(Default)]
+use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
+
pub struct EntryViewState {
+ workspace: WeakEntity<Workspace>,
+ project: Entity<Project>,
+ thread_store: Entity<ThreadStore>,
+ text_thread_store: Entity<TextThreadStore>,
entries: Vec<Entry>,
}
impl EntryViewState {
+ pub fn new(
+ workspace: WeakEntity<Workspace>,
+ project: Entity<Project>,
+ thread_store: Entity<ThreadStore>,
+ text_thread_store: Entity<TextThreadStore>,
+ ) -> Self {
+ Self {
+ workspace,
+ project,
+ thread_store,
+ text_thread_store,
+ entries: Vec::new(),
+ }
+ }
+
pub fn entry(&self, index: usize) -> Option<&Entry> {
self.entries.get(index)
}
pub fn sync_entry(
&mut self,
- workspace: WeakEntity<Workspace>,
- thread: Entity<AcpThread>,
index: usize,
+ thread: &Entity<AcpThread>,
window: &mut Window,
- cx: &mut App,
+ cx: &mut Context<Self>,
) {
- debug_assert!(index <= self.entries.len());
- let entry = if let Some(entry) = self.entries.get_mut(index) {
- entry
- } else {
- self.entries.push(Entry::default());
- self.entries.last_mut().unwrap()
+ let Some(thread_entry) = thread.read(cx).entries().get(index) else {
+ return;
+ };
+
+ match thread_entry {
+ AgentThreadEntry::UserMessage(message) => {
+ let has_id = message.id.is_some();
+ let chunks = message.chunks.clone();
+ let message_editor = cx.new(|cx| {
+ let mut editor = MessageEditor::new(
+ self.workspace.clone(),
+ self.project.clone(),
+ self.thread_store.clone(),
+ self.text_thread_store.clone(),
+ editor::EditorMode::AutoHeight {
+ min_lines: 1,
+ max_lines: None,
+ },
+ window,
+ cx,
+ );
+ if !has_id {
+ editor.set_read_only(true, cx);
+ }
+ editor.set_message(chunks, window, cx);
+ editor
+ });
+ cx.subscribe(&message_editor, move |_, editor, event, cx| {
+ cx.emit(EntryViewEvent {
+ entry_index: index,
+ view_event: ViewEvent::MessageEditorEvent(editor, *event),
+ })
+ })
+ .detach();
+ self.set_entry(index, Entry::UserMessage(message_editor));
+ }
+ AgentThreadEntry::ToolCall(tool_call) => {
+ let terminals = tool_call.terminals().cloned().collect::<Vec<_>>();
+ let diffs = tool_call.diffs().cloned().collect::<Vec<_>>();
+
+ let views = if let Some(Entry::Content(views)) = self.entries.get_mut(index) {
+ views
+ } else {
+ self.set_entry(index, Entry::empty());
+ let Some(Entry::Content(views)) = self.entries.get_mut(index) else {
+ unreachable!()
+ };
+ views
+ };
+
+ for terminal in terminals {
+ views.entry(terminal.entity_id()).or_insert_with(|| {
+ create_terminal(
+ self.workspace.clone(),
+ self.project.clone(),
+ terminal.clone(),
+ window,
+ cx,
+ )
+ .into_any()
+ });
+ }
+
+ for diff in diffs {
+ views
+ .entry(diff.entity_id())
+ .or_insert_with(|| create_editor_diff(diff.clone(), window, cx).into_any());
+ }
+ }
+ AgentThreadEntry::AssistantMessage(_) => {
+ if index == self.entries.len() {
+ self.entries.push(Entry::empty())
+ }
+ }
};
+ }
- entry.sync_diff_multibuffers(&thread, index, window, cx);
- entry.sync_terminals(&workspace, &thread, index, window, cx);
+ fn set_entry(&mut self, index: usize, entry: Entry) {
+ if index == self.entries.len() {
+ self.entries.push(entry);
+ } else {
+ self.entries[index] = entry;
+ }
}
pub fn remove(&mut self, range: Range<usize>) {
@@ -48,26 +144,51 @@ impl EntryViewState {
pub fn settings_changed(&mut self, cx: &mut App) {
for entry in self.entries.iter() {
- for view in entry.views.values() {
- if let Ok(diff_editor) = view.clone().downcast::<Editor>() {
- diff_editor.update(cx, |diff_editor, cx| {
- diff_editor
- .set_text_style_refinement(diff_editor_text_style_refinement(cx));
- cx.notify();
- })
+ match entry {
+ Entry::UserMessage { .. } => {}
+ Entry::Content(response_views) => {
+ for view in response_views.values() {
+ if let Ok(diff_editor) = view.clone().downcast::<Editor>() {
+ diff_editor.update(cx, |diff_editor, cx| {
+ diff_editor.set_text_style_refinement(
+ diff_editor_text_style_refinement(cx),
+ );
+ cx.notify();
+ })
+ }
+ }
}
}
}
}
}
-pub struct Entry {
- views: HashMap<EntityId, AnyEntity>,
+impl EventEmitter<EntryViewEvent> for EntryViewState {}
+
+pub struct EntryViewEvent {
+ pub entry_index: usize,
+ pub view_event: ViewEvent,
+}
+
+pub enum ViewEvent {
+ MessageEditorEvent(Entity<MessageEditor>, MessageEditorEvent),
+}
+
+pub enum Entry {
+ UserMessage(Entity<MessageEditor>),
+ Content(HashMap<EntityId, AnyEntity>),
}
impl Entry {
- pub fn editor_for_diff(&self, diff: &Entity<MultiBuffer>) -> Option<Entity<Editor>> {
- self.views
+ pub fn message_editor(&self) -> Option<&Entity<MessageEditor>> {
+ match self {
+ Self::UserMessage(editor) => Some(editor),
+ Entry::Content(_) => None,
+ }
+ }
+
+ pub fn editor_for_diff(&self, diff: &Entity<acp_thread::Diff>) -> Option<Entity<Editor>> {
+ self.content_map()?
.get(&diff.entity_id())
.cloned()
.map(|entity| entity.downcast::<Editor>().unwrap())
@@ -77,118 +198,88 @@ impl Entry {
&self,
terminal: &Entity<acp_thread::Terminal>,
) -> Option<Entity<TerminalView>> {
- self.views
+ self.content_map()?
.get(&terminal.entity_id())
.cloned()
.map(|entity| entity.downcast::<TerminalView>().unwrap())
}
- fn sync_diff_multibuffers(
- &mut self,
- thread: &Entity<AcpThread>,
- index: usize,
- window: &mut Window,
- cx: &mut App,
- ) {
- let Some(entry) = thread.read(cx).entries().get(index) else {
- return;
- };
-
- let multibuffers = entry
- .diffs()
- .map(|diff| diff.read(cx).multibuffer().clone());
-
- let multibuffers = multibuffers.collect::<Vec<_>>();
-
- for multibuffer in multibuffers {
- if self.views.contains_key(&multibuffer.entity_id()) {
- return;
- }
-
- let editor = cx.new(|cx| {
- let mut editor = Editor::new(
- EditorMode::Full {
- scale_ui_elements_with_buffer_font_size: false,
- show_active_line_background: false,
- sized_by_content: true,
- },
- multibuffer.clone(),
- None,
- window,
- cx,
- );
- editor.set_show_gutter(false, cx);
- editor.disable_inline_diagnostics();
- editor.disable_expand_excerpt_buttons(cx);
- editor.set_show_vertical_scrollbar(false, cx);
- editor.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
- editor.set_soft_wrap_mode(SoftWrap::None, cx);
- editor.scroll_manager.set_forbid_vertical_scroll(true);
- editor.set_show_indent_guides(false, cx);
- editor.set_read_only(true);
- editor.set_show_breakpoints(false, cx);
- editor.set_show_code_actions(false, cx);
- editor.set_show_git_diff_gutter(false, cx);
- editor.set_expand_all_diff_hunks(cx);
- editor.set_text_style_refinement(diff_editor_text_style_refinement(cx));
- editor
- });
-
- let entity_id = multibuffer.entity_id();
- self.views.insert(entity_id, editor.into_any());
+ fn content_map(&self) -> Option<&HashMap<EntityId, AnyEntity>> {
+ match self {
+ Self::Content(map) => Some(map),
+ _ => None,
}
}
- fn sync_terminals(
- &mut self,
- workspace: &WeakEntity<Workspace>,
- thread: &Entity<AcpThread>,
- index: usize,
- window: &mut Window,
- cx: &mut App,
- ) {
- let Some(entry) = thread.read(cx).entries().get(index) else {
- return;
- };
-
- let terminals = entry
- .terminals()
- .map(|terminal| terminal.clone())
- .collect::<Vec<_>>();
-
- for terminal in terminals {
- if self.views.contains_key(&terminal.entity_id()) {
- return;
- }
-
- let Some(strong_workspace) = workspace.upgrade() else {
- return;
- };
-
- let terminal_view = cx.new(|cx| {
- let mut view = TerminalView::new(
- terminal.read(cx).inner().clone(),
- workspace.clone(),
- None,
- strong_workspace.read(cx).project().downgrade(),
- window,
- cx,
- );
- view.set_embedded_mode(Some(1000), cx);
- view
- });
-
- let entity_id = terminal.entity_id();
- self.views.insert(entity_id, terminal_view.into_any());
- }
+ fn empty() -> Self {
+ Self::Content(HashMap::default())
}
#[cfg(test)]
- pub fn len(&self) -> usize {
- self.views.len()
+ pub fn has_content(&self) -> bool {
+ match self {
+ Self::Content(map) => !map.is_empty(),
+ Self::UserMessage(_) => false,
+ }
}
}
+fn create_terminal(
+ workspace: WeakEntity<Workspace>,
+ project: Entity<Project>,
+ terminal: Entity<acp_thread::Terminal>,
+ window: &mut Window,
+ cx: &mut App,
+) -> Entity<TerminalView> {
+ cx.new(|cx| {
+ let mut view = TerminalView::new(
+ terminal.read(cx).inner().clone(),
+ workspace.clone(),
+ None,
+ project.downgrade(),
+ window,
+ cx,
+ );
+ view.set_embedded_mode(Some(1000), cx);
+ view
+ })
+}
+
+fn create_editor_diff(
+ diff: Entity<acp_thread::Diff>,
+ window: &mut Window,
+ cx: &mut App,
+) -> Entity<Editor> {
+ cx.new(|cx| {
+ let mut editor = Editor::new(
+ EditorMode::Full {
+ scale_ui_elements_with_buffer_font_size: false,
+ show_active_line_background: false,
+ sized_by_content: true,
+ },
+ diff.read(cx).multibuffer().clone(),
+ None,
+ window,
+ cx,
+ );
+ editor.set_show_gutter(false, cx);
+ editor.disable_inline_diagnostics();
+ editor.disable_expand_excerpt_buttons(cx);
+ editor.set_show_vertical_scrollbar(false, cx);
+ editor.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
+ editor.set_soft_wrap_mode(SoftWrap::None, cx);
+ editor.scroll_manager.set_forbid_vertical_scroll(true);
+ editor.set_show_indent_guides(false, cx);
+ editor.set_read_only(true);
+ editor.set_show_breakpoints(false, cx);
+ editor.set_show_code_actions(false, cx);
+ editor.set_show_git_diff_gutter(false, cx);
+ editor.set_expand_all_diff_hunks(cx);
+ editor.set_text_style_refinement(diff_editor_text_style_refinement(cx));
+ editor
+ })
+}
+
fn diff_editor_text_style_refinement(cx: &mut App) -> TextStyleRefinement {
TextStyleRefinement {
font_size: Some(
@@ -201,26 +292,20 @@ fn diff_editor_text_style_refinement(cx: &mut App) -> TextStyleRefinement {
}
}
-impl Default for Entry {
- fn default() -> Self {
- Self {
- // Avoid allocating in the heap by default
- views: HashMap::with_capacity(0),
- }
- }
-}
-
#[cfg(test)]
mod tests {
use std::{path::Path, rc::Rc};
use acp_thread::{AgentConnection, StubAgentConnection};
+ use agent::{TextThreadStore, ThreadStore};
use agent_client_protocol as acp;
use agent_settings::AgentSettings;
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
use editor::{EditorSettings, RowInfo};
use fs::FakeFs;
- use gpui::{SemanticVersion, TestAppContext};
+ use gpui::{AppContext as _, SemanticVersion, TestAppContext};
+
+ use crate::acp::entry_view_state::EntryViewState;
use multi_buffer::MultiBufferRow;
use pretty_assertions::assert_matches;
use project::Project;
@@ -230,8 +315,6 @@ mod tests {
use util::path;
use workspace::Workspace;
- use crate::acp::entry_view_state::EntryViewState;
-
#[gpui::test]
async fn test_diff_sync(cx: &mut TestAppContext) {
init_test(cx);
@@ -269,7 +352,7 @@ mod tests {
.update(|_, cx| {
connection
.clone()
- .new_thread(project, Path::new(path!("/project")), cx)
+ .new_thread(project.clone(), Path::new(path!("/project")), cx)
})
.await
.unwrap();
@@ -279,12 +362,23 @@ mod tests {
connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx)
});
- let mut view_state = EntryViewState::default();
- cx.update(|window, cx| {
- view_state.sync_entry(workspace.downgrade(), thread.clone(), 0, window, cx);
+ let thread_store = cx.new(|cx| ThreadStore::fake(project.clone(), cx));
+ let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
+
+ let view_state = cx.new(|_cx| {
+ EntryViewState::new(
+ workspace.downgrade(),
+ project.clone(),
+ thread_store,
+ text_thread_store,
+ )
+ });
+
+ view_state.update_in(cx, |view_state, window, cx| {
+ view_state.sync_entry(0, &thread, window, cx)
});
- let multibuffer = thread.read_with(cx, |thread, cx| {
+ let diff = thread.read_with(cx, |thread, _cx| {
thread
.entries()
.get(0)
@@ -292,15 +386,14 @@ mod tests {
.diffs()
.next()
.unwrap()
- .read(cx)
- .multibuffer()
.clone()
});
cx.run_until_parked();
- let entry = view_state.entry(0).unwrap();
- let diff_editor = entry.editor_for_diff(&multibuffer).unwrap();
+ let diff_editor = view_state.read_with(cx, |view_state, _cx| {
+ view_state.entry(0).unwrap().editor_for_diff(&diff).unwrap()
+ });
assert_eq!(
diff_editor.read_with(cx, |editor, cx| editor.text(cx)),
"hi world\nhello world"
@@ -1,61 +1,66 @@
-use crate::acp::completion_provider::ContextPickerCompletionProvider;
-use crate::acp::completion_provider::MentionImage;
-use crate::acp::completion_provider::MentionSet;
-use acp_thread::MentionUri;
-use agent::TextThreadStore;
-use agent::ThreadStore;
+use crate::{
+ acp::completion_provider::ContextPickerCompletionProvider,
+ context_picker::fetch_context_picker::fetch_url_content,
+};
+use acp_thread::{MentionUri, selection_name};
+use agent::{TextThreadStore, ThreadId, ThreadStore};
use agent_client_protocol as acp;
-use anyhow::Result;
-use collections::HashSet;
-use editor::ExcerptId;
-use editor::actions::Paste;
-use editor::display_map::CreaseId;
+use anyhow::{Context as _, Result, anyhow};
+use collections::{HashMap, HashSet};
use editor::{
- AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorMode,
- EditorStyle, MultiBuffer,
+ Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
+ EditorMode, EditorStyle, ExcerptId, FoldPlaceholder, MultiBuffer, ToOffset,
+ actions::Paste,
+ display_map::{Crease, CreaseId, FoldId},
+};
+use futures::{
+ FutureExt as _, TryFutureExt as _,
+ future::{Shared, try_join_all},
};
-use futures::FutureExt as _;
-use gpui::ClipboardEntry;
-use gpui::Image;
-use gpui::ImageFormat;
use gpui::{
- AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, Task, TextStyle, WeakEntity,
+ AppContext, ClipboardEntry, Context, Entity, EventEmitter, FocusHandle, Focusable, Image,
+ ImageFormat, Img, Task, TextStyle, WeakEntity,
};
-use language::Buffer;
-use language::Language;
+use language::{Buffer, Language};
use language_model::LanguageModelImage;
-use parking_lot::Mutex;
use project::{CompletionIntent, Project};
+use rope::Point;
use settings::Settings;
-use std::fmt::Write;
-use std::path::Path;
-use std::rc::Rc;
-use std::sync::Arc;
+use std::{
+ ffi::OsStr,
+ fmt::Write,
+ ops::Range,
+ path::{Path, PathBuf},
+ rc::Rc,
+ sync::Arc,
+};
+use text::OffsetRangeExt;
use theme::ThemeSettings;
-use ui::IconName;
-use ui::SharedString;
use ui::{
- ActiveTheme, App, InteractiveElement, IntoElement, ParentElement, Render, Styled, TextSize,
- Window, div,
+ ActiveTheme, AnyElement, App, ButtonCommon, ButtonLike, ButtonStyle, Color, Icon, IconName,
+ IconSize, InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ParentElement,
+ Render, SelectableButton, SharedString, Styled, TextSize, TintColor, Toggleable, Window, div,
+ h_flex,
};
+use url::Url;
use util::ResultExt;
-use workspace::Workspace;
-use workspace::notifications::NotifyResultExt as _;
+use workspace::{Workspace, notifications::NotifyResultExt as _};
use zed_actions::agent::Chat;
-use super::completion_provider::Mention;
-
pub struct MessageEditor {
+ mention_set: MentionSet,
editor: Entity<Editor>,
project: Entity<Project>,
+ workspace: WeakEntity<Workspace>,
thread_store: Entity<ThreadStore>,
text_thread_store: Entity<TextThreadStore>,
- mention_set: Arc<Mutex<MentionSet>>,
}
+#[derive(Clone, Copy)]
pub enum MessageEditorEvent {
Send,
Cancel,
+ Focus,
}
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
@@ -77,8 +82,13 @@ impl MessageEditor {
},
None,
);
-
- let mention_set = Arc::new(Mutex::new(MentionSet::default()));
+ let completion_provider = ContextPickerCompletionProvider::new(
+ workspace.clone(),
+ thread_store.downgrade(),
+ text_thread_store.downgrade(),
+ cx.weak_entity(),
+ );
+ let mention_set = MentionSet::default();
let editor = cx.new(|cx| {
let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
@@ -88,13 +98,7 @@ impl MessageEditor {
editor.set_show_indent_guides(false, cx);
editor.set_soft_wrap();
editor.set_use_modal_editing(true);
- editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
- mention_set.clone(),
- workspace,
- thread_store.downgrade(),
- text_thread_store.downgrade(),
- cx.weak_entity(),
- ))));
+ editor.set_completion_provider(Some(Rc::new(completion_provider)));
editor.set_context_menu_options(ContextMenuOptions {
min_entries_visible: 12,
max_entries_visible: 12,
@@ -103,31 +107,267 @@ impl MessageEditor {
editor
});
+ cx.on_focus(&editor.focus_handle(cx), window, |_, _, cx| {
+ cx.emit(MessageEditorEvent::Focus)
+ })
+ .detach();
+
Self {
editor,
project,
mention_set,
thread_store,
text_thread_store,
+ workspace,
}
}
+ #[cfg(test)]
+ pub(crate) fn editor(&self) -> &Entity<Editor> {
+ &self.editor
+ }
+
+ #[cfg(test)]
+ pub(crate) fn mention_set(&mut self) -> &mut MentionSet {
+ &mut self.mention_set
+ }
+
pub fn is_empty(&self, cx: &App) -> bool {
self.editor.read(cx).is_empty(cx)
}
+ pub fn mentioned_path_and_threads(&self) -> (HashSet<PathBuf>, HashSet<ThreadId>) {
+ let mut excluded_paths = HashSet::default();
+ let mut excluded_threads = HashSet::default();
+
+ for uri in self.mention_set.uri_by_crease_id.values() {
+ match uri {
+ MentionUri::File { abs_path, .. } => {
+ excluded_paths.insert(abs_path.clone());
+ }
+ MentionUri::Thread { id, .. } => {
+ excluded_threads.insert(id.clone());
+ }
+ _ => {}
+ }
+ }
+
+ (excluded_paths, excluded_threads)
+ }
+
+ pub fn confirm_completion(
+ &mut self,
+ crease_text: SharedString,
+ start: text::Anchor,
+ content_len: usize,
+ mention_uri: MentionUri,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let snapshot = self
+ .editor
+ .update(cx, |editor, cx| editor.snapshot(window, cx));
+ let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot.as_singleton() else {
+ return;
+ };
+ let Some(anchor) = snapshot
+ .buffer_snapshot
+ .anchor_in_excerpt(*excerpt_id, start)
+ else {
+ return;
+ };
+
+ let Some(crease_id) = crate::context_picker::insert_crease_for_mention(
+ *excerpt_id,
+ start,
+ content_len,
+ crease_text.clone(),
+ mention_uri.icon_path(cx),
+ self.editor.clone(),
+ window,
+ cx,
+ ) else {
+ return;
+ };
+
+ match mention_uri {
+ MentionUri::Fetch { url } => {
+ self.confirm_mention_for_fetch(crease_id, anchor, url, window, cx);
+ }
+ MentionUri::File {
+ abs_path,
+ is_directory,
+ } => {
+ self.confirm_mention_for_file(
+ crease_id,
+ anchor,
+ abs_path,
+ is_directory,
+ window,
+ cx,
+ );
+ }
+ MentionUri::Thread { id, name } => {
+ self.confirm_mention_for_thread(crease_id, anchor, id, name, window, cx);
+ }
+ MentionUri::TextThread { path, name } => {
+ self.confirm_mention_for_text_thread(crease_id, anchor, path, name, window, cx);
+ }
+ MentionUri::Symbol { .. } | MentionUri::Rule { .. } | MentionUri::Selection { .. } => {
+ self.mention_set.insert_uri(crease_id, mention_uri.clone());
+ }
+ }
+ }
+
+ fn confirm_mention_for_file(
+ &mut self,
+ crease_id: CreaseId,
+ anchor: Anchor,
+ abs_path: PathBuf,
+ is_directory: bool,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let extension = abs_path
+ .extension()
+ .and_then(OsStr::to_str)
+ .unwrap_or_default();
+
+ if Img::extensions().contains(&extension) && !extension.contains("svg") {
+ let project = self.project.clone();
+ let Some(project_path) = project
+ .read(cx)
+ .project_path_for_absolute_path(&abs_path, cx)
+ else {
+ return;
+ };
+ let image = cx.spawn(async move |_, cx| {
+ let image = project
+ .update(cx, |project, cx| project.open_image(project_path, cx))?
+ .await?;
+ image.read_with(cx, |image, _cx| image.image.clone())
+ });
+ self.confirm_mention_for_image(crease_id, anchor, Some(abs_path), image, window, cx);
+ } else {
+ self.mention_set.insert_uri(
+ crease_id,
+ MentionUri::File {
+ abs_path,
+ is_directory,
+ },
+ );
+ }
+ }
+
+ fn confirm_mention_for_fetch(
+ &mut self,
+ crease_id: CreaseId,
+ anchor: Anchor,
+ url: url::Url,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let Some(http_client) = self
+ .workspace
+ .update(cx, |workspace, _cx| workspace.client().http_client())
+ .ok()
+ else {
+ return;
+ };
+
+ let url_string = url.to_string();
+ let fetch = cx
+ .background_executor()
+ .spawn(async move {
+ fetch_url_content(http_client, url_string)
+ .map_err(|e| e.to_string())
+ .await
+ })
+ .shared();
+ self.mention_set
+ .add_fetch_result(url.clone(), fetch.clone());
+
+ cx.spawn_in(window, async move |this, cx| {
+ let fetch = fetch.await.notify_async_err(cx);
+ this.update(cx, |this, cx| {
+ let mention_uri = MentionUri::Fetch { url };
+ if fetch.is_some() {
+ this.mention_set.insert_uri(crease_id, mention_uri.clone());
+ } else {
+ // Remove crease if we failed to fetch
+ this.editor.update(cx, |editor, cx| {
+ editor.display_map.update(cx, |display_map, cx| {
+ display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
+ });
+ editor.remove_creases([crease_id], cx);
+ });
+ }
+ })
+ .ok();
+ })
+ .detach();
+ }
+
+ pub fn confirm_mention_for_selection(
+ &mut self,
+ source_range: Range<text::Anchor>,
+ selections: Vec<(Entity<Buffer>, Range<text::Anchor>, Range<usize>)>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx);
+ let Some((&excerpt_id, _, _)) = snapshot.as_singleton() else {
+ return;
+ };
+ let Some(start) = snapshot.anchor_in_excerpt(excerpt_id, source_range.start) else {
+ return;
+ };
+
+ let offset = start.to_offset(&snapshot);
+
+ for (buffer, selection_range, range_to_fold) in selections {
+ let range = snapshot.anchor_after(offset + range_to_fold.start)
+ ..snapshot.anchor_after(offset + range_to_fold.end);
+
+ let path = buffer
+ .read(cx)
+ .file()
+ .map_or(PathBuf::from("untitled"), |file| file.path().to_path_buf());
+ let snapshot = buffer.read(cx).snapshot();
+
+ let point_range = selection_range.to_point(&snapshot);
+ let line_range = point_range.start.row..point_range.end.row;
+
+ let uri = MentionUri::Selection {
+ path: path.clone(),
+ line_range: line_range.clone(),
+ };
+ let crease = crate::context_picker::crease_for_mention(
+ selection_name(&path, &line_range).into(),
+ uri.icon_path(cx),
+ range,
+ self.editor.downgrade(),
+ );
+
+ let crease_id = self.editor.update(cx, |editor, cx| {
+ let crease_ids = editor.insert_creases(vec![crease.clone()], cx);
+ editor.fold_creases(vec![crease], false, window, cx);
+ crease_ids.first().copied().unwrap()
+ });
+
+ self.mention_set
+ .insert_uri(crease_id, MentionUri::Selection { path, line_range });
+ }
+ }
+
pub fn contents(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<Vec<acp::ContentBlock>>> {
- let contents = self.mention_set.lock().contents(
- self.project.clone(),
- self.thread_store.clone(),
- self.text_thread_store.clone(),
- window,
- cx,
- );
+ let contents =
+ self.mention_set
+ .contents(self.project.clone(), self.thread_store.clone(), window, cx);
let editor = self.editor.clone();
cx.spawn(async move |_, cx| {
@@ -198,15 +438,15 @@ impl MessageEditor {
pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.editor.update(cx, |editor, cx| {
editor.clear(window, cx);
- editor.remove_creases(self.mention_set.lock().drain(), cx)
+ editor.remove_creases(self.mention_set.drain(), cx)
});
}
- fn chat(&mut self, _: &Chat, _: &mut Window, cx: &mut Context<Self>) {
+ fn send(&mut self, _: &Chat, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(MessageEditorEvent::Send)
}
- fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
+ fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(MessageEditorEvent::Cancel)
}
@@ -233,27 +473,46 @@ impl MessageEditor {
let replacement_text = "image";
for image in images {
- let (excerpt_id, anchor) = self.editor.update(cx, |message_editor, cx| {
- let snapshot = message_editor.snapshot(window, cx);
- let (excerpt_id, _, snapshot) = snapshot.buffer_snapshot.as_singleton().unwrap();
+ let (excerpt_id, text_anchor, multibuffer_anchor) =
+ self.editor.update(cx, |message_editor, cx| {
+ let snapshot = message_editor.snapshot(window, cx);
+ let (excerpt_id, _, buffer_snapshot) =
+ snapshot.buffer_snapshot.as_singleton().unwrap();
- let anchor = snapshot.anchor_before(snapshot.len());
- message_editor.edit(
- [(
- multi_buffer::Anchor::max()..multi_buffer::Anchor::max(),
- format!("{replacement_text} "),
- )],
- cx,
- );
- (*excerpt_id, anchor)
- });
+ let text_anchor = buffer_snapshot.anchor_before(buffer_snapshot.len());
+ let multibuffer_anchor = snapshot
+ .buffer_snapshot
+ .anchor_in_excerpt(*excerpt_id, text_anchor);
+ message_editor.edit(
+ [(
+ multi_buffer::Anchor::max()..multi_buffer::Anchor::max(),
+ format!("{replacement_text} "),
+ )],
+ cx,
+ );
+ (*excerpt_id, text_anchor, multibuffer_anchor)
+ });
- self.insert_image(
+ let content_len = replacement_text.len();
+ let Some(anchor) = multibuffer_anchor else {
+ return;
+ };
+ let Some(crease_id) = insert_crease_for_image(
excerpt_id,
+ text_anchor,
+ content_len,
+ None.clone(),
+ self.editor.clone(),
+ window,
+ cx,
+ ) else {
+ return;
+ };
+ self.confirm_mention_for_image(
+ crease_id,
anchor,
- replacement_text.len(),
- Arc::new(image),
None,
+ Task::ready(Ok(Arc::new(image))),
window,
cx,
);
@@ -267,9 +526,6 @@ impl MessageEditor {
cx: &mut Context<Self>,
) {
let buffer = self.editor.read(cx).buffer().clone();
- let Some((&excerpt_id, _, _)) = buffer.read(cx).snapshot(cx).as_singleton() else {
- return;
- };
let Some(buffer) = buffer.read(cx).as_singleton() else {
return;
};
@@ -292,10 +548,8 @@ impl MessageEditor {
&path_prefix,
false,
entry.is_dir(),
- excerpt_id,
anchor..anchor,
- self.editor.clone(),
- self.mention_set.clone(),
+ cx.weak_entity(),
self.project.clone(),
cx,
) else {
@@ -317,66 +571,172 @@ impl MessageEditor {
}
}
- fn insert_image(
+ pub fn set_read_only(&mut self, read_only: bool, cx: &mut Context<Self>) {
+ self.editor.update(cx, |message_editor, cx| {
+ message_editor.set_read_only(read_only);
+ cx.notify()
+ })
+ }
+
+ fn confirm_mention_for_image(
&mut self,
- excerpt_id: ExcerptId,
- crease_start: text::Anchor,
- content_len: usize,
- image: Arc<Image>,
- abs_path: Option<Arc<Path>>,
+ crease_id: CreaseId,
+ anchor: Anchor,
+ abs_path: Option<PathBuf>,
+ image: Task<Result<Arc<Image>>>,
window: &mut Window,
cx: &mut Context<Self>,
) {
- let Some(crease_id) = insert_crease_for_image(
- excerpt_id,
- crease_start,
- content_len,
- self.editor.clone(),
- window,
- cx,
- ) else {
- return;
- };
- self.editor.update(cx, |_editor, cx| {
- let format = image.format;
- let convert = LanguageModelImage::from_image(image, cx);
-
- let task = cx
- .spawn_in(window, async move |editor, cx| {
- if let Some(image) = convert.await {
+ let editor = self.editor.clone();
+ let task = cx
+ .spawn_in(window, {
+ let abs_path = abs_path.clone();
+ async move |_, cx| {
+ let image = image.await.map_err(|e| e.to_string())?;
+ let format = image.format;
+ let image = cx
+ .update(|_, cx| LanguageModelImage::from_image(image, cx))
+ .map_err(|e| e.to_string())?
+ .await;
+ if let Some(image) = image {
Ok(MentionImage {
abs_path,
data: image.source,
format,
})
} else {
- editor
- .update(cx, |editor, cx| {
- let snapshot = editor.buffer().read(cx).snapshot(cx);
- let Some(anchor) =
- snapshot.anchor_in_excerpt(excerpt_id, crease_start)
- else {
- return;
- };
- editor.display_map.update(cx, |display_map, cx| {
- display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
- });
- editor.remove_creases([crease_id], cx);
- })
- .ok();
- Err("Failed to convert image".to_string())
+ Err("Failed to convert image".into())
}
- })
- .shared();
+ }
+ })
+ .shared();
+
+ self.mention_set.insert_image(crease_id, task.clone());
+
+ cx.spawn_in(window, async move |this, cx| {
+ if task.await.notify_async_err(cx).is_some() {
+ if let Some(abs_path) = abs_path.clone() {
+ this.update(cx, |this, _cx| {
+ this.mention_set.insert_uri(
+ crease_id,
+ MentionUri::File {
+ abs_path,
+ is_directory: false,
+ },
+ );
+ })
+ .ok();
+ }
+ } else {
+ editor
+ .update(cx, |editor, cx| {
+ editor.display_map.update(cx, |display_map, cx| {
+ display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
+ });
+ editor.remove_creases([crease_id], cx);
+ })
+ .ok();
+ }
+ })
+ .detach();
+ }
- cx.spawn_in(window, {
- let task = task.clone();
- async move |_, cx| task.clone().await.notify_async_err(cx)
+ fn confirm_mention_for_thread(
+ &mut self,
+ crease_id: CreaseId,
+ anchor: Anchor,
+ id: ThreadId,
+ name: String,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let uri = MentionUri::Thread {
+ id: id.clone(),
+ name,
+ };
+ let open_task = self.thread_store.update(cx, |thread_store, cx| {
+ thread_store.open_thread(&id, window, cx)
+ });
+ let task = cx
+ .spawn(async move |_, cx| {
+ let thread = open_task.await.map_err(|e| e.to_string())?;
+ let content = thread
+ .read_with(cx, |thread, _cx| thread.latest_detailed_summary_or_text())
+ .map_err(|e| e.to_string())?;
+ Ok(content)
})
- .detach();
+ .shared();
+
+ self.mention_set.insert_thread(id, task.clone());
+
+ let editor = self.editor.clone();
+ cx.spawn_in(window, async move |this, cx| {
+ if task.await.notify_async_err(cx).is_some() {
+ this.update(cx, |this, _| {
+ this.mention_set.insert_uri(crease_id, uri);
+ })
+ .ok();
+ } else {
+ editor
+ .update(cx, |editor, cx| {
+ editor.display_map.update(cx, |display_map, cx| {
+ display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
+ });
+ editor.remove_creases([crease_id], cx);
+ })
+ .ok();
+ }
+ })
+ .detach();
+ }
- self.mention_set.lock().insert_image(crease_id, task);
+ fn confirm_mention_for_text_thread(
+ &mut self,
+ crease_id: CreaseId,
+ anchor: Anchor,
+ path: PathBuf,
+ name: String,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let uri = MentionUri::TextThread {
+ path: path.clone(),
+ name,
+ };
+ let context = self.text_thread_store.update(cx, |text_thread_store, cx| {
+ text_thread_store.open_local_context(path.as_path().into(), cx)
});
+ let task = cx
+ .spawn(async move |_, cx| {
+ let context = context.await.map_err(|e| e.to_string())?;
+ let xml = context
+ .update(cx, |context, cx| context.to_xml(cx))
+ .map_err(|e| e.to_string())?;
+ Ok(xml)
+ })
+ .shared();
+
+ self.mention_set.insert_text_thread(path, task.clone());
+
+ let editor = self.editor.clone();
+ cx.spawn_in(window, async move |this, cx| {
+ if task.await.notify_async_err(cx).is_some() {
+ this.update(cx, |this, _| {
+ this.mention_set.insert_uri(crease_id, uri);
+ })
+ .ok();
+ } else {
+ editor
+ .update(cx, |editor, cx| {
+ editor.display_map.update(cx, |display_map, cx| {
+ display_map.unfold_intersecting(vec![anchor..anchor], true, cx);
+ });
+ editor.remove_creases([crease_id], cx);
+ })
+ .ok();
+ }
+ })
+ .detach();
}
pub fn set_mode(&mut self, mode: EditorMode, cx: &mut Context<Self>) {
@@ -392,6 +752,8 @@ impl MessageEditor {
window: &mut Window,
cx: &mut Context<Self>,
) {
+ self.clear(window, cx);
+
let mut text = String::new();
let mut mentions = Vec::new();
let mut images = Vec::new();
@@ -409,7 +771,7 @@ impl MessageEditor {
let start = text.len();
write!(&mut text, "{}", mention_uri.as_link()).ok();
let end = text.len();
- mentions.push((start..end, mention_uri));
+ mentions.push((start..end, mention_uri, resource.text));
}
}
acp::ContentBlock::Image(content) => {
@@ -429,8 +791,7 @@ impl MessageEditor {
editor.buffer().read(cx).snapshot(cx)
});
- self.mention_set.lock().clear();
- for (range, mention_uri) in mentions {
+ for (range, mention_uri, text) in mentions {
let anchor = snapshot.anchor_before(range.start);
let crease_id = crate::context_picker::insert_crease_for_mention(
anchor.excerpt_id,
@@ -444,7 +805,26 @@ impl MessageEditor {
);
if let Some(crease_id) = crease_id {
- self.mention_set.lock().insert_uri(crease_id, mention_uri);
+ self.mention_set.insert_uri(crease_id, mention_uri.clone());
+ }
+
+ match mention_uri {
+ MentionUri::Thread { id, .. } => {
+ self.mention_set
+ .insert_thread(id, Task::ready(Ok(text.into())).shared());
+ }
+ MentionUri::TextThread { path, .. } => {
+ self.mention_set
+ .insert_text_thread(path, Task::ready(Ok(text)).shared());
+ }
+ MentionUri::Fetch { url } => {
+ self.mention_set
+ .add_fetch_result(url, Task::ready(Ok(text)).shared());
+ }
+ MentionUri::File { .. }
+ | MentionUri::Symbol { .. }
+ | MentionUri::Rule { .. }
+ | MentionUri::Selection { .. } => {}
}
}
for (range, content) in images {
@@ -479,7 +859,7 @@ impl MessageEditor {
let data: SharedString = content.data.to_string().into();
if let Some(crease_id) = crease_id {
- self.mention_set.lock().insert_image(
+ self.mention_set.insert_image(
crease_id,
Task::ready(Ok(MentionImage {
abs_path,
@@ -499,6 +879,11 @@ impl MessageEditor {
editor.set_text(text, window, cx);
});
}
+
+ #[cfg(test)]
+ pub fn text(&self, cx: &App) -> String {
+ self.editor.read(cx).text(cx)
+ }
}
impl Focusable for MessageEditor {
@@ -511,7 +896,7 @@ impl Render for MessageEditor {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.key_context("MessageEditor")
- .on_action(cx.listener(Self::chat))
+ .on_action(cx.listener(Self::send))
.on_action(cx.listener(Self::cancel))
.capture_action(cx.listener(Self::paste))
.flex_1()
@@ -550,38 +935,325 @@ pub(crate) fn insert_crease_for_image(
excerpt_id: ExcerptId,
anchor: text::Anchor,
content_len: usize,
+ abs_path: Option<Arc<Path>>,
editor: Entity<Editor>,
window: &mut Window,
cx: &mut App,
) -> Option<CreaseId> {
- crate::context_picker::insert_crease_for_mention(
- excerpt_id,
- anchor,
- content_len,
- "Image".into(),
- IconName::Image.path().into(),
- editor,
- window,
- cx,
- )
+ let crease_label = abs_path
+ .as_ref()
+ .and_then(|path| path.file_name())
+ .map(|name| name.to_string_lossy().to_string().into())
+ .unwrap_or(SharedString::from("Image"));
+
+ editor.update(cx, |editor, cx| {
+ let snapshot = editor.buffer().read(cx).snapshot(cx);
+
+ let start = snapshot.anchor_in_excerpt(excerpt_id, anchor)?;
+
+ let start = start.bias_right(&snapshot);
+ let end = snapshot.anchor_before(start.to_offset(&snapshot) + content_len);
+
+ let placeholder = FoldPlaceholder {
+ render: render_image_fold_icon_button(crease_label, cx.weak_entity()),
+ merge_adjacent: false,
+ ..Default::default()
+ };
+
+ let crease = Crease::Inline {
+ range: start..end,
+ placeholder,
+ render_toggle: None,
+ render_trailer: None,
+ metadata: None,
+ };
+
+ let ids = editor.insert_creases(vec![crease.clone()], cx);
+ editor.fold_creases(vec![crease], false, window, cx);
+
+ Some(ids[0])
+ })
+}
+
+fn render_image_fold_icon_button(
+ label: SharedString,
+ editor: WeakEntity<Editor>,
+) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
+ Arc::new({
+ move |fold_id, fold_range, cx| {
+ let is_in_text_selection = editor
+ .update(cx, |editor, cx| editor.is_range_selected(&fold_range, cx))
+ .unwrap_or_default();
+
+ ButtonLike::new(fold_id)
+ .style(ButtonStyle::Filled)
+ .selected_style(ButtonStyle::Tinted(TintColor::Accent))
+ .toggle_state(is_in_text_selection)
+ .child(
+ h_flex()
+ .gap_1()
+ .child(
+ Icon::new(IconName::Image)
+ .size(IconSize::XSmall)
+ .color(Color::Muted),
+ )
+ .child(
+ Label::new(label.clone())
+ .size(LabelSize::Small)
+ .buffer_font(cx)
+ .single_line(),
+ ),
+ )
+ .into_any_element()
+ }
+ })
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub enum Mention {
+ Text { uri: MentionUri, content: String },
+ Image(MentionImage),
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct MentionImage {
+ pub abs_path: Option<PathBuf>,
+ pub data: SharedString,
+ pub format: ImageFormat,
+}
+
+#[derive(Default)]
+pub struct MentionSet {
+ uri_by_crease_id: HashMap<CreaseId, MentionUri>,
+ fetch_results: HashMap<Url, Shared<Task<Result<String, String>>>>,
+ images: HashMap<CreaseId, Shared<Task<Result<MentionImage, String>>>>,
+ thread_summaries: HashMap<ThreadId, Shared<Task<Result<SharedString, String>>>>,
+ text_thread_summaries: HashMap<PathBuf, Shared<Task<Result<String, String>>>>,
+}
+
+impl MentionSet {
+ pub fn insert_uri(&mut self, crease_id: CreaseId, uri: MentionUri) {
+ self.uri_by_crease_id.insert(crease_id, uri);
+ }
+
+ pub fn add_fetch_result(&mut self, url: Url, content: Shared<Task<Result<String, String>>>) {
+ self.fetch_results.insert(url, content);
+ }
+
+ pub fn insert_image(
+ &mut self,
+ crease_id: CreaseId,
+ task: Shared<Task<Result<MentionImage, String>>>,
+ ) {
+ self.images.insert(crease_id, task);
+ }
+
+ fn insert_thread(&mut self, id: ThreadId, task: Shared<Task<Result<SharedString, String>>>) {
+ self.thread_summaries.insert(id, task);
+ }
+
+ fn insert_text_thread(&mut self, path: PathBuf, task: Shared<Task<Result<String, String>>>) {
+ self.text_thread_summaries.insert(path, task);
+ }
+
+ pub fn drain(&mut self) -> impl Iterator<Item = CreaseId> {
+ self.fetch_results.clear();
+ self.thread_summaries.clear();
+ self.text_thread_summaries.clear();
+ self.uri_by_crease_id
+ .drain()
+ .map(|(id, _)| id)
+ .chain(self.images.drain().map(|(id, _)| id))
+ }
+
+ pub fn contents(
+ &self,
+ project: Entity<Project>,
+ thread_store: Entity<ThreadStore>,
+ _window: &mut Window,
+ cx: &mut App,
+ ) -> Task<Result<HashMap<CreaseId, Mention>>> {
+ let mut processed_image_creases = HashSet::default();
+
+ let mut contents = self
+ .uri_by_crease_id
+ .iter()
+ .map(|(&crease_id, uri)| {
+ match uri {
+ MentionUri::File { abs_path, .. } => {
+ // TODO directories
+ let uri = uri.clone();
+ let abs_path = abs_path.to_path_buf();
+
+ if let Some(task) = self.images.get(&crease_id).cloned() {
+ processed_image_creases.insert(crease_id);
+ return cx.spawn(async move |_| {
+ let image = task.await.map_err(|e| anyhow!("{e}"))?;
+ anyhow::Ok((crease_id, Mention::Image(image)))
+ });
+ }
+
+ let buffer_task = project.update(cx, |project, cx| {
+ let path = project
+ .find_project_path(abs_path, cx)
+ .context("Failed to find project path")?;
+ anyhow::Ok(project.open_buffer(path, cx))
+ });
+ cx.spawn(async move |cx| {
+ let buffer = buffer_task?.await?;
+ let content = buffer.read_with(cx, |buffer, _cx| buffer.text())?;
+
+ anyhow::Ok((crease_id, Mention::Text { uri, content }))
+ })
+ }
+ MentionUri::Symbol {
+ path, line_range, ..
+ }
+ | MentionUri::Selection {
+ path, line_range, ..
+ } => {
+ let uri = uri.clone();
+ let path_buf = path.clone();
+ let line_range = line_range.clone();
+
+ let buffer_task = project.update(cx, |project, cx| {
+ let path = project
+ .find_project_path(&path_buf, cx)
+ .context("Failed to find project path")?;
+ anyhow::Ok(project.open_buffer(path, cx))
+ });
+
+ cx.spawn(async move |cx| {
+ let buffer = buffer_task?.await?;
+ let content = buffer.read_with(cx, |buffer, _cx| {
+ buffer
+ .text_for_range(
+ Point::new(line_range.start, 0)
+ ..Point::new(
+ line_range.end,
+ buffer.line_len(line_range.end),
+ ),
+ )
+ .collect()
+ })?;
+
+ anyhow::Ok((crease_id, Mention::Text { uri, content }))
+ })
+ }
+ MentionUri::Thread { id, .. } => {
+ let Some(content) = self.thread_summaries.get(id).cloned() else {
+ return Task::ready(Err(anyhow!("missing thread summary")));
+ };
+ let uri = uri.clone();
+ cx.spawn(async move |_| {
+ Ok((
+ crease_id,
+ Mention::Text {
+ uri,
+ content: content
+ .await
+ .map_err(|e| anyhow::anyhow!("{e}"))?
+ .to_string(),
+ },
+ ))
+ })
+ }
+ MentionUri::TextThread { path, .. } => {
+ let Some(content) = self.text_thread_summaries.get(path).cloned() else {
+ return Task::ready(Err(anyhow!("missing text thread summary")));
+ };
+ let uri = uri.clone();
+ cx.spawn(async move |_| {
+ Ok((
+ crease_id,
+ Mention::Text {
+ uri,
+ content: content
+ .await
+ .map_err(|e| anyhow::anyhow!("{e}"))?
+ .to_string(),
+ },
+ ))
+ })
+ }
+ MentionUri::Rule { id: prompt_id, .. } => {
+ let Some(prompt_store) = thread_store.read(cx).prompt_store().clone()
+ else {
+ return Task::ready(Err(anyhow!("missing prompt store")));
+ };
+ let text_task = prompt_store.read(cx).load(*prompt_id, cx);
+ let uri = uri.clone();
+ cx.spawn(async move |_| {
+ // TODO: report load errors instead of just logging
+ let text = text_task.await?;
+ anyhow::Ok((crease_id, Mention::Text { uri, content: text }))
+ })
+ }
+ MentionUri::Fetch { url } => {
+ let Some(content) = self.fetch_results.get(&url).cloned() else {
+ return Task::ready(Err(anyhow!("missing fetch result")));
+ };
+ let uri = uri.clone();
+ cx.spawn(async move |_| {
+ Ok((
+ crease_id,
+ Mention::Text {
+ uri,
+ content: content.await.map_err(|e| anyhow::anyhow!("{e}"))?,
+ },
+ ))
+ })
+ }
+ }
+ })
+ .collect::<Vec<_>>();
+
+ // Handle images that didn't have a mention URI (because they were added by the paste handler).
+ contents.extend(self.images.iter().filter_map(|(crease_id, image)| {
+ if processed_image_creases.contains(crease_id) {
+ return None;
+ }
+ let crease_id = *crease_id;
+ let image = image.clone();
+ Some(cx.spawn(async move |_| {
+ Ok((
+ crease_id,
+ Mention::Image(image.await.map_err(|e| anyhow::anyhow!("{e}"))?),
+ ))
+ }))
+ }));
+
+ cx.spawn(async move |_cx| {
+ let contents = try_join_all(contents).await?.into_iter().collect();
+ anyhow::Ok(contents)
+ })
+ }
}
#[cfg(test)]
mod tests {
- use std::path::Path;
+ use std::{ops::Range, path::Path, sync::Arc};
use agent::{TextThreadStore, ThreadStore};
use agent_client_protocol as acp;
- use editor::EditorMode;
+ use editor::{AnchorRangeExt as _, Editor, EditorMode};
use fs::FakeFs;
- use gpui::{AppContext, TestAppContext};
+ use futures::StreamExt as _;
+ use gpui::{
+ AppContext, Entity, EventEmitter, FocusHandle, Focusable, TestAppContext, VisualTestContext,
+ };
use lsp::{CompletionContext, CompletionTriggerKind};
- use project::{CompletionIntent, Project};
+ use project::{CompletionIntent, Project, ProjectPath};
use serde_json::json;
+ use text::Point;
+ use ui::{App, Context, IntoElement, Render, SharedString, Window};
use util::path;
- use workspace::Workspace;
+ use workspace::{AppState, Item, Workspace};
- use crate::acp::{message_editor::MessageEditor, thread_view::tests::init_test};
+ use crate::acp::{
+ message_editor::{Mention, MessageEditor},
+ thread_view::tests::init_test,
+ };
#[gpui::test]
async fn test_at_mention_removal(cx: &mut TestAppContext) {
@@ -8,7 +8,7 @@ use action_log::ActionLog;
use agent::{TextThreadStore, ThreadStore};
use agent_client_protocol::{self as acp};
use agent_servers::AgentServer;
-use agent_settings::{AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
+use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
use anyhow::bail;
use audio::{Audio, Sound};
use buffer_diff::BufferDiff;
@@ -17,6 +17,7 @@ use collections::{HashMap, HashSet};
use editor::scroll::Autoscroll;
use editor::{Editor, EditorMode, MultiBuffer, PathKey, SelectionEffects};
use file_icons::FileIcons;
+use fs::Fs;
use gpui::{
Action, Animation, AnimationExt, App, BorderStyle, ClickEvent, ClipboardItem, EdgesRefinement,
Empty, Entity, FocusHandle, Focusable, Hsla, Length, ListOffset, ListState, MouseButton,
@@ -30,6 +31,7 @@ use project::Project;
use prompt_store::PromptId;
use rope::Point;
use settings::{Settings as _, SettingsStore};
+use std::sync::Arc;
use std::{collections::BTreeMap, process::ExitStatus, rc::Rc, time::Duration};
use text::Anchor;
use theme::ThemeSettings;
@@ -44,12 +46,14 @@ use zed_actions::assistant::OpenRulesLibrary;
use super::entry_view_state::EntryViewState;
use crate::acp::AcpModelSelectorPopover;
+use crate::acp::entry_view_state::{EntryViewEvent, ViewEvent};
use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
use crate::agent_diff::AgentDiff;
+use crate::profile_selector::{ProfileProvider, ProfileSelector};
use crate::ui::{AgentNotification, AgentNotificationEvent, BurnModeTooltip};
use crate::{
AgentDiffPane, AgentPanel, ContinueThread, ContinueWithBurnMode, ExpandMessageEditor, Follow,
- KeepAll, OpenAgentDiff, RejectAll, ToggleBurnMode,
+ KeepAll, OpenAgentDiff, RejectAll, ToggleBurnMode, ToggleProfileSelector,
};
const RESPONSE_PADDING_X: Pixels = px(19.);
@@ -79,16 +83,31 @@ impl ThreadError {
}
}
+impl ProfileProvider for Entity<agent2::Thread> {
+ fn profile_id(&self, cx: &App) -> AgentProfileId {
+ self.read(cx).profile().clone()
+ }
+
+ fn set_profile(&self, profile_id: AgentProfileId, cx: &mut App) {
+ self.update(cx, |thread, _cx| {
+ thread.set_profile(profile_id);
+ });
+ }
+
+ fn profiles_supported(&self, cx: &App) -> bool {
+ self.read(cx).model().supports_tools()
+ }
+}
+
pub struct AcpThreadView {
agent: Rc<dyn AgentServer>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
- thread_store: Entity<ThreadStore>,
- text_thread_store: Entity<TextThreadStore>,
thread_state: ThreadState,
- entry_view_state: EntryViewState,
+ entry_view_state: Entity<EntryViewState>,
message_editor: Entity<MessageEditor>,
model_selector: Option<Entity<AcpModelSelectorPopover>>,
+ profile_selector: Option<Entity<ProfileSelector>>,
notifications: Vec<WindowHandle<AgentNotification>>,
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
thread_error: Option<ThreadError>,
@@ -101,16 +120,9 @@ pub struct AcpThreadView {
plan_expanded: bool,
editor_expanded: bool,
terminal_expanded: bool,
- editing_message: Option<EditingMessage>,
+ editing_message: Option<usize>,
_cancel_task: Option<Task<()>>,
- _subscriptions: [Subscription; 2],
-}
-
-struct EditingMessage {
- index: usize,
- message_id: UserMessageId,
- editor: Entity<MessageEditor>,
- _subscription: Subscription,
+ _subscriptions: [Subscription; 3],
}
enum ThreadState {
@@ -158,17 +170,26 @@ impl AcpThreadView {
let list_state = ListState::new(0, gpui::ListAlignment::Bottom, px(2048.0));
+ let entry_view_state = cx.new(|_| {
+ EntryViewState::new(
+ workspace.clone(),
+ project.clone(),
+ thread_store.clone(),
+ text_thread_store.clone(),
+ )
+ });
+
let subscriptions = [
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
- cx.subscribe_in(&message_editor, window, Self::on_message_editor_event),
+ cx.subscribe_in(&message_editor, window, Self::handle_message_editor_event),
+ cx.subscribe_in(&entry_view_state, window, Self::handle_entry_view_event),
];
Self {
agent: agent.clone(),
workspace: workspace.clone(),
project: project.clone(),
- thread_store,
- text_thread_store,
+ entry_view_state,
thread_state: Self::initial_state(
agent,
restore_thread,
@@ -179,9 +200,9 @@ impl AcpThreadView {
),
message_editor,
model_selector: None,
+ profile_selector: None,
notifications: Vec::new(),
notification_subscriptions: HashMap::default(),
- entry_view_state: EntryViewState::default(),
list_state: list_state.clone(),
scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
thread_error: None,
@@ -315,6 +336,17 @@ impl AcpThreadView {
_subscription: [thread_subscription, action_log_subscription],
};
+ this.profile_selector = this.as_native_thread(cx).map(|thread| {
+ cx.new(|cx| {
+ ProfileSelector::new(
+ <dyn Fs>::global(cx),
+ Arc::new(thread.clone()),
+ this.focus_handle(cx),
+ cx,
+ )
+ })
+ });
+
cx.notify();
}
Err(err) => {
@@ -400,7 +432,7 @@ impl AcpThreadView {
cx.notify();
}
- pub fn on_message_editor_event(
+ pub fn handle_message_editor_event(
&mut self,
_: &Entity<MessageEditor>,
event: &MessageEditorEvent,
@@ -410,6 +442,28 @@ impl AcpThreadView {
match event {
MessageEditorEvent::Send => self.send(window, cx),
MessageEditorEvent::Cancel => self.cancel_generation(cx),
+ MessageEditorEvent::Focus => {}
+ }
+ }
+
+ pub fn handle_entry_view_event(
+ &mut self,
+ _: &Entity<EntryViewState>,
+ event: &EntryViewEvent,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ match &event.view_event {
+ ViewEvent::MessageEditorEvent(_editor, MessageEditorEvent::Focus) => {
+ self.editing_message = Some(event.entry_index);
+ cx.notify();
+ }
+ ViewEvent::MessageEditorEvent(editor, MessageEditorEvent::Send) => {
+ self.regenerate(event.entry_index, editor, window, cx);
+ }
+ ViewEvent::MessageEditorEvent(_editor, MessageEditorEvent::Cancel) => {
+ self.cancel_editing(&Default::default(), window, cx);
+ }
}
}
@@ -480,27 +534,56 @@ impl AcpThreadView {
.detach();
}
- fn cancel_editing(&mut self, _: &ClickEvent, _window: &mut Window, cx: &mut Context<Self>) {
- self.editing_message.take();
+ fn cancel_editing(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
+ let Some(thread) = self.thread().cloned() else {
+ return;
+ };
+
+ if let Some(index) = self.editing_message.take() {
+ if let Some(editor) = self
+ .entry_view_state
+ .read(cx)
+ .entry(index)
+ .and_then(|e| e.message_editor())
+ .cloned()
+ {
+ editor.update(cx, |editor, cx| {
+ if let Some(user_message) = thread
+ .read(cx)
+ .entries()
+ .get(index)
+ .and_then(|e| e.user_message())
+ {
+ editor.set_message(user_message.chunks.clone(), window, cx);
+ }
+ })
+ }
+ };
+ self.focus_handle(cx).focus(window);
cx.notify();
}
- fn regenerate(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
- let Some(editing_message) = self.editing_message.take() else {
+ fn regenerate(
+ &mut self,
+ entry_ix: usize,
+ message_editor: &Entity<MessageEditor>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let Some(thread) = self.thread().cloned() else {
return;
};
- let Some(thread) = self.thread().cloned() else {
+ let Some(rewind) = thread.update(cx, |thread, cx| {
+ let user_message_id = thread.entries().get(entry_ix)?.user_message()?.id.clone()?;
+ Some(thread.rewind(user_message_id, cx))
+ }) else {
return;
};
- let rewind = thread.update(cx, |thread, cx| {
- thread.rewind(editing_message.message_id, cx)
- });
+ let contents =
+ message_editor.update(cx, |message_editor, cx| message_editor.contents(window, cx));
- let contents = editing_message
- .editor
- .update(cx, |message_editor, cx| message_editor.contents(window, cx));
let task = cx.foreground_executor().spawn(async move {
rewind.await?;
contents.await
@@ -556,27 +639,20 @@ impl AcpThreadView {
AcpThreadEvent::NewEntry => {
let len = thread.read(cx).entries().len();
let index = len - 1;
- self.entry_view_state.sync_entry(
- self.workspace.clone(),
- thread.clone(),
- index,
- window,
- cx,
- );
+ self.entry_view_state.update(cx, |view_state, cx| {
+ view_state.sync_entry(index, &thread, window, cx)
+ });
self.list_state.splice(index..index, 1);
}
AcpThreadEvent::EntryUpdated(index) => {
- self.entry_view_state.sync_entry(
- self.workspace.clone(),
- thread.clone(),
- *index,
- window,
- cx,
- );
+ self.entry_view_state.update(cx, |view_state, cx| {
+ view_state.sync_entry(*index, &thread, window, cx)
+ });
self.list_state.splice(*index..index + 1, 1);
}
AcpThreadEvent::EntriesRemoved(range) => {
- self.entry_view_state.remove(range.clone());
+ self.entry_view_state
+ .update(cx, |view_state, _cx| view_state.remove(range.clone()));
self.list_state.splice(range.clone(), 0);
}
AcpThreadEvent::ToolAuthorizationRequired => {
@@ -709,29 +785,15 @@ impl AcpThreadView {
.border_1()
.border_color(cx.theme().colors().border)
.text_xs()
- .id("message")
- .on_click(cx.listener({
- move |this, _, window, cx| {
- this.start_editing_message(entry_ix, window, cx)
- }
- }))
.children(
- if let Some(editing) = self.editing_message.as_ref()
- && Some(&editing.message_id) == message.id.as_ref()
- {
- Some(
- self.render_edit_message_editor(editing, cx)
- .into_any_element(),
- )
- } else {
- message.content.markdown().map(|md| {
- self.render_markdown(
- md.clone(),
- user_message_markdown_style(window, cx),
- )
- .into_any_element()
- })
- },
+ self.entry_view_state
+ .read(cx)
+ .entry(entry_ix)
+ .and_then(|entry| entry.message_editor())
+ .map(|editor| {
+ self.render_sent_message_editor(entry_ix, editor, cx)
+ .into_any_element()
+ }),
),
)
.into_any(),
@@ -806,8 +868,8 @@ impl AcpThreadView {
primary
};
- if let Some(editing) = self.editing_message.as_ref()
- && editing.index < entry_ix
+ if let Some(editing_index) = self.editing_message.as_ref()
+ && *editing_index < entry_ix
{
let backdrop = div()
.id(("backdrop", entry_ix))
@@ -821,8 +883,8 @@ impl AcpThreadView {
div()
.relative()
- .child(backdrop)
.child(primary)
+ .child(backdrop)
.into_any_element()
} else {
primary
@@ -1010,14 +1072,10 @@ impl AcpThreadView {
let card_header_id = SharedString::from("inner-tool-call-header");
let status_icon = match &tool_call.status {
- ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::Pending,
- }
- | ToolCallStatus::WaitingForConfirmation { .. } => None,
- ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::InProgress,
- ..
- } => Some(
+ ToolCallStatus::Pending
+ | ToolCallStatus::WaitingForConfirmation { .. }
+ | ToolCallStatus::Completed => None,
+ ToolCallStatus::InProgress => Some(
Icon::new(IconName::ArrowCircle)
.color(Color::Accent)
.size(IconSize::Small)
@@ -1028,16 +1086,7 @@ impl AcpThreadView {
)
.into_any(),
),
- ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::Completed,
- ..
- } => None,
- ToolCallStatus::Rejected
- | ToolCallStatus::Canceled
- | ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::Failed,
- ..
- } => Some(
+ ToolCallStatus::Rejected | ToolCallStatus::Canceled | ToolCallStatus::Failed => Some(
Icon::new(IconName::Close)
.color(Color::Error)
.size(IconSize::Small)
@@ -1103,15 +1152,23 @@ impl AcpThreadView {
tool_call.content.is_empty(),
cx,
)),
- ToolCallStatus::Allowed { .. } | ToolCallStatus::Canceled => v_flex()
- .w_full()
- .children(tool_call.content.iter().map(|content| {
- div()
- .child(
- self.render_tool_call_content(entry_ix, content, tool_call, window, cx),
- )
- .into_any_element()
- })),
+ ToolCallStatus::Pending
+ | ToolCallStatus::InProgress
+ | ToolCallStatus::Completed
+ | ToolCallStatus::Failed
+ | ToolCallStatus::Canceled => {
+ v_flex()
+ .w_full()
+ .children(tool_call.content.iter().map(|content| {
+ div()
+ .child(
+ self.render_tool_call_content(
+ entry_ix, content, tool_call, window, cx,
+ ),
+ )
+ .into_any_element()
+ }))
+ }
ToolCallStatus::Rejected => v_flex().size_0(),
};
@@ -1243,9 +1300,7 @@ impl AcpThreadView {
Empty.into_any_element()
}
}
- ToolCallContent::Diff(diff) => {
- self.render_diff_editor(entry_ix, &diff.read(cx).multibuffer(), cx)
- }
+ ToolCallContent::Diff(diff) => self.render_diff_editor(entry_ix, &diff, cx),
ToolCallContent::Terminal(terminal) => {
self.render_terminal_tool_call(entry_ix, terminal, tool_call, window, cx)
}
@@ -1392,7 +1447,7 @@ impl AcpThreadView {
fn render_diff_editor(
&self,
entry_ix: usize,
- multibuffer: &Entity<MultiBuffer>,
+ diff: &Entity<acp_thread::Diff>,
cx: &Context<Self>,
) -> AnyElement {
v_flex()
@@ -1400,8 +1455,8 @@ impl AcpThreadView {
.border_t_1()
.border_color(self.tool_card_border_color(cx))
.child(
- if let Some(entry) = self.entry_view_state.entry(entry_ix)
- && let Some(editor) = entry.editor_for_diff(&multibuffer)
+ if let Some(entry) = self.entry_view_state.read(cx).entry(entry_ix)
+ && let Some(editor) = entry.editor_for_diff(&diff)
{
editor.clone().into_any_element()
} else {
@@ -1426,12 +1481,7 @@ impl AcpThreadView {
let tool_failed = matches!(
&tool_call.status,
- ToolCallStatus::Rejected
- | ToolCallStatus::Canceled
- | ToolCallStatus::Allowed {
- status: acp::ToolCallStatus::Failed,
- ..
- }
+ ToolCallStatus::Rejected | ToolCallStatus::Canceled | ToolCallStatus::Failed
);
let output = terminal_data.output();
@@ -1604,6 +1654,7 @@ impl AcpThreadView {
let terminal_view = self
.entry_view_state
+ .read(cx)
.entry(entry_ix)
.and_then(|entry| entry.terminal(&terminal));
let show_output = self.terminal_expanded && terminal_view.is_some();
@@ -2334,6 +2385,11 @@ impl AcpThreadView {
v_flex()
.on_action(cx.listener(Self::expand_message_editor))
+ .on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
+ if let Some(profile_selector) = this.profile_selector.as_ref() {
+ profile_selector.read(cx).menu_handle().toggle(window, cx);
+ }
+ }))
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
if let Some(model_selector) = this.model_selector.as_ref() {
model_selector
@@ -2397,6 +2453,7 @@ impl AcpThreadView {
.child(
h_flex()
.gap_1()
+ .children(self.profile_selector.clone())
.children(self.model_selector.clone())
.child(self.render_send_button(cx)),
),
@@ -2466,82 +2523,38 @@ impl AcpThreadView {
)
}
- fn start_editing_message(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
- let Some(thread) = self.thread() else {
- return;
- };
- let Some(AgentThreadEntry::UserMessage(message)) = thread.read(cx).entries().get(index)
- else {
- return;
- };
- let Some(message_id) = message.id.clone() else {
- return;
- };
-
- self.list_state.scroll_to_reveal_item(index);
-
- let chunks = message.chunks.clone();
- let editor = cx.new(|cx| {
- let mut editor = MessageEditor::new(
- self.workspace.clone(),
- self.project.clone(),
- self.thread_store.clone(),
- self.text_thread_store.clone(),
- editor::EditorMode::AutoHeight {
- min_lines: 1,
- max_lines: None,
- },
- window,
- cx,
- );
- editor.set_message(chunks, window, cx);
- editor
- });
- let subscription =
- cx.subscribe_in(&editor, window, |this, _, event, window, cx| match event {
- MessageEditorEvent::Send => {
- this.regenerate(&Default::default(), window, cx);
- }
- MessageEditorEvent::Cancel => {
- this.cancel_editing(&Default::default(), window, cx);
- }
- });
- editor.focus_handle(cx).focus(window);
-
- self.editing_message.replace(EditingMessage {
- index: index,
- message_id: message_id.clone(),
- editor,
- _subscription: subscription,
- });
- cx.notify();
- }
-
- fn render_edit_message_editor(&self, editing: &EditingMessage, cx: &Context<Self>) -> Div {
- v_flex()
- .w_full()
- .gap_2()
- .child(editing.editor.clone())
- .child(
- h_flex()
- .gap_1()
- .child(
- Icon::new(IconName::Warning)
- .color(Color::Warning)
- .size(IconSize::XSmall),
- )
- .child(
- Label::new("Editing will restart the thread from this point.")
- .color(Color::Muted)
- .size(LabelSize::XSmall),
- )
- .child(self.render_editing_message_editor_buttons(editing, cx)),
- )
+ fn render_sent_message_editor(
+ &self,
+ entry_ix: usize,
+ editor: &Entity<MessageEditor>,
+ cx: &Context<Self>,
+ ) -> Div {
+ v_flex().w_full().gap_2().child(editor.clone()).when(
+ self.editing_message == Some(entry_ix),
+ |el| {
+ el.child(
+ h_flex()
+ .gap_1()
+ .child(
+ Icon::new(IconName::Warning)
+ .color(Color::Warning)
+ .size(IconSize::XSmall),
+ )
+ .child(
+ Label::new("Editing will restart the thread from this point.")
+ .color(Color::Muted)
+ .size(LabelSize::XSmall),
+ )
+ .child(self.render_sent_message_editor_buttons(entry_ix, editor, cx)),
+ )
+ },
+ )
}
- fn render_editing_message_editor_buttons(
+ fn render_sent_message_editor_buttons(
&self,
- editing: &EditingMessage,
+ entry_ix: usize,
+ editor: &Entity<MessageEditor>,
cx: &Context<Self>,
) -> Div {
h_flex()
@@ -2554,7 +2567,7 @@ impl AcpThreadView {
.icon_color(Color::Error)
.icon_size(IconSize::Small)
.tooltip({
- let focus_handle = editing.editor.focus_handle(cx);
+ let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Cancel Edit",
@@ -2569,12 +2582,12 @@ impl AcpThreadView {
)
.child(
IconButton::new("confirm-edit-message", IconName::Return)
- .disabled(editing.editor.read(cx).is_empty(cx))
+ .disabled(editor.read(cx).is_empty(cx))
.shape(ui::IconButtonShape::Square)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.tooltip({
- let focus_handle = editing.editor.focus_handle(cx);
+ let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Regenerate",
@@ -2585,7 +2598,12 @@ impl AcpThreadView {
)
}
})
- .on_click(cx.listener(Self::regenerate)),
+ .on_click(cx.listener({
+ let editor = editor.clone();
+ move |this, _, window, cx| {
+ this.regenerate(entry_ix, &editor, window, cx);
+ }
+ })),
)
}
@@ -3118,7 +3136,9 @@ impl AcpThreadView {
}
fn settings_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
- self.entry_view_state.settings_changed(cx);
+ self.entry_view_state.update(cx, |entry_view_state, cx| {
+ entry_view_state.settings_changed(cx);
+ });
}
pub(crate) fn insert_dragged_files(
@@ -3133,9 +3153,7 @@ impl AcpThreadView {
drop(added_worktrees);
})
}
-}
-impl AcpThreadView {
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<'_, Self>) -> Option<Div> {
let content = match self.thread_error.as_ref()? {
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
@@ -3420,35 +3438,6 @@ impl Render for AcpThreadView {
}
}
-fn user_message_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
- let mut style = default_markdown_style(false, window, cx);
- let mut text_style = window.text_style();
- let theme_settings = ThemeSettings::get_global(cx);
-
- let buffer_font = theme_settings.buffer_font.family.clone();
- let buffer_font_size = TextSize::Small.rems(cx);
-
- text_style.refine(&TextStyleRefinement {
- font_family: Some(buffer_font),
- font_size: Some(buffer_font_size.into()),
- ..Default::default()
- });
-
- style.base_text_style = text_style;
- style.link_callback = Some(Rc::new(move |url, cx| {
- if MentionUri::parse(url).is_ok() {
- let colors = cx.theme().colors();
- Some(TextStyleRefinement {
- background_color: Some(colors.element_background),
- ..Default::default()
- })
- } else {
- None
- }
- }));
- style
-}
-
fn default_markdown_style(buffer_font: bool, window: &Window, cx: &App) -> MarkdownStyle {
let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors();
@@ -3607,12 +3596,13 @@ pub(crate) mod tests {
use agent_client_protocol::SessionId;
use editor::EditorSettings;
use fs::FakeFs;
- use gpui::{SemanticVersion, TestAppContext, VisualTestContext};
+ use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
use project::Project;
use serde_json::json;
use settings::SettingsStore;
use std::any::Any;
use std::path::Path;
+ use workspace::Item;
use super::*;
@@ -3760,6 +3750,50 @@ pub(crate) mod tests {
(thread_view, cx)
}
+ fn add_to_workspace(thread_view: Entity<AcpThreadView>, cx: &mut VisualTestContext) {
+ let workspace = thread_view.read_with(cx, |thread_view, _cx| thread_view.workspace.clone());
+
+ workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.add_item_to_active_pane(
+ Box::new(cx.new(|_| ThreadViewItem(thread_view.clone()))),
+ None,
+ true,
+ window,
+ cx,
+ );
+ })
+ .unwrap();
+ }
+
+ struct ThreadViewItem(Entity<AcpThreadView>);
+
+ impl Item for ThreadViewItem {
+ type Event = ();
+
+ fn include_in_nav_history() -> bool {
+ false
+ }
+
+ fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
+ "Test".into()
+ }
+ }
+
+ impl EventEmitter<()> for ThreadViewItem {}
+
+ impl Focusable for ThreadViewItem {
+ fn focus_handle(&self, cx: &App) -> FocusHandle {
+ self.0.read(cx).focus_handle(cx).clone()
+ }
+ }
+
+ impl Render for ThreadViewItem {
+ fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
+ self.0.clone().into_any_element()
+ }
+ }
+
struct StubAgentServer<C> {
connection: C,
}
@@ -3781,19 +3815,19 @@ pub(crate) mod tests {
C: 'static + AgentConnection + Send + Clone,
{
fn logo(&self) -> ui::IconName {
- unimplemented!()
+ ui::IconName::Ai
}
fn name(&self) -> AgentServerName {
- unimplemented!()
+ AgentServerName("Test".into())
}
fn empty_state_headline(&self) -> &'static str {
- unimplemented!()
+ "Test"
}
fn empty_state_message(&self) -> &'static str {
- unimplemented!()
+ "Test"
}
fn connect(
@@ -3943,9 +3977,17 @@ pub(crate) mod tests {
assert_eq!(thread.entries().len(), 2);
});
- thread_view.read_with(cx, |view, _| {
- assert_eq!(view.entry_view_state.entry(0).unwrap().len(), 0);
- assert_eq!(view.entry_view_state.entry(1).unwrap().len(), 1);
+ thread_view.read_with(cx, |view, cx| {
+ view.entry_view_state.read_with(cx, |entry_view_state, _| {
+ assert!(
+ entry_view_state
+ .entry(0)
+ .unwrap()
+ .message_editor()
+ .is_some()
+ );
+ assert!(entry_view_state.entry(1).unwrap().has_content());
+ });
});
// Second user message
@@ -3974,18 +4016,31 @@ pub(crate) mod tests {
let second_user_message_id = thread.read_with(cx, |thread, _| {
assert_eq!(thread.entries().len(), 4);
- let AgentThreadEntry::UserMessage(user_message) = thread.entries().get(2).unwrap()
- else {
+ let AgentThreadEntry::UserMessage(user_message) = &thread.entries()[2] else {
panic!();
};
user_message.id.clone().unwrap()
});
- thread_view.read_with(cx, |view, _| {
- assert_eq!(view.entry_view_state.entry(0).unwrap().len(), 0);
- assert_eq!(view.entry_view_state.entry(1).unwrap().len(), 1);
- assert_eq!(view.entry_view_state.entry(2).unwrap().len(), 0);
- assert_eq!(view.entry_view_state.entry(3).unwrap().len(), 1);
+ thread_view.read_with(cx, |view, cx| {
+ view.entry_view_state.read_with(cx, |entry_view_state, _| {
+ assert!(
+ entry_view_state
+ .entry(0)
+ .unwrap()
+ .message_editor()
+ .is_some()
+ );
+ assert!(entry_view_state.entry(1).unwrap().has_content());
+ assert!(
+ entry_view_state
+ .entry(2)
+ .unwrap()
+ .message_editor()
+ .is_some()
+ );
+ assert!(entry_view_state.entry(3).unwrap().has_content());
+ });
});
// Rewind to first message
@@ -4000,13 +4055,169 @@ pub(crate) mod tests {
assert_eq!(thread.entries().len(), 2);
});
- thread_view.read_with(cx, |view, _| {
- assert_eq!(view.entry_view_state.entry(0).unwrap().len(), 0);
- assert_eq!(view.entry_view_state.entry(1).unwrap().len(), 1);
+ thread_view.read_with(cx, |view, cx| {
+ view.entry_view_state.read_with(cx, |entry_view_state, _| {
+ assert!(
+ entry_view_state
+ .entry(0)
+ .unwrap()
+ .message_editor()
+ .is_some()
+ );
+ assert!(entry_view_state.entry(1).unwrap().has_content());
- // Old views should be dropped
- assert!(view.entry_view_state.entry(2).is_none());
- assert!(view.entry_view_state.entry(3).is_none());
+ // Old views should be dropped
+ assert!(entry_view_state.entry(2).is_none());
+ assert!(entry_view_state.entry(3).is_none());
+ });
});
}
+
+ #[gpui::test]
+ async fn test_message_editing_cancel(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let connection = StubAgentConnection::new();
+
+ connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
+ content: acp::ContentBlock::Text(acp::TextContent {
+ text: "Response".into(),
+ annotations: None,
+ }),
+ }]);
+
+ let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
+ add_to_workspace(thread_view.clone(), cx);
+
+ let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone());
+ message_editor.update_in(cx, |editor, window, cx| {
+ editor.set_text("Original message to edit", window, cx);
+ });
+ thread_view.update_in(cx, |thread_view, window, cx| {
+ thread_view.send(window, cx);
+ });
+
+ cx.run_until_parked();
+
+ let user_message_editor = thread_view.read_with(cx, |view, cx| {
+ assert_eq!(view.editing_message, None);
+
+ view.entry_view_state
+ .read(cx)
+ .entry(0)
+ .unwrap()
+ .message_editor()
+ .unwrap()
+ .clone()
+ });
+
+ // Focus
+ cx.focus(&user_message_editor);
+ thread_view.read_with(cx, |view, _cx| {
+ assert_eq!(view.editing_message, Some(0));
+ });
+
+ // Edit
+ user_message_editor.update_in(cx, |editor, window, cx| {
+ editor.set_text("Edited message content", window, cx);
+ });
+
+ // Cancel
+ user_message_editor.update_in(cx, |_editor, window, cx| {
+ window.dispatch_action(Box::new(editor::actions::Cancel), cx);
+ });
+
+ thread_view.read_with(cx, |view, _cx| {
+ assert_eq!(view.editing_message, None);
+ });
+
+ user_message_editor.read_with(cx, |editor, cx| {
+ assert_eq!(editor.text(cx), "Original message to edit");
+ });
+ }
+
+ #[gpui::test]
+ async fn test_message_editing_regenerate(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let connection = StubAgentConnection::new();
+
+ connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
+ content: acp::ContentBlock::Text(acp::TextContent {
+ text: "Response".into(),
+ annotations: None,
+ }),
+ }]);
+
+ let (thread_view, cx) =
+ setup_thread_view(StubAgentServer::new(connection.clone()), cx).await;
+ add_to_workspace(thread_view.clone(), cx);
+
+ let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone());
+ message_editor.update_in(cx, |editor, window, cx| {
+ editor.set_text("Original message to edit", window, cx);
+ });
+ thread_view.update_in(cx, |thread_view, window, cx| {
+ thread_view.send(window, cx);
+ });
+
+ cx.run_until_parked();
+
+ let user_message_editor = thread_view.read_with(cx, |view, cx| {
+ assert_eq!(view.editing_message, None);
+ assert_eq!(view.thread().unwrap().read(cx).entries().len(), 2);
+
+ view.entry_view_state
+ .read(cx)
+ .entry(0)
+ .unwrap()
+ .message_editor()
+ .unwrap()
+ .clone()
+ });
+
+ // Focus
+ cx.focus(&user_message_editor);
+
+ // Edit
+ user_message_editor.update_in(cx, |editor, window, cx| {
+ editor.set_text("Edited message content", window, cx);
+ });
+
+ // Send
+ connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
+ content: acp::ContentBlock::Text(acp::TextContent {
+ text: "New Response".into(),
+ annotations: None,
+ }),
+ }]);
+
+ user_message_editor.update_in(cx, |_editor, window, cx| {
+ window.dispatch_action(Box::new(Chat), cx);
+ });
+
+ cx.run_until_parked();
+
+ thread_view.read_with(cx, |view, cx| {
+ assert_eq!(view.editing_message, None);
+
+ let entries = view.thread().unwrap().read(cx).entries();
+ assert_eq!(entries.len(), 2);
+ assert_eq!(
+ entries[0].to_markdown(cx),
+ "## User\n\nEdited message content\n\n"
+ );
+ assert_eq!(
+ entries[1].to_markdown(cx),
+ "## Assistant\n\nNew Response\n\n"
+ );
+
+ let new_editor = view.entry_view_state.read_with(cx, |state, _cx| {
+ assert!(!state.entry(1).unwrap().has_content());
+ state.entry(0).unwrap().message_editor().unwrap().clone()
+ });
+
+ assert_eq!(new_editor.read(cx).text(cx), "Edited message content");
+ })
+ }
}
@@ -4020,7 +4020,7 @@ mod tests {
cx.run_until_parked();
- // Verify that the previous completion was cancelled
+ // Verify that the previous completion was canceled
assert_eq!(cancellation_events.lock().unwrap().len(), 1);
// Verify that a new request was started after cancellation
@@ -300,6 +300,7 @@ impl AgentConfiguration {
)
.child(
div()
+ .w_full()
.px_2()
.when(is_expanded, |parent| match configuration_view {
Some(configuration_view) => parent.child(configuration_view),
@@ -465,7 +466,7 @@ impl AgentConfiguration {
"modifier-send",
"Use modifier to submit a message",
Some(
- "Make a modifier (cmd-enter on macOS, ctrl-enter on Linux) required to send messages.".into(),
+ "Make a modifier (cmd-enter on macOS, ctrl-enter on Linux or Windows) required to send messages.".into(),
),
use_modifier_to_send,
move |state, _window, cx| {
@@ -1035,7 +1036,6 @@ fn extension_only_provides_context_server(manifest: &ExtensionManifest) -> bool
&& manifest.grammars.is_empty()
&& manifest.language_servers.is_empty()
&& manifest.slash_commands.is_empty()
- && manifest.indexed_docs_providers.is_empty()
&& manifest.snippets.is_none()
&& manifest.debug_locators.is_empty()
}
@@ -68,8 +68,8 @@ use theme::ThemeSettings;
use time::UtcOffset;
use ui::utils::WithRemSize;
use ui::{
- Banner, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding, PopoverMenu,
- PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
+ Banner, Callout, ContextMenu, ContextMenuEntry, Divider, ElevationIndex, KeyBinding,
+ PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
};
use util::ResultExt as _;
use workspace::{
@@ -246,9 +246,9 @@ pub enum AgentType {
impl AgentType {
fn label(self) -> impl Into<SharedString> {
match self {
- Self::Zed | Self::TextThread => "Zed",
+ Self::Zed | Self::TextThread => "Zed Agent",
Self::NativeAgent => "Agent 2",
- Self::Gemini => "Gemini",
+ Self::Gemini => "Google Gemini",
Self::ClaudeCode => "Claude Code",
}
}
@@ -844,12 +844,10 @@ impl AgentPanel {
ActiveView::Thread { thread, .. } => {
thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
}
- ActiveView::ExternalAgentThread { thread_view, .. } => {
- thread_view.update(cx, |thread_element, cx| {
- thread_element.cancel_generation(cx)
- });
- }
- ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
+ ActiveView::ExternalAgentThread { .. }
+ | ActiveView::TextThread { .. }
+ | ActiveView::History
+ | ActiveView::Configuration => {}
}
}
@@ -1287,13 +1285,11 @@ impl AgentPanel {
ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
let _ = settings
.agent_font_size
- .insert(theme::clamp_font_size(agent_font_size).0);
+ .insert(Some(theme::clamp_font_size(agent_font_size).into()));
},
);
} else {
- theme::adjust_agent_font_size(cx, |size| {
- *size += delta;
- });
+ theme::adjust_agent_font_size(cx, |size| size + delta);
}
}
WhichFontSize::BufferFont => {
@@ -1823,7 +1819,8 @@ impl AgentPanel {
.w_full()
.child(change_title_editor.clone())
.child(
- ui::IconButton::new("retry-summary-generation", IconName::RotateCcw)
+ IconButton::new("retry-summary-generation", IconName::RotateCcw)
+ .icon_size(IconSize::Small)
.on_click({
let active_thread = active_thread.clone();
move |_, _window, cx| {
@@ -1875,7 +1872,8 @@ impl AgentPanel {
.w_full()
.child(title_editor.clone())
.child(
- ui::IconButton::new("retry-summary-generation", IconName::RotateCcw)
+ IconButton::new("retry-summary-generation", IconName::RotateCcw)
+ .icon_size(IconSize::Small)
.on_click({
let context_editor = context_editor.clone();
move |_, _window, cx| {
@@ -2013,21 +2011,17 @@ impl AgentPanel {
})
}
- fn render_recent_entries_menu(
- &self,
- icon: IconName,
- cx: &mut Context<Self>,
- ) -> impl IntoElement {
+ fn render_recent_entries_menu(&self, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
PopoverMenu::new("agent-nav-menu")
.trigger_with_tooltip(
- IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
+ IconButton::new("agent-nav-menu", IconName::MenuAlt).icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
- "Toggle Panel Menu",
+ "Toggle Recent Threads",
&ToggleNavigationMenu,
&focus_handle,
window,
@@ -2163,9 +2157,7 @@ impl AgentPanel {
.pl(DynamicSpacing::Base04.rems(cx))
.child(self.render_toolbar_back_button(cx))
.into_any_element(),
- _ => self
- .render_recent_entries_menu(IconName::MenuAlt, cx)
- .into_any_element(),
+ _ => self.render_recent_entries_menu(cx).into_any_element(),
})
.child(self.render_title_view(window, cx)),
)
@@ -2403,6 +2395,22 @@ impl AgentPanel {
}
});
+ let selected_agent_label = self.selected_agent.label().into();
+ let selected_agent = div()
+ .id("selected_agent_icon")
+ .px(DynamicSpacing::Base02.rems(cx))
+ .child(Icon::new(self.selected_agent.icon()).color(Color::Muted))
+ .tooltip(move |window, cx| {
+ Tooltip::with_meta(
+ selected_agent_label.clone(),
+ None,
+ "Selected Agent",
+ window,
+ cx,
+ )
+ })
+ .into_any_element();
+
h_flex()
.id("agent-panel-toolbar")
.h(Tab::container_height(cx))
@@ -2416,26 +2424,17 @@ impl AgentPanel {
.child(
h_flex()
.size_full()
- .gap(DynamicSpacing::Base08.rems(cx))
+ .gap(DynamicSpacing::Base04.rems(cx))
+ .pl(DynamicSpacing::Base04.rems(cx))
.child(match &self.active_view {
- ActiveView::History | ActiveView::Configuration => div()
- .pl(DynamicSpacing::Base04.rems(cx))
- .child(self.render_toolbar_back_button(cx))
- .into_any_element(),
+ ActiveView::History | ActiveView::Configuration => {
+ self.render_toolbar_back_button(cx).into_any_element()
+ }
_ => h_flex()
- .h_full()
- .px(DynamicSpacing::Base04.rems(cx))
- .border_r_1()
- .border_color(cx.theme().colors().border)
- .child(
- h_flex()
- .px_0p5()
- .gap_1p5()
- .child(
- Icon::new(self.selected_agent.icon()).color(Color::Muted),
- )
- .child(Label::new(self.selected_agent.label())),
- )
+ .gap_1()
+ .child(self.render_recent_entries_menu(cx))
+ .child(Divider::vertical())
+ .child(selected_agent)
.into_any_element(),
})
.child(self.render_title_view(window, cx)),
@@ -2454,7 +2453,6 @@ impl AgentPanel {
.border_l_1()
.border_color(cx.theme().colors().border)
.child(new_thread_menu)
- .child(self.render_recent_entries_menu(IconName::HistoryRerun, cx))
.child(self.render_panel_options_menu(window, cx)),
),
)
@@ -242,7 +242,6 @@ pub fn init(
client.telemetry().clone(),
cx,
);
- indexed_docs::init(cx);
cx.observe_new(move |workspace, window, cx| {
ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
})
@@ -409,12 +408,6 @@ fn update_slash_commands_from_settings(cx: &mut App) {
let slash_command_registry = SlashCommandRegistry::global(cx);
let settings = SlashCommandSettings::get_global(cx);
- if settings.docs.enabled {
- slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
- } else {
- slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
- }
-
if settings.cargo_workspace.enabled {
slash_command_registry
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
@@ -13,7 +13,7 @@ use anyhow::{Result, anyhow};
use collections::HashSet;
pub use completion_provider::ContextPickerCompletionProvider;
use editor::display_map::{Crease, CreaseId, CreaseMetadata, FoldId};
-use editor::{Anchor, AnchorRangeExt as _, Editor, ExcerptId, FoldPlaceholder, ToOffset};
+use editor::{Anchor, Editor, ExcerptId, FoldPlaceholder, ToOffset};
use fetch_context_picker::FetchContextPicker;
use file_context_picker::FileContextPicker;
use file_context_picker::render_file_context_entry;
@@ -228,7 +228,7 @@ impl ContextPicker {
}
fn build_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Entity<ContextMenu> {
- let context_picker = cx.entity().clone();
+ let context_picker = cx.entity();
let menu = ContextMenu::build(window, cx, move |menu, _window, cx| {
let recent = self.recent_entries(cx);
@@ -837,42 +837,9 @@ fn render_fold_icon_button(
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
Arc::new({
move |fold_id, fold_range, cx| {
- let is_in_text_selection = editor.upgrade().is_some_and(|editor| {
- editor.update(cx, |editor, cx| {
- let snapshot = editor
- .buffer()
- .update(cx, |multi_buffer, cx| multi_buffer.snapshot(cx));
-
- let is_in_pending_selection = || {
- editor
- .selections
- .pending
- .as_ref()
- .is_some_and(|pending_selection| {
- pending_selection
- .selection
- .range()
- .includes(&fold_range, &snapshot)
- })
- };
-
- let mut is_in_complete_selection = || {
- editor
- .selections
- .disjoint_in_range::<usize>(fold_range.clone(), cx)
- .into_iter()
- .any(|selection| {
- // This is needed to cover a corner case, if we just check for an existing
- // selection in the fold range, having a cursor at the start of the fold
- // marks it as selected. Non-empty selections don't cause this.
- let length = selection.end - selection.start;
- length > 0
- })
- };
-
- is_in_pending_selection() || is_in_complete_selection()
- })
- });
+ let is_in_text_selection = editor
+ .update(cx, |editor, cx| editor.is_range_selected(&fold_range, cx))
+ .unwrap_or_default();
ButtonLike::new(fold_id)
.style(ButtonStyle::Filled)
@@ -72,7 +72,7 @@ pub fn init(
let Some(window) = window else {
return;
};
- let workspace = cx.entity().clone();
+ let workspace = cx.entity();
InlineAssistant::update_global(cx, |inline_assistant, cx| {
inline_assistant.register_workspace(&workspace, window, cx)
});
@@ -1,5 +1,6 @@
use std::{cmp::Reverse, sync::Arc};
+use cloud_llm_client::Plan;
use collections::{HashSet, IndexMap};
use feature_flags::ZedProFeatureFlag;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
@@ -10,7 +11,6 @@ use language_model::{
};
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
-use proto::Plan;
use ui::{ListItem, ListItemSpacing, prelude::*};
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
@@ -536,7 +536,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
) -> Option<gpui::AnyElement> {
use feature_flags::FeatureFlagAppExt;
- let plan = proto::Plan::ZedPro;
+ let plan = Plan::ZedPro;
Some(
h_flex()
@@ -557,7 +557,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
window
.dispatch_action(Box::new(zed_actions::OpenAccountSettings), cx)
}),
- Plan::Free | Plan::ZedProTrial => Button::new(
+ Plan::ZedFree | Plan::ZedProTrial => Button::new(
"try-pro",
if plan == Plan::ZedProTrial {
"Upgrade to Pro"
@@ -14,7 +14,7 @@ use agent::{
context::{AgentContextKey, ContextLoadResult, load_context},
context_store::ContextStoreEvent,
};
-use agent_settings::{AgentSettings, CompletionMode};
+use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
use ai_onboarding::ApiKeysWithProviders;
use buffer_diff::BufferDiff;
use cloud_llm_client::CompletionIntent;
@@ -55,7 +55,7 @@ use zed_actions::agent::ToggleModelSelector;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
-use crate::profile_selector::ProfileSelector;
+use crate::profile_selector::{ProfileProvider, ProfileSelector};
use crate::{
ActiveThread, AgentDiffPane, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
@@ -152,6 +152,24 @@ pub(crate) fn create_editor(
editor
}
+impl ProfileProvider for Entity<Thread> {
+ fn profiles_supported(&self, cx: &App) -> bool {
+ self.read(cx)
+ .configured_model()
+ .map_or(false, |model| model.model.supports_tools())
+ }
+
+ fn profile_id(&self, cx: &App) -> AgentProfileId {
+ self.read(cx).profile().id().clone()
+ }
+
+ fn set_profile(&self, profile_id: AgentProfileId, cx: &mut App) {
+ self.update(cx, |this, cx| {
+ this.set_profile(profile_id, cx);
+ });
+ }
+}
+
impl MessageEditor {
pub fn new(
fs: Arc<dyn Fs>,
@@ -221,8 +239,9 @@ impl MessageEditor {
)
});
- let profile_selector =
- cx.new(|cx| ProfileSelector::new(fs, thread.clone(), editor.focus_handle(cx), cx));
+ let profile_selector = cx.new(|cx| {
+ ProfileSelector::new(fs, Arc::new(thread.clone()), editor.focus_handle(cx), cx)
+ });
Self {
editor: editor.clone(),
@@ -422,11 +441,11 @@ impl MessageEditor {
thread.cancel_editing(cx);
});
- let cancelled = self.thread.update(cx, |thread, cx| {
+ let canceled = self.thread.update(cx, |thread, cx| {
thread.cancel_last_completion(Some(window.window_handle()), cx)
});
- if cancelled {
+ if canceled {
self.set_editor_is_expanded(false, cx);
self.send_to_model(window, cx);
}
@@ -1385,7 +1404,7 @@ impl MessageEditor {
})
.ok();
});
- // Replace existing load task, if any, causing it to be cancelled.
+ // Replace existing load task, if any, causing it to be canceled.
let load_task = load_task.shared();
self.load_context_task = Some(load_task.clone());
cx.spawn(async move |this, cx| {
@@ -1,12 +1,8 @@
use crate::{ManageProfiles, ToggleProfileSelector};
-use agent::{
- Thread,
- agent_profile::{AgentProfile, AvailableProfiles},
-};
+use agent::agent_profile::{AgentProfile, AvailableProfiles};
use agent_settings::{AgentDockPosition, AgentProfileId, AgentSettings, builtin_profiles};
use fs::Fs;
-use gpui::{Action, Empty, Entity, FocusHandle, Subscription, prelude::*};
-use language_model::LanguageModelRegistry;
+use gpui::{Action, Entity, FocusHandle, Subscription, prelude::*};
use settings::{Settings as _, SettingsStore, update_settings_file};
use std::sync::Arc;
use ui::{
@@ -14,10 +10,22 @@ use ui::{
prelude::*,
};
+/// Trait for types that can provide and manage agent profiles
+pub trait ProfileProvider {
+ /// Get the current profile ID
+ fn profile_id(&self, cx: &App) -> AgentProfileId;
+
+ /// Set the profile ID
+ fn set_profile(&self, profile_id: AgentProfileId, cx: &mut App);
+
+ /// Check if profiles are supported in the current context (e.g. if the model that is selected has tool support)
+ fn profiles_supported(&self, cx: &App) -> bool;
+}
+
pub struct ProfileSelector {
profiles: AvailableProfiles,
fs: Arc<dyn Fs>,
- thread: Entity<Thread>,
+ provider: Arc<dyn ProfileProvider>,
menu_handle: PopoverMenuHandle<ContextMenu>,
focus_handle: FocusHandle,
_subscriptions: Vec<Subscription>,
@@ -26,7 +34,7 @@ pub struct ProfileSelector {
impl ProfileSelector {
pub fn new(
fs: Arc<dyn Fs>,
- thread: Entity<Thread>,
+ provider: Arc<dyn ProfileProvider>,
focus_handle: FocusHandle,
cx: &mut Context<Self>,
) -> Self {
@@ -37,7 +45,7 @@ impl ProfileSelector {
Self {
profiles: AgentProfile::available_profiles(cx),
fs,
- thread,
+ provider,
menu_handle: PopoverMenuHandle::default(),
focus_handle,
_subscriptions: vec![settings_subscription],
@@ -113,10 +121,10 @@ impl ProfileSelector {
builtin_profiles::MINIMAL => Some("Chat about anything with no tools."),
_ => None,
};
- let thread_profile_id = self.thread.read(cx).profile().id();
+ let thread_profile_id = self.provider.profile_id(cx);
let entry = ContextMenuEntry::new(profile_name.clone())
- .toggleable(IconPosition::End, &profile_id == thread_profile_id);
+ .toggleable(IconPosition::End, profile_id == thread_profile_id);
let entry = if let Some(doc_text) = documentation {
entry.documentation_aside(documentation_side(settings.dock), move |_| {
@@ -128,7 +136,7 @@ impl ProfileSelector {
entry.handler({
let fs = self.fs.clone();
- let thread = self.thread.clone();
+ let provider = self.provider.clone();
let profile_id = profile_id.clone();
move |_window, cx| {
update_settings_file::<AgentSettings>(fs.clone(), cx, {
@@ -138,9 +146,7 @@ impl ProfileSelector {
}
});
- thread.update(cx, |this, cx| {
- this.set_profile(profile_id.clone(), cx);
- });
+ provider.set_profile(profile_id.clone(), cx);
}
})
}
@@ -149,23 +155,15 @@ impl ProfileSelector {
impl Render for ProfileSelector {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let settings = AgentSettings::get_global(cx);
- let profile_id = self.thread.read(cx).profile().id();
- let profile = settings.profiles.get(profile_id);
+ let profile_id = self.provider.profile_id(cx);
+ let profile = settings.profiles.get(&profile_id);
let selected_profile = profile
.map(|profile| profile.name.clone())
.unwrap_or_else(|| "Unknown".into());
- let configured_model = self.thread.read(cx).configured_model().or_else(|| {
- let model_registry = LanguageModelRegistry::read_global(cx);
- model_registry.default_model()
- });
- let Some(configured_model) = configured_model else {
- return Empty.into_any_element();
- };
-
- if configured_model.model.supports_tools() {
- let this = cx.entity().clone();
+ if self.provider.profiles_supported(cx) {
+ let this = cx.entity();
let focus_handle = self.focus_handle.clone();
let trigger_button = Button::new("profile-selector-model", selected_profile)
.label_size(LabelSize::Small)
@@ -7,22 +7,11 @@ use settings::{Settings, SettingsSources};
/// Settings for slash commands.
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
pub struct SlashCommandSettings {
- /// Settings for the `/docs` slash command.
- #[serde(default)]
- pub docs: DocsCommandSettings,
/// Settings for the `/cargo-workspace` slash command.
#[serde(default)]
pub cargo_workspace: CargoWorkspaceCommandSettings,
}
-/// Settings for the `/docs` slash command.
-#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
-pub struct DocsCommandSettings {
- /// Whether `/docs` is enabled.
- #[serde(default)]
- pub enabled: bool,
-}
-
/// Settings for the `/cargo-workspace` slash command.
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
pub struct CargoWorkspaceCommandSettings {
@@ -5,10 +5,7 @@ use crate::{
use agent_settings::{AgentSettings, CompletionMode};
use anyhow::Result;
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
-use assistant_slash_commands::{
- DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs, FileSlashCommand,
- selections_creases,
-};
+use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections_creases};
use client::{proto, zed_urls};
use collections::{BTreeSet, HashMap, HashSet, hash_map};
use editor::{
@@ -30,7 +27,6 @@ use gpui::{
StatefulInteractiveElement, Styled, Subscription, Task, Transformation, WeakEntity, actions,
div, img, percentage, point, prelude::*, pulsating_between, size,
};
-use indexed_docs::IndexedDocsStore;
use language::{
BufferSnapshot, LspAdapterDelegate, ToOffset,
language_settings::{SoftWrap, all_language_settings},
@@ -77,7 +73,7 @@ use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker}
use assistant_context::{
AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
- ParsedSlashCommand, PendingSlashCommandStatus, ThoughtProcessOutputSection,
+ PendingSlashCommandStatus, ThoughtProcessOutputSection,
};
actions!(
@@ -701,19 +697,7 @@ impl TextThreadEditor {
}
};
let render_trailer = {
- let command = command.clone();
- move |row, _unfold, _window: &mut Window, cx: &mut App| {
- // TODO: In the future we should investigate how we can expose
- // this as a hook on the `SlashCommand` trait so that we don't
- // need to special-case it here.
- if command.name == DocsSlashCommand::NAME {
- return render_docs_slash_command_trailer(
- row,
- command.clone(),
- cx,
- );
- }
-
+ move |_row, _unfold, _window: &mut Window, _cx: &mut App| {
Empty.into_any()
}
};
@@ -2398,70 +2382,6 @@ fn render_pending_slash_command_gutter_decoration(
icon.into_any_element()
}
-fn render_docs_slash_command_trailer(
- row: MultiBufferRow,
- command: ParsedSlashCommand,
- cx: &mut App,
-) -> AnyElement {
- if command.arguments.is_empty() {
- return Empty.into_any();
- }
- let args = DocsSlashCommandArgs::parse(&command.arguments);
-
- let Some(store) = args
- .provider()
- .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
- else {
- return Empty.into_any();
- };
-
- let Some(package) = args.package() else {
- return Empty.into_any();
- };
-
- let mut children = Vec::new();
-
- if store.is_indexing(&package) {
- children.push(
- div()
- .id(("crates-being-indexed", row.0))
- .child(Icon::new(IconName::ArrowCircle).with_animation(
- "arrow-circle",
- Animation::new(Duration::from_secs(4)).repeat(),
- |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
- ))
- .tooltip({
- let package = package.clone();
- Tooltip::text(format!("Indexing {package}β¦"))
- })
- .into_any_element(),
- );
- }
-
- if let Some(latest_error) = store.latest_error_for_package(&package) {
- children.push(
- div()
- .id(("latest-error", row.0))
- .child(
- Icon::new(IconName::Warning)
- .size(IconSize::Small)
- .color(Color::Warning),
- )
- .tooltip(Tooltip::text(format!("Failed to index: {latest_error}")))
- .into_any_element(),
- )
- }
-
- let is_indexing = store.is_indexing(&package);
- let latest_error = store.latest_error_for_package(&package);
-
- if !is_indexing && latest_error.is_none() {
- return Empty.into_any();
- }
-
- h_flex().gap_2().children(children).into_any_element()
-}
-
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CopyMetadata {
creases: Vec<SelectedCreaseMetadata>,
@@ -541,6 +541,7 @@ impl Render for ThreadHistory {
v_flex()
.key_context("ThreadHistory")
.size_full()
+ .bg(cx.theme().colors().panel_background)
.on_action(cx.listener(Self::select_previous))
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_first))
@@ -27,7 +27,6 @@ globset.workspace = true
gpui.workspace = true
html_to_markdown.workspace = true
http_client.workspace = true
-indexed_docs.workspace = true
language.workspace = true
project.workspace = true
prompt_store.workspace = true
@@ -3,7 +3,6 @@ mod context_server_command;
mod default_command;
mod delta_command;
mod diagnostics_command;
-mod docs_command;
mod fetch_command;
mod file_command;
mod now_command;
@@ -18,7 +17,6 @@ pub use crate::context_server_command::*;
pub use crate::default_command::*;
pub use crate::delta_command::*;
pub use crate::diagnostics_command::*;
-pub use crate::docs_command::*;
pub use crate::fetch_command::*;
pub use crate::file_command::*;
pub use crate::now_command::*;
@@ -1,543 +0,0 @@
-use std::path::Path;
-use std::sync::Arc;
-use std::sync::atomic::AtomicBool;
-use std::time::Duration;
-
-use anyhow::{Context as _, Result, anyhow, bail};
-use assistant_slash_command::{
- ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
- SlashCommandResult,
-};
-use gpui::{App, BackgroundExecutor, Entity, Task, WeakEntity};
-use indexed_docs::{
- DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
- ProviderId,
-};
-use language::{BufferSnapshot, LspAdapterDelegate};
-use project::{Project, ProjectPath};
-use ui::prelude::*;
-use util::{ResultExt, maybe};
-use workspace::Workspace;
-
-pub struct DocsSlashCommand;
-
-impl DocsSlashCommand {
- pub const NAME: &'static str = "docs";
-
- fn path_to_cargo_toml(project: Entity<Project>, cx: &mut App) -> Option<Arc<Path>> {
- let worktree = project.read(cx).worktrees(cx).next()?;
- let worktree = worktree.read(cx);
- let entry = worktree.entry_for_path("Cargo.toml")?;
- let path = ProjectPath {
- worktree_id: worktree.id(),
- path: entry.path.clone(),
- };
- Some(Arc::from(
- project.read(cx).absolute_path(&path, cx)?.as_path(),
- ))
- }
-
- /// Ensures that the indexed doc providers for Rust are registered.
- ///
- /// Ideally we would do this sooner, but we need to wait until we're able to
- /// access the workspace so we can read the project.
- fn ensure_rust_doc_providers_are_registered(
- &self,
- workspace: Option<WeakEntity<Workspace>>,
- cx: &mut App,
- ) {
- let indexed_docs_registry = IndexedDocsRegistry::global(cx);
- if indexed_docs_registry
- .get_provider_store(LocalRustdocProvider::id())
- .is_none()
- {
- let index_provider_deps = maybe!({
- let workspace = workspace
- .as_ref()
- .context("no workspace")?
- .upgrade()
- .context("workspace dropped")?;
- let project = workspace.read(cx).project().clone();
- let fs = project.read(cx).fs().clone();
- let cargo_workspace_root = Self::path_to_cargo_toml(project, cx)
- .and_then(|path| path.parent().map(|path| path.to_path_buf()))
- .context("no Cargo workspace root found")?;
-
- anyhow::Ok((fs, cargo_workspace_root))
- });
-
- if let Some((fs, cargo_workspace_root)) = index_provider_deps.log_err() {
- indexed_docs_registry.register_provider(Box::new(LocalRustdocProvider::new(
- fs,
- cargo_workspace_root,
- )));
- }
- }
-
- if indexed_docs_registry
- .get_provider_store(DocsDotRsProvider::id())
- .is_none()
- {
- let http_client = maybe!({
- let workspace = workspace
- .as_ref()
- .context("no workspace")?
- .upgrade()
- .context("workspace was dropped")?;
- let project = workspace.read(cx).project().clone();
- anyhow::Ok(project.read(cx).client().http_client())
- });
-
- if let Some(http_client) = http_client.log_err() {
- indexed_docs_registry
- .register_provider(Box::new(DocsDotRsProvider::new(http_client)));
- }
- }
- }
-
- /// Runs just-in-time indexing for a given package, in case the slash command
- /// is run without any entries existing in the index.
- fn run_just_in_time_indexing(
- store: Arc<IndexedDocsStore>,
- key: String,
- package: PackageName,
- executor: BackgroundExecutor,
- ) -> Task<()> {
- executor.clone().spawn(async move {
- let (prefix, needs_full_index) = if let Some((prefix, _)) = key.split_once('*') {
- // If we have a wildcard in the search, we want to wait until
- // we've completely finished indexing so we get a full set of
- // results for the wildcard.
- (prefix.to_string(), true)
- } else {
- (key, false)
- };
-
- // If we already have some entries, we assume that we've indexed the package before
- // and don't need to do it again.
- let has_any_entries = store
- .any_with_prefix(prefix.clone())
- .await
- .unwrap_or_default();
- if has_any_entries {
- return ();
- };
-
- let index_task = store.clone().index(package.clone());
-
- if needs_full_index {
- _ = index_task.await;
- } else {
- loop {
- executor.timer(Duration::from_millis(200)).await;
-
- if store
- .any_with_prefix(prefix.clone())
- .await
- .unwrap_or_default()
- || !store.is_indexing(&package)
- {
- break;
- }
- }
- }
- })
- }
-}
-
-impl SlashCommand for DocsSlashCommand {
- fn name(&self) -> String {
- Self::NAME.into()
- }
-
- fn description(&self) -> String {
- "insert docs".into()
- }
-
- fn menu_text(&self) -> String {
- "Insert Documentation".into()
- }
-
- fn requires_argument(&self) -> bool {
- true
- }
-
- fn complete_argument(
- self: Arc<Self>,
- arguments: &[String],
- _cancel: Arc<AtomicBool>,
- workspace: Option<WeakEntity<Workspace>>,
- _: &mut Window,
- cx: &mut App,
- ) -> Task<Result<Vec<ArgumentCompletion>>> {
- self.ensure_rust_doc_providers_are_registered(workspace, cx);
-
- let indexed_docs_registry = IndexedDocsRegistry::global(cx);
- let args = DocsSlashCommandArgs::parse(arguments);
- let store = args
- .provider()
- .context("no docs provider specified")
- .and_then(|provider| IndexedDocsStore::try_global(provider, cx));
- cx.background_spawn(async move {
- fn build_completions(items: Vec<String>) -> Vec<ArgumentCompletion> {
- items
- .into_iter()
- .map(|item| ArgumentCompletion {
- label: item.clone().into(),
- new_text: item.to_string(),
- after_completion: assistant_slash_command::AfterCompletion::Run,
- replace_previous_arguments: false,
- })
- .collect()
- }
-
- match args {
- DocsSlashCommandArgs::NoProvider => {
- let providers = indexed_docs_registry.list_providers();
- if providers.is_empty() {
- return Ok(vec![ArgumentCompletion {
- label: "No available docs providers.".into(),
- new_text: String::new(),
- after_completion: false.into(),
- replace_previous_arguments: false,
- }]);
- }
-
- Ok(providers
- .into_iter()
- .map(|provider| ArgumentCompletion {
- label: provider.to_string().into(),
- new_text: provider.to_string(),
- after_completion: false.into(),
- replace_previous_arguments: false,
- })
- .collect())
- }
- DocsSlashCommandArgs::SearchPackageDocs {
- provider,
- package,
- index,
- } => {
- let store = store?;
-
- if index {
- // We don't need to hold onto this task, as the `IndexedDocsStore` will hold it
- // until it completes.
- drop(store.clone().index(package.as_str().into()));
- }
-
- let suggested_packages = store.clone().suggest_packages().await?;
- let search_results = store.search(package).await;
-
- let mut items = build_completions(search_results);
- let workspace_crate_completions = suggested_packages
- .into_iter()
- .filter(|package_name| {
- !items
- .iter()
- .any(|item| item.label.text() == package_name.as_ref())
- })
- .map(|package_name| ArgumentCompletion {
- label: format!("{package_name} (unindexed)").into(),
- new_text: format!("{package_name}"),
- after_completion: true.into(),
- replace_previous_arguments: false,
- })
- .collect::<Vec<_>>();
- items.extend(workspace_crate_completions);
-
- if items.is_empty() {
- return Ok(vec![ArgumentCompletion {
- label: format!(
- "Enter a {package_term} name.",
- package_term = package_term(&provider)
- )
- .into(),
- new_text: provider.to_string(),
- after_completion: false.into(),
- replace_previous_arguments: false,
- }]);
- }
-
- Ok(items)
- }
- DocsSlashCommandArgs::SearchItemDocs { item_path, .. } => {
- let store = store?;
- let items = store.search(item_path).await;
- Ok(build_completions(items))
- }
- }
- })
- }
-
- fn run(
- self: Arc<Self>,
- arguments: &[String],
- _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
- _context_buffer: BufferSnapshot,
- _workspace: WeakEntity<Workspace>,
- _delegate: Option<Arc<dyn LspAdapterDelegate>>,
- _: &mut Window,
- cx: &mut App,
- ) -> Task<SlashCommandResult> {
- if arguments.is_empty() {
- return Task::ready(Err(anyhow!("missing an argument")));
- };
-
- let args = DocsSlashCommandArgs::parse(arguments);
- let executor = cx.background_executor().clone();
- let task = cx.background_spawn({
- let store = args
- .provider()
- .context("no docs provider specified")
- .and_then(|provider| IndexedDocsStore::try_global(provider, cx));
- async move {
- let (provider, key) = match args.clone() {
- DocsSlashCommandArgs::NoProvider => bail!("no docs provider specified"),
- DocsSlashCommandArgs::SearchPackageDocs {
- provider, package, ..
- } => (provider, package),
- DocsSlashCommandArgs::SearchItemDocs {
- provider,
- item_path,
- ..
- } => (provider, item_path),
- };
-
- if key.trim().is_empty() {
- bail!(
- "no {package_term} name provided",
- package_term = package_term(&provider)
- );
- }
-
- let store = store?;
-
- if let Some(package) = args.package() {
- Self::run_just_in_time_indexing(store.clone(), key.clone(), package, executor)
- .await;
- }
-
- let (text, ranges) = if let Some((prefix, _)) = key.split_once('*') {
- let docs = store.load_many_by_prefix(prefix.to_string()).await?;
-
- let mut text = String::new();
- let mut ranges = Vec::new();
-
- for (key, docs) in docs {
- let prev_len = text.len();
-
- text.push_str(&docs.0);
- text.push_str("\n");
- ranges.push((key, prev_len..text.len()));
- text.push_str("\n");
- }
-
- (text, ranges)
- } else {
- let item_docs = store.load(key.clone()).await?;
- let text = item_docs.to_string();
- let range = 0..text.len();
-
- (text, vec![(key, range)])
- };
-
- anyhow::Ok((provider, text, ranges))
- }
- });
-
- cx.foreground_executor().spawn(async move {
- let (provider, text, ranges) = task.await?;
- Ok(SlashCommandOutput {
- text,
- sections: ranges
- .into_iter()
- .map(|(key, range)| SlashCommandOutputSection {
- range,
- icon: IconName::FileDoc,
- label: format!("docs ({provider}): {key}",).into(),
- metadata: None,
- })
- .collect(),
- run_commands_in_text: false,
- }
- .to_event_stream())
- })
- }
-}
-
-fn is_item_path_delimiter(char: char) -> bool {
- !char.is_alphanumeric() && char != '-' && char != '_'
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub enum DocsSlashCommandArgs {
- NoProvider,
- SearchPackageDocs {
- provider: ProviderId,
- package: String,
- index: bool,
- },
- SearchItemDocs {
- provider: ProviderId,
- package: String,
- item_path: String,
- },
-}
-
-impl DocsSlashCommandArgs {
- pub fn parse(arguments: &[String]) -> Self {
- let Some(provider) = arguments
- .get(0)
- .cloned()
- .filter(|arg| !arg.trim().is_empty())
- else {
- return Self::NoProvider;
- };
- let provider = ProviderId(provider.into());
- let Some(argument) = arguments.get(1) else {
- return Self::NoProvider;
- };
-
- if let Some((package, rest)) = argument.split_once(is_item_path_delimiter) {
- if rest.trim().is_empty() {
- Self::SearchPackageDocs {
- provider,
- package: package.to_owned(),
- index: true,
- }
- } else {
- Self::SearchItemDocs {
- provider,
- package: package.to_owned(),
- item_path: argument.to_owned(),
- }
- }
- } else {
- Self::SearchPackageDocs {
- provider,
- package: argument.to_owned(),
- index: false,
- }
- }
- }
-
- pub fn provider(&self) -> Option<ProviderId> {
- match self {
- Self::NoProvider => None,
- Self::SearchPackageDocs { provider, .. } | Self::SearchItemDocs { provider, .. } => {
- Some(provider.clone())
- }
- }
- }
-
- pub fn package(&self) -> Option<PackageName> {
- match self {
- Self::NoProvider => None,
- Self::SearchPackageDocs { package, .. } | Self::SearchItemDocs { package, .. } => {
- Some(package.as_str().into())
- }
- }
- }
-}
-
-/// Returns the term used to refer to a package.
-fn package_term(provider: &ProviderId) -> &'static str {
- if provider == &DocsDotRsProvider::id() || provider == &LocalRustdocProvider::id() {
- return "crate";
- }
-
- "package"
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_docs_slash_command_args() {
- assert_eq!(
- DocsSlashCommandArgs::parse(&["".to_string()]),
- DocsSlashCommandArgs::NoProvider
- );
- assert_eq!(
- DocsSlashCommandArgs::parse(&["rustdoc".to_string()]),
- DocsSlashCommandArgs::NoProvider
- );
-
- assert_eq!(
- DocsSlashCommandArgs::parse(&["rustdoc".to_string(), "".to_string()]),
- DocsSlashCommandArgs::SearchPackageDocs {
- provider: ProviderId("rustdoc".into()),
- package: "".into(),
- index: false
- }
- );
- assert_eq!(
- DocsSlashCommandArgs::parse(&["gleam".to_string(), "".to_string()]),
- DocsSlashCommandArgs::SearchPackageDocs {
- provider: ProviderId("gleam".into()),
- package: "".into(),
- index: false
- }
- );
-
- assert_eq!(
- DocsSlashCommandArgs::parse(&["rustdoc".to_string(), "gpui".to_string()]),
- DocsSlashCommandArgs::SearchPackageDocs {
- provider: ProviderId("rustdoc".into()),
- package: "gpui".into(),
- index: false,
- }
- );
- assert_eq!(
- DocsSlashCommandArgs::parse(&["gleam".to_string(), "gleam_stdlib".to_string()]),
- DocsSlashCommandArgs::SearchPackageDocs {
- provider: ProviderId("gleam".into()),
- package: "gleam_stdlib".into(),
- index: false
- }
- );
-
- // Adding an item path delimiter indicates we can start indexing.
- assert_eq!(
- DocsSlashCommandArgs::parse(&["rustdoc".to_string(), "gpui:".to_string()]),
- DocsSlashCommandArgs::SearchPackageDocs {
- provider: ProviderId("rustdoc".into()),
- package: "gpui".into(),
- index: true,
- }
- );
- assert_eq!(
- DocsSlashCommandArgs::parse(&["gleam".to_string(), "gleam_stdlib/".to_string()]),
- DocsSlashCommandArgs::SearchPackageDocs {
- provider: ProviderId("gleam".into()),
- package: "gleam_stdlib".into(),
- index: true
- }
- );
-
- assert_eq!(
- DocsSlashCommandArgs::parse(&[
- "rustdoc".to_string(),
- "gpui::foo::bar::Baz".to_string()
- ]),
- DocsSlashCommandArgs::SearchItemDocs {
- provider: ProviderId("rustdoc".into()),
- package: "gpui".into(),
- item_path: "gpui::foo::bar::Baz".into()
- }
- );
- assert_eq!(
- DocsSlashCommandArgs::parse(&[
- "gleam".to_string(),
- "gleam_stdlib/gleam/int".to_string()
- ]),
- DocsSlashCommandArgs::SearchItemDocs {
- provider: ProviderId("gleam".into()),
- package: "gleam_stdlib".into(),
- item_path: "gleam_stdlib/gleam/int".into()
- }
- );
- }
-}
@@ -1,16 +1,12 @@
use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
use anyhow::{Context as _, Result, anyhow};
-use chrono::Duration;
use cloud_api_client::{AuthenticatedUser, GetAuthenticatedUserResponse, PlanInfo};
use cloud_llm_client::{CurrentUsage, Plan, UsageData, UsageLimit};
use futures::{StreamExt, stream::BoxStream};
use gpui::{AppContext as _, BackgroundExecutor, Entity, TestAppContext};
use http_client::{AsyncBody, Method, Request, http};
use parking_lot::Mutex;
-use rpc::{
- ConnectionId, Peer, Receipt, TypedEnvelope,
- proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
-};
+use rpc::{ConnectionId, Peer, Receipt, TypedEnvelope, proto};
use std::sync::Arc;
pub struct FakeServer {
@@ -187,50 +183,27 @@ impl FakeServer {
pub async fn receive<M: proto::EnvelopedMessage>(&self) -> Result<TypedEnvelope<M>> {
self.executor.start_waiting();
- loop {
- let message = self
- .state
- .lock()
- .incoming
- .as_mut()
- .expect("not connected")
- .next()
- .await
- .context("other half hung up")?;
- self.executor.finish_waiting();
- let type_name = message.payload_type_name();
- let message = message.into_any();
-
- if message.is::<TypedEnvelope<M>>() {
- return Ok(*message.downcast().unwrap());
- }
-
- let accepted_tos_at = chrono::Utc::now()
- .checked_sub_signed(Duration::hours(5))
- .expect("failed to build accepted_tos_at")
- .timestamp() as u64;
-
- if message.is::<TypedEnvelope<GetPrivateUserInfo>>() {
- self.respond(
- message
- .downcast::<TypedEnvelope<GetPrivateUserInfo>>()
- .unwrap()
- .receipt(),
- GetPrivateUserInfoResponse {
- metrics_id: "the-metrics-id".into(),
- staff: false,
- flags: Default::default(),
- accepted_tos_at: Some(accepted_tos_at),
- },
- );
- continue;
- }
+ let message = self
+ .state
+ .lock()
+ .incoming
+ .as_mut()
+ .expect("not connected")
+ .next()
+ .await
+ .context("other half hung up")?;
+ self.executor.finish_waiting();
+ let type_name = message.payload_type_name();
+ let message = message.into_any();
- panic!(
- "fake server received unexpected message type: {:?}",
- type_name
- );
+ if message.is::<TypedEnvelope<M>>() {
+ return Ok(*message.downcast().unwrap());
}
+
+ panic!(
+ "fake server received unexpected message type: {:?}",
+ type_name
+ );
}
pub fn respond<T: proto::RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) {
@@ -177,7 +177,6 @@ impl UserStore {
let (mut current_user_tx, current_user_rx) = watch::channel();
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
let rpc_subscriptions = vec![
- client.add_message_handler(cx.weak_entity(), Self::handle_update_plan),
client.add_message_handler(cx.weak_entity(), Self::handle_update_contacts),
client.add_message_handler(cx.weak_entity(), Self::handle_update_invite_info),
client.add_message_handler(cx.weak_entity(), Self::handle_show_contacts),
@@ -343,26 +342,6 @@ impl UserStore {
Ok(())
}
- async fn handle_update_plan(
- this: Entity<Self>,
- _message: TypedEnvelope<proto::UpdateUserPlan>,
- mut cx: AsyncApp,
- ) -> Result<()> {
- let client = this
- .read_with(&cx, |this, _| this.client.upgrade())?
- .context("client was dropped")?;
-
- let response = client
- .cloud_client()
- .get_authenticated_user()
- .await
- .context("failed to fetch authenticated user")?;
-
- this.update(&mut cx, |this, cx| {
- this.update_authenticated_user(response, cx);
- })
- }
-
fn update_contacts(&mut self, message: UpdateContacts, cx: &Context<Self>) -> Task<Result<()>> {
match message {
UpdateContacts::Wait(barrier) => {
@@ -1019,19 +998,6 @@ impl RequestUsage {
}
}
- pub fn from_proto(amount: u32, limit: proto::UsageLimit) -> Option<Self> {
- let limit = match limit.variant? {
- proto::usage_limit::Variant::Limited(limited) => {
- UsageLimit::Limited(limited.limit as i32)
- }
- proto::usage_limit::Variant::Unlimited(_) => UsageLimit::Unlimited,
- };
- Some(RequestUsage {
- limit,
- amount: amount as i32,
- })
- }
-
fn from_headers(
limit_name: &str,
amount_name: &str,
@@ -19,7 +19,6 @@ test-support = ["sqlite"]
[dependencies]
anyhow.workspace = true
-async-stripe.workspace = true
async-trait.workspace = true
async-tungstenite.workspace = true
aws-config = { version = "1.1.5" }
@@ -30,16 +29,13 @@ axum-extra = { version = "0.4", features = ["erased-json"] }
base64.workspace = true
chrono.workspace = true
clock.workspace = true
-cloud_llm_client.workspace = true
collections.workspace = true
dashmap.workspace = true
-derive_more.workspace = true
envy = "0.4.2"
futures.workspace = true
gpui.workspace = true
hex.workspace = true
http_client.workspace = true
-jsonwebtoken.workspace = true
livekit_api.workspace = true
log.workspace = true
nanoid.workspace = true
@@ -65,7 +61,6 @@ subtle.workspace = true
supermaven_api.workspace = true
telemetry_events.workspace = true
text.workspace = true
-thiserror.workspace = true
time.workspace = true
tokio = { workspace = true, features = ["full"] }
toml.workspace = true
@@ -136,6 +131,3 @@ util.workspace = true
workspace = { workspace = true, features = ["test-support"] }
worktree = { workspace = true, features = ["test-support"] }
zlog.workspace = true
-
-[package.metadata.cargo-machete]
-ignored = ["async-stripe"]
@@ -219,12 +219,6 @@ spec:
secretKeyRef:
name: slack
key: panics_webhook
- - name: STRIPE_API_KEY
- valueFrom:
- secretKeyRef:
- name: stripe
- key: api_key
- optional: true
- name: COMPLETE_WITH_LANGUAGE_MODEL_RATE_LIMIT_PER_HOUR
value: "1000"
- name: SUPERMAVEN_ADMIN_API_KEY
@@ -474,67 +474,6 @@ CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id
CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count");
-CREATE TABLE rate_buckets (
- user_id INT NOT NULL,
- rate_limit_name VARCHAR(255) NOT NULL,
- token_count INT NOT NULL,
- last_refill TIMESTAMP WITHOUT TIME ZONE NOT NULL,
- PRIMARY KEY (user_id, rate_limit_name),
- FOREIGN KEY (user_id) REFERENCES users (id)
-);
-
-CREATE INDEX idx_user_id_rate_limit ON rate_buckets (user_id, rate_limit_name);
-
-CREATE TABLE IF NOT EXISTS billing_preferences (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- user_id INTEGER NOT NULL REFERENCES users (id),
- max_monthly_llm_usage_spending_in_cents INTEGER NOT NULL,
- model_request_overages_enabled bool NOT NULL DEFAULT FALSE,
- model_request_overages_spend_limit_in_cents integer NOT NULL DEFAULT 0
-);
-
-CREATE UNIQUE INDEX "uix_billing_preferences_on_user_id" ON billing_preferences (user_id);
-
-CREATE TABLE IF NOT EXISTS billing_customers (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- user_id INTEGER NOT NULL REFERENCES users (id),
- has_overdue_invoices BOOLEAN NOT NULL DEFAULT FALSE,
- stripe_customer_id TEXT NOT NULL,
- trial_started_at TIMESTAMP
-);
-
-CREATE UNIQUE INDEX "uix_billing_customers_on_user_id" ON billing_customers (user_id);
-
-CREATE UNIQUE INDEX "uix_billing_customers_on_stripe_customer_id" ON billing_customers (stripe_customer_id);
-
-CREATE TABLE IF NOT EXISTS billing_subscriptions (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- billing_customer_id INTEGER NOT NULL REFERENCES billing_customers (id),
- stripe_subscription_id TEXT NOT NULL,
- stripe_subscription_status TEXT NOT NULL,
- stripe_cancel_at TIMESTAMP,
- stripe_cancellation_reason TEXT,
- kind TEXT,
- stripe_current_period_start BIGINT,
- stripe_current_period_end BIGINT
-);
-
-CREATE INDEX "ix_billing_subscriptions_on_billing_customer_id" ON billing_subscriptions (billing_customer_id);
-
-CREATE UNIQUE INDEX "uix_billing_subscriptions_on_stripe_subscription_id" ON billing_subscriptions (stripe_subscription_id);
-
-CREATE TABLE IF NOT EXISTS processed_stripe_events (
- stripe_event_id TEXT PRIMARY KEY,
- stripe_event_type TEXT NOT NULL,
- stripe_event_created_timestamp INTEGER NOT NULL,
- processed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE INDEX "ix_processed_stripe_events_on_stripe_event_created_timestamp" ON processed_stripe_events (stripe_event_created_timestamp);
-
CREATE TABLE IF NOT EXISTS "breakpoints" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
@@ -0,0 +1,2 @@
+alter table users
+alter column admin set not null;
@@ -0,0 +1,2 @@
+alter table billing_customers
+ add column orb_customer_id text;
@@ -0,0 +1 @@
+drop table rate_buckets;
@@ -1,19 +1,11 @@
-pub mod billing;
pub mod contributors;
pub mod events;
pub mod extensions;
pub mod ips_file;
pub mod slack;
-use crate::db::Database;
-use crate::{
- AppState, Error, Result, auth,
- db::{User, UserId},
- rpc,
-};
-use ::rpc::proto;
+use crate::{AppState, Error, Result, auth, db::UserId, rpc};
use anyhow::Context as _;
-use axum::extract;
use axum::{
Extension, Json, Router,
body::Body,
@@ -25,7 +17,6 @@ use axum::{
routing::{get, post},
};
use axum_extra::response::ErasedJson;
-use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, OnceLock};
use tower::ServiceBuilder;
@@ -100,10 +91,7 @@ impl std::fmt::Display for SystemIdHeader {
pub fn routes(rpc_server: Arc<rpc::Server>) -> Router<(), Body> {
Router::new()
- .route("/users/look_up", get(look_up_user))
.route("/users/:id/access_tokens", post(create_access_token))
- .route("/users/:id/refresh_llm_tokens", post(refresh_llm_tokens))
- .route("/users/:id/update_plan", post(update_plan))
.route("/rpc_server_snapshot", get(get_rpc_server_snapshot))
.merge(contributors::router())
.layer(
@@ -144,99 +132,6 @@ pub async fn validate_api_token<B>(req: Request<B>, next: Next<B>) -> impl IntoR
Ok::<_, Error>(next.run(req).await)
}
-#[derive(Debug, Deserialize)]
-struct LookUpUserParams {
- identifier: String,
-}
-
-#[derive(Debug, Serialize)]
-struct LookUpUserResponse {
- user: Option<User>,
-}
-
-async fn look_up_user(
- Query(params): Query<LookUpUserParams>,
- Extension(app): Extension<Arc<AppState>>,
-) -> Result<Json<LookUpUserResponse>> {
- let user = resolve_identifier_to_user(&app.db, ¶ms.identifier).await?;
- let user = if let Some(user) = user {
- match user {
- UserOrId::User(user) => Some(user),
- UserOrId::Id(id) => app.db.get_user_by_id(id).await?,
- }
- } else {
- None
- };
-
- Ok(Json(LookUpUserResponse { user }))
-}
-
-enum UserOrId {
- User(User),
- Id(UserId),
-}
-
-async fn resolve_identifier_to_user(
- db: &Arc<Database>,
- identifier: &str,
-) -> Result<Option<UserOrId>> {
- if let Some(identifier) = identifier.parse::<i32>().ok() {
- let user = db.get_user_by_id(UserId(identifier)).await?;
-
- return Ok(user.map(UserOrId::User));
- }
-
- if identifier.starts_with("cus_") {
- let billing_customer = db
- .get_billing_customer_by_stripe_customer_id(&identifier)
- .await?;
-
- return Ok(billing_customer.map(|billing_customer| UserOrId::Id(billing_customer.user_id)));
- }
-
- if identifier.starts_with("sub_") {
- let billing_subscription = db
- .get_billing_subscription_by_stripe_subscription_id(&identifier)
- .await?;
-
- if let Some(billing_subscription) = billing_subscription {
- let billing_customer = db
- .get_billing_customer_by_id(billing_subscription.billing_customer_id)
- .await?;
-
- return Ok(
- billing_customer.map(|billing_customer| UserOrId::Id(billing_customer.user_id))
- );
- } else {
- return Ok(None);
- }
- }
-
- if identifier.contains('@') {
- let user = db.get_user_by_email(identifier).await?;
-
- return Ok(user.map(UserOrId::User));
- }
-
- if let Some(user) = db.get_user_by_github_login(identifier).await? {
- return Ok(Some(UserOrId::User(user)));
- }
-
- Ok(None)
-}
-
-#[derive(Deserialize, Debug)]
-struct CreateUserParams {
- github_user_id: i32,
- github_login: String,
- email_address: String,
- email_confirmation_code: Option<String>,
- #[serde(default)]
- admin: bool,
- #[serde(default)]
- invite_count: i32,
-}
-
async fn get_rpc_server_snapshot(
Extension(rpc_server): Extension<Arc<rpc::Server>>,
) -> Result<ErasedJson> {
@@ -295,90 +190,3 @@ async fn create_access_token(
encrypted_access_token,
}))
}
-
-#[derive(Serialize)]
-struct RefreshLlmTokensResponse {}
-
-async fn refresh_llm_tokens(
- Path(user_id): Path<UserId>,
- Extension(rpc_server): Extension<Arc<rpc::Server>>,
-) -> Result<Json<RefreshLlmTokensResponse>> {
- rpc_server.refresh_llm_tokens_for_user(user_id).await;
-
- Ok(Json(RefreshLlmTokensResponse {}))
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-struct UpdatePlanBody {
- pub plan: cloud_llm_client::Plan,
- pub subscription_period: SubscriptionPeriod,
- pub usage: cloud_llm_client::CurrentUsage,
- pub trial_started_at: Option<DateTime<Utc>>,
- pub is_usage_based_billing_enabled: bool,
- pub is_account_too_young: bool,
- pub has_overdue_invoices: bool,
-}
-
-#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
-struct SubscriptionPeriod {
- pub started_at: DateTime<Utc>,
- pub ended_at: DateTime<Utc>,
-}
-
-#[derive(Serialize)]
-struct UpdatePlanResponse {}
-
-async fn update_plan(
- Path(user_id): Path<UserId>,
- Extension(rpc_server): Extension<Arc<rpc::Server>>,
- extract::Json(body): extract::Json<UpdatePlanBody>,
-) -> Result<Json<UpdatePlanResponse>> {
- let plan = match body.plan {
- cloud_llm_client::Plan::ZedFree => proto::Plan::Free,
- cloud_llm_client::Plan::ZedPro => proto::Plan::ZedPro,
- cloud_llm_client::Plan::ZedProTrial => proto::Plan::ZedProTrial,
- };
-
- let update_user_plan = proto::UpdateUserPlan {
- plan: plan.into(),
- trial_started_at: body
- .trial_started_at
- .map(|trial_started_at| trial_started_at.timestamp() as u64),
- is_usage_based_billing_enabled: Some(body.is_usage_based_billing_enabled),
- usage: Some(proto::SubscriptionUsage {
- model_requests_usage_amount: body.usage.model_requests.used,
- model_requests_usage_limit: Some(usage_limit_to_proto(body.usage.model_requests.limit)),
- edit_predictions_usage_amount: body.usage.edit_predictions.used,
- edit_predictions_usage_limit: Some(usage_limit_to_proto(
- body.usage.edit_predictions.limit,
- )),
- }),
- subscription_period: Some(proto::SubscriptionPeriod {
- started_at: body.subscription_period.started_at.timestamp() as u64,
- ended_at: body.subscription_period.ended_at.timestamp() as u64,
- }),
- account_too_young: Some(body.is_account_too_young),
- has_overdue_invoices: Some(body.has_overdue_invoices),
- };
-
- rpc_server
- .update_plan_for_user(user_id, update_user_plan)
- .await?;
-
- Ok(Json(UpdatePlanResponse {}))
-}
-
-fn usage_limit_to_proto(limit: cloud_llm_client::UsageLimit) -> proto::UsageLimit {
- proto::UsageLimit {
- variant: Some(match limit {
- cloud_llm_client::UsageLimit::Limited(limit) => {
- proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
- limit: limit as u32,
- })
- }
- cloud_llm_client::UsageLimit::Unlimited => {
- proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
- }
- }),
- }
-}
@@ -1,59 +0,0 @@
-use std::sync::Arc;
-use stripe::SubscriptionStatus;
-
-use crate::AppState;
-use crate::db::billing_subscription::StripeSubscriptionStatus;
-use crate::db::{CreateBillingCustomerParams, billing_customer};
-use crate::stripe_client::{StripeClient, StripeCustomerId};
-
-impl From<SubscriptionStatus> for StripeSubscriptionStatus {
- fn from(value: SubscriptionStatus) -> Self {
- match value {
- SubscriptionStatus::Incomplete => Self::Incomplete,
- SubscriptionStatus::IncompleteExpired => Self::IncompleteExpired,
- SubscriptionStatus::Trialing => Self::Trialing,
- SubscriptionStatus::Active => Self::Active,
- SubscriptionStatus::PastDue => Self::PastDue,
- SubscriptionStatus::Canceled => Self::Canceled,
- SubscriptionStatus::Unpaid => Self::Unpaid,
- SubscriptionStatus::Paused => Self::Paused,
- }
- }
-}
-
-/// Finds or creates a billing customer using the provided customer.
-pub async fn find_or_create_billing_customer(
- app: &Arc<AppState>,
- stripe_client: &dyn StripeClient,
- customer_id: &StripeCustomerId,
-) -> anyhow::Result<Option<billing_customer::Model>> {
- // If we already have a billing customer record associated with the Stripe customer,
- // there's nothing more we need to do.
- if let Some(billing_customer) = app
- .db
- .get_billing_customer_by_stripe_customer_id(customer_id.0.as_ref())
- .await?
- {
- return Ok(Some(billing_customer));
- }
-
- let customer = stripe_client.get_customer(customer_id).await?;
-
- let Some(email) = customer.email else {
- return Ok(None);
- };
-
- let Some(user) = app.db.get_user_by_email(&email).await? else {
- return Ok(None);
- };
-
- let billing_customer = app
- .db
- .create_billing_customer(&CreateBillingCustomerParams {
- user_id: user.id,
- stripe_customer_id: customer.id.to_string(),
- })
- .await?;
-
- Ok(Some(billing_customer))
-}
@@ -564,170 +564,10 @@ fn for_snowflake(
country_code: Option<String>,
checksum_matched: bool,
) -> impl Iterator<Item = SnowflakeRow> {
- body.events.into_iter().filter_map(move |event| {
+ body.events.into_iter().map(move |event| {
let timestamp =
first_event_at + Duration::milliseconds(event.milliseconds_since_first_event);
- // We will need to double check, but I believe all of the events that
- // are being transformed here are now migrated over to use the
- // telemetry::event! macro, as of this commit so this code can go away
- // when we feel enough users have upgraded past this point.
let (event_type, mut event_properties) = match &event.event {
- Event::Editor(e) => (
- match e.operation.as_str() {
- "open" => "Editor Opened".to_string(),
- "save" => "Editor Saved".to_string(),
- _ => format!("Unknown Editor Event: {}", e.operation),
- },
- serde_json::to_value(e).unwrap(),
- ),
- Event::EditPrediction(e) => (
- format!(
- "Edit Prediction {}",
- if e.suggestion_accepted {
- "Accepted"
- } else {
- "Discarded"
- }
- ),
- serde_json::to_value(e).unwrap(),
- ),
- Event::EditPredictionRating(e) => (
- "Edit Prediction Rated".to_string(),
- serde_json::to_value(e).unwrap(),
- ),
- Event::Call(e) => {
- let event_type = match e.operation.trim() {
- "unshare project" => "Project Unshared".to_string(),
- "open channel notes" => "Channel Notes Opened".to_string(),
- "share project" => "Project Shared".to_string(),
- "join channel" => "Channel Joined".to_string(),
- "hang up" => "Call Ended".to_string(),
- "accept incoming" => "Incoming Call Accepted".to_string(),
- "invite" => "Participant Invited".to_string(),
- "disable microphone" => "Microphone Disabled".to_string(),
- "enable microphone" => "Microphone Enabled".to_string(),
- "enable screen share" => "Screen Share Enabled".to_string(),
- "disable screen share" => "Screen Share Disabled".to_string(),
- "decline incoming" => "Incoming Call Declined".to_string(),
- _ => format!("Unknown Call Event: {}", e.operation),
- };
-
- (event_type, serde_json::to_value(e).unwrap())
- }
- Event::Assistant(e) => (
- match e.phase {
- telemetry_events::AssistantPhase::Response => "Assistant Responded".to_string(),
- telemetry_events::AssistantPhase::Invoked => "Assistant Invoked".to_string(),
- telemetry_events::AssistantPhase::Accepted => {
- "Assistant Response Accepted".to_string()
- }
- telemetry_events::AssistantPhase::Rejected => {
- "Assistant Response Rejected".to_string()
- }
- },
- serde_json::to_value(e).unwrap(),
- ),
- Event::Cpu(_) | Event::Memory(_) => return None,
- Event::App(e) => {
- let mut properties = json!({});
- let event_type = match e.operation.trim() {
- // App
- "open" => "App Opened".to_string(),
- "first open" => "App First Opened".to_string(),
- "first open for release channel" => {
- "App First Opened For Release Channel".to_string()
- }
- "close" => "App Closed".to_string(),
-
- // Project
- "open project" => "Project Opened".to_string(),
- "open node project" => {
- properties["project_type"] = json!("node");
- "Project Opened".to_string()
- }
- "open pnpm project" => {
- properties["project_type"] = json!("pnpm");
- "Project Opened".to_string()
- }
- "open yarn project" => {
- properties["project_type"] = json!("yarn");
- "Project Opened".to_string()
- }
-
- // SSH
- "create ssh server" => "SSH Server Created".to_string(),
- "create ssh project" => "SSH Project Created".to_string(),
- "open ssh project" => "SSH Project Opened".to_string(),
-
- // Welcome Page
- "welcome page: change keymap" => "Welcome Keymap Changed".to_string(),
- "welcome page: change theme" => "Welcome Theme Changed".to_string(),
- "welcome page: close" => "Welcome Page Closed".to_string(),
- "welcome page: edit settings" => "Welcome Settings Edited".to_string(),
- "welcome page: install cli" => "Welcome CLI Installed".to_string(),
- "welcome page: open" => "Welcome Page Opened".to_string(),
- "welcome page: open extensions" => "Welcome Extensions Page Opened".to_string(),
- "welcome page: sign in to copilot" => "Welcome Copilot Signed In".to_string(),
- "welcome page: toggle diagnostic telemetry" => {
- "Welcome Diagnostic Telemetry Toggled".to_string()
- }
- "welcome page: toggle metric telemetry" => {
- "Welcome Metric Telemetry Toggled".to_string()
- }
- "welcome page: toggle vim" => "Welcome Vim Mode Toggled".to_string(),
- "welcome page: view docs" => "Welcome Documentation Viewed".to_string(),
-
- // Extensions
- "extensions page: open" => "Extensions Page Opened".to_string(),
- "extensions: install extension" => "Extension Installed".to_string(),
- "extensions: uninstall extension" => "Extension Uninstalled".to_string(),
-
- // Misc
- "markdown preview: open" => "Markdown Preview Opened".to_string(),
- "project diagnostics: open" => "Project Diagnostics Opened".to_string(),
- "project search: open" => "Project Search Opened".to_string(),
- "repl sessions: open" => "REPL Session Started".to_string(),
-
- // Feature Upsell
- "feature upsell: toggle vim" => {
- properties["source"] = json!("Feature Upsell");
- "Vim Mode Toggled".to_string()
- }
- _ => e
- .operation
- .strip_prefix("feature upsell: viewed docs (")
- .and_then(|s| s.strip_suffix(')'))
- .map_or_else(
- || format!("Unknown App Event: {}", e.operation),
- |docs_url| {
- properties["url"] = json!(docs_url);
- properties["source"] = json!("Feature Upsell");
- "Documentation Viewed".to_string()
- },
- ),
- };
- (event_type, properties)
- }
- Event::Setting(e) => (
- "Settings Changed".to_string(),
- serde_json::to_value(e).unwrap(),
- ),
- Event::Extension(e) => (
- "Extension Loaded".to_string(),
- serde_json::to_value(e).unwrap(),
- ),
- Event::Edit(e) => (
- "Editor Edited".to_string(),
- serde_json::to_value(e).unwrap(),
- ),
- Event::Action(e) => (
- "Action Invoked".to_string(),
- serde_json::to_value(e).unwrap(),
- ),
- Event::Repl(e) => (
- "Kernel Status Changed".to_string(),
- serde_json::to_value(e).unwrap(),
- ),
Event::Flexible(e) => (
e.event_type.clone(),
serde_json::to_value(&e.event_properties).unwrap(),
@@ -759,7 +599,7 @@ fn for_snowflake(
})
});
- Some(SnowflakeRow {
+ SnowflakeRow {
time: timestamp,
user_id: body.metrics_id.clone(),
device_id: body.system_id.clone(),
@@ -767,7 +607,7 @@ fn for_snowflake(
event_properties,
user_properties,
insert_id: Some(Uuid::new_v4().to_string()),
- })
+ }
})
}
@@ -41,12 +41,7 @@ use worktree_settings_file::LocalSettingsKind;
pub use tests::TestDb;
pub use ids::*;
-pub use queries::billing_customers::{CreateBillingCustomerParams, UpdateBillingCustomerParams};
-pub use queries::billing_subscriptions::{
- CreateBillingSubscriptionParams, UpdateBillingSubscriptionParams,
-};
pub use queries::contributors::ContributorSelector;
-pub use queries::processed_stripe_events::CreateProcessedStripeEventParams;
pub use sea_orm::ConnectOptions;
pub use tables::user::Model as User;
pub use tables::*;
@@ -70,9 +70,6 @@ macro_rules! id_type {
}
id_type!(AccessTokenId);
-id_type!(BillingCustomerId);
-id_type!(BillingSubscriptionId);
-id_type!(BillingPreferencesId);
id_type!(BufferId);
id_type!(ChannelBufferCollaboratorId);
id_type!(ChannelChatParticipantId);
@@ -1,9 +1,6 @@
use super::*;
pub mod access_tokens;
-pub mod billing_customers;
-pub mod billing_preferences;
-pub mod billing_subscriptions;
pub mod buffers;
pub mod channels;
pub mod contacts;
@@ -12,7 +9,6 @@ pub mod embeddings;
pub mod extensions;
pub mod messages;
pub mod notifications;
-pub mod processed_stripe_events;
pub mod projects;
pub mod rooms;
pub mod servers;
@@ -1,100 +0,0 @@
-use super::*;
-
-#[derive(Debug)]
-pub struct CreateBillingCustomerParams {
- pub user_id: UserId,
- pub stripe_customer_id: String,
-}
-
-#[derive(Debug, Default)]
-pub struct UpdateBillingCustomerParams {
- pub user_id: ActiveValue<UserId>,
- pub stripe_customer_id: ActiveValue<String>,
- pub has_overdue_invoices: ActiveValue<bool>,
- pub trial_started_at: ActiveValue<Option<DateTime>>,
-}
-
-impl Database {
- /// Creates a new billing customer.
- pub async fn create_billing_customer(
- &self,
- params: &CreateBillingCustomerParams,
- ) -> Result<billing_customer::Model> {
- self.transaction(|tx| async move {
- let customer = billing_customer::Entity::insert(billing_customer::ActiveModel {
- user_id: ActiveValue::set(params.user_id),
- stripe_customer_id: ActiveValue::set(params.stripe_customer_id.clone()),
- ..Default::default()
- })
- .exec_with_returning(&*tx)
- .await?;
-
- Ok(customer)
- })
- .await
- }
-
- /// Updates the specified billing customer.
- pub async fn update_billing_customer(
- &self,
- id: BillingCustomerId,
- params: &UpdateBillingCustomerParams,
- ) -> Result<()> {
- self.transaction(|tx| async move {
- billing_customer::Entity::update(billing_customer::ActiveModel {
- id: ActiveValue::set(id),
- user_id: params.user_id.clone(),
- stripe_customer_id: params.stripe_customer_id.clone(),
- has_overdue_invoices: params.has_overdue_invoices.clone(),
- trial_started_at: params.trial_started_at.clone(),
- created_at: ActiveValue::not_set(),
- })
- .exec(&*tx)
- .await?;
-
- Ok(())
- })
- .await
- }
-
- pub async fn get_billing_customer_by_id(
- &self,
- id: BillingCustomerId,
- ) -> Result<Option<billing_customer::Model>> {
- self.transaction(|tx| async move {
- Ok(billing_customer::Entity::find()
- .filter(billing_customer::Column::Id.eq(id))
- .one(&*tx)
- .await?)
- })
- .await
- }
-
- /// Returns the billing customer for the user with the specified ID.
- pub async fn get_billing_customer_by_user_id(
- &self,
- user_id: UserId,
- ) -> Result<Option<billing_customer::Model>> {
- self.transaction(|tx| async move {
- Ok(billing_customer::Entity::find()
- .filter(billing_customer::Column::UserId.eq(user_id))
- .one(&*tx)
- .await?)
- })
- .await
- }
-
- /// Returns the billing customer for the user with the specified Stripe customer ID.
- pub async fn get_billing_customer_by_stripe_customer_id(
- &self,
- stripe_customer_id: &str,
- ) -> Result<Option<billing_customer::Model>> {
- self.transaction(|tx| async move {
- Ok(billing_customer::Entity::find()
- .filter(billing_customer::Column::StripeCustomerId.eq(stripe_customer_id))
- .one(&*tx)
- .await?)
- })
- .await
- }
-}
@@ -1,17 +0,0 @@
-use super::*;
-
-impl Database {
- /// Returns the billing preferences for the given user, if they exist.
- pub async fn get_billing_preferences(
- &self,
- user_id: UserId,
- ) -> Result<Option<billing_preference::Model>> {
- self.transaction(|tx| async move {
- Ok(billing_preference::Entity::find()
- .filter(billing_preference::Column::UserId.eq(user_id))
- .one(&*tx)
- .await?)
- })
- .await
- }
-}
@@ -1,158 +0,0 @@
-use anyhow::Context as _;
-
-use crate::db::billing_subscription::{
- StripeCancellationReason, StripeSubscriptionStatus, SubscriptionKind,
-};
-
-use super::*;
-
-#[derive(Debug)]
-pub struct CreateBillingSubscriptionParams {
- pub billing_customer_id: BillingCustomerId,
- pub kind: Option<SubscriptionKind>,
- pub stripe_subscription_id: String,
- pub stripe_subscription_status: StripeSubscriptionStatus,
- pub stripe_cancellation_reason: Option<StripeCancellationReason>,
- pub stripe_current_period_start: Option<i64>,
- pub stripe_current_period_end: Option<i64>,
-}
-
-#[derive(Debug, Default)]
-pub struct UpdateBillingSubscriptionParams {
- pub billing_customer_id: ActiveValue<BillingCustomerId>,
- pub kind: ActiveValue<Option<SubscriptionKind>>,
- pub stripe_subscription_id: ActiveValue<String>,
- pub stripe_subscription_status: ActiveValue<StripeSubscriptionStatus>,
- pub stripe_cancel_at: ActiveValue<Option<DateTime>>,
- pub stripe_cancellation_reason: ActiveValue<Option<StripeCancellationReason>>,
- pub stripe_current_period_start: ActiveValue<Option<i64>>,
- pub stripe_current_period_end: ActiveValue<Option<i64>>,
-}
-
-impl Database {
- /// Creates a new billing subscription.
- pub async fn create_billing_subscription(
- &self,
- params: &CreateBillingSubscriptionParams,
- ) -> Result<billing_subscription::Model> {
- self.transaction(|tx| async move {
- let id = billing_subscription::Entity::insert(billing_subscription::ActiveModel {
- billing_customer_id: ActiveValue::set(params.billing_customer_id),
- kind: ActiveValue::set(params.kind),
- stripe_subscription_id: ActiveValue::set(params.stripe_subscription_id.clone()),
- stripe_subscription_status: ActiveValue::set(params.stripe_subscription_status),
- stripe_cancellation_reason: ActiveValue::set(params.stripe_cancellation_reason),
- stripe_current_period_start: ActiveValue::set(params.stripe_current_period_start),
- stripe_current_period_end: ActiveValue::set(params.stripe_current_period_end),
- ..Default::default()
- })
- .exec(&*tx)
- .await?
- .last_insert_id;
-
- Ok(billing_subscription::Entity::find_by_id(id)
- .one(&*tx)
- .await?
- .context("failed to retrieve inserted billing subscription")?)
- })
- .await
- }
-
- /// Updates the specified billing subscription.
- pub async fn update_billing_subscription(
- &self,
- id: BillingSubscriptionId,
- params: &UpdateBillingSubscriptionParams,
- ) -> Result<()> {
- self.transaction(|tx| async move {
- billing_subscription::Entity::update(billing_subscription::ActiveModel {
- id: ActiveValue::set(id),
- billing_customer_id: params.billing_customer_id.clone(),
- kind: params.kind.clone(),
- stripe_subscription_id: params.stripe_subscription_id.clone(),
- stripe_subscription_status: params.stripe_subscription_status.clone(),
- stripe_cancel_at: params.stripe_cancel_at.clone(),
- stripe_cancellation_reason: params.stripe_cancellation_reason.clone(),
- stripe_current_period_start: params.stripe_current_period_start.clone(),
- stripe_current_period_end: params.stripe_current_period_end.clone(),
- created_at: ActiveValue::not_set(),
- })
- .exec(&*tx)
- .await?;
-
- Ok(())
- })
- .await
- }
-
- /// Returns the billing subscription with the specified Stripe subscription ID.
- pub async fn get_billing_subscription_by_stripe_subscription_id(
- &self,
- stripe_subscription_id: &str,
- ) -> Result<Option<billing_subscription::Model>> {
- self.transaction(|tx| async move {
- Ok(billing_subscription::Entity::find()
- .filter(
- billing_subscription::Column::StripeSubscriptionId.eq(stripe_subscription_id),
- )
- .one(&*tx)
- .await?)
- })
- .await
- }
-
- pub async fn get_active_billing_subscription(
- &self,
- user_id: UserId,
- ) -> Result<Option<billing_subscription::Model>> {
- self.transaction(|tx| async move {
- Ok(billing_subscription::Entity::find()
- .inner_join(billing_customer::Entity)
- .filter(billing_customer::Column::UserId.eq(user_id))
- .filter(
- Condition::all()
- .add(
- Condition::any()
- .add(
- billing_subscription::Column::StripeSubscriptionStatus
- .eq(StripeSubscriptionStatus::Active),
- )
- .add(
- billing_subscription::Column::StripeSubscriptionStatus
- .eq(StripeSubscriptionStatus::Trialing),
- ),
- )
- .add(billing_subscription::Column::Kind.is_not_null()),
- )
- .one(&*tx)
- .await?)
- })
- .await
- }
-
- /// Returns whether the user has an active billing subscription.
- pub async fn has_active_billing_subscription(&self, user_id: UserId) -> Result<bool> {
- Ok(self.count_active_billing_subscriptions(user_id).await? > 0)
- }
-
- /// Returns the count of the active billing subscriptions for the user with the specified ID.
- pub async fn count_active_billing_subscriptions(&self, user_id: UserId) -> Result<usize> {
- self.transaction(|tx| async move {
- let count = billing_subscription::Entity::find()
- .inner_join(billing_customer::Entity)
- .filter(
- billing_customer::Column::UserId.eq(user_id).and(
- billing_subscription::Column::StripeSubscriptionStatus
- .eq(StripeSubscriptionStatus::Active)
- .or(billing_subscription::Column::StripeSubscriptionStatus
- .eq(StripeSubscriptionStatus::Trialing)),
- ),
- )
- .count(&*tx)
- .await?;
-
- Ok(count as usize)
- })
- .await
- }
-}
@@ -1,69 +0,0 @@
-use super::*;
-
-#[derive(Debug)]
-pub struct CreateProcessedStripeEventParams {
- pub stripe_event_id: String,
- pub stripe_event_type: String,
- pub stripe_event_created_timestamp: i64,
-}
-
-impl Database {
- /// Creates a new processed Stripe event.
- pub async fn create_processed_stripe_event(
- &self,
- params: &CreateProcessedStripeEventParams,
- ) -> Result<()> {
- self.transaction(|tx| async move {
- processed_stripe_event::Entity::insert(processed_stripe_event::ActiveModel {
- stripe_event_id: ActiveValue::set(params.stripe_event_id.clone()),
- stripe_event_type: ActiveValue::set(params.stripe_event_type.clone()),
- stripe_event_created_timestamp: ActiveValue::set(
- params.stripe_event_created_timestamp,
- ),
- ..Default::default()
- })
- .exec_without_returning(&*tx)
- .await?;
-
- Ok(())
- })
- .await
- }
-
- /// Returns the processed Stripe event with the specified event ID.
- pub async fn get_processed_stripe_event_by_event_id(
- &self,
- event_id: &str,
- ) -> Result<Option<processed_stripe_event::Model>> {
- self.transaction(|tx| async move {
- Ok(processed_stripe_event::Entity::find_by_id(event_id)
- .one(&*tx)
- .await?)
- })
- .await
- }
-
- /// Returns the processed Stripe events with the specified event IDs.
- pub async fn get_processed_stripe_events_by_event_ids(
- &self,
- event_ids: &[&str],
- ) -> Result<Vec<processed_stripe_event::Model>> {
- self.transaction(|tx| async move {
- Ok(processed_stripe_event::Entity::find()
- .filter(
- processed_stripe_event::Column::StripeEventId.is_in(event_ids.iter().copied()),
- )
- .all(&*tx)
- .await?)
- })
- .await
- }
-
- /// Returns whether the Stripe event with the specified ID has already been processed.
- pub async fn already_processed_stripe_event(&self, event_id: &str) -> Result<bool> {
- Ok(self
- .get_processed_stripe_event_by_event_id(event_id)
- .await?
- .is_some())
- }
-}
@@ -1,7 +1,4 @@
pub mod access_token;
-pub mod billing_customer;
-pub mod billing_preference;
-pub mod billing_subscription;
pub mod buffer;
pub mod buffer_operation;
pub mod buffer_snapshot;
@@ -23,7 +20,6 @@ pub mod notification;
pub mod notification_kind;
pub mod observed_buffer_edits;
pub mod observed_channel_messages;
-pub mod processed_stripe_event;
pub mod project;
pub mod project_collaborator;
pub mod project_repository;
@@ -1,41 +0,0 @@
-use crate::db::{BillingCustomerId, UserId};
-use sea_orm::entity::prelude::*;
-
-/// A billing customer.
-#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "billing_customers")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: BillingCustomerId,
- pub user_id: UserId,
- pub stripe_customer_id: String,
- pub has_overdue_invoices: bool,
- pub trial_started_at: Option<DateTime>,
- pub created_at: DateTime,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(
- belongs_to = "super::user::Entity",
- from = "Column::UserId",
- to = "super::user::Column::Id"
- )]
- User,
- #[sea_orm(has_many = "super::billing_subscription::Entity")]
- BillingSubscription,
-}
-
-impl Related<super::user::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::User.def()
- }
-}
-
-impl Related<super::billing_subscription::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::BillingSubscription.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
@@ -1,32 +0,0 @@
-use crate::db::{BillingPreferencesId, UserId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "billing_preferences")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: BillingPreferencesId,
- pub created_at: DateTime,
- pub user_id: UserId,
- pub max_monthly_llm_usage_spending_in_cents: i32,
- pub model_request_overages_enabled: bool,
- pub model_request_overages_spend_limit_in_cents: i32,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(
- belongs_to = "super::user::Entity",
- from = "Column::UserId",
- to = "super::user::Column::Id"
- )]
- User,
-}
-
-impl Related<super::user::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::User.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
@@ -1,176 +0,0 @@
-use crate::db::{BillingCustomerId, BillingSubscriptionId};
-use crate::stripe_client;
-use chrono::{Datelike as _, NaiveDate, Utc};
-use sea_orm::entity::prelude::*;
-use serde::Serialize;
-
-/// A billing subscription.
-#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "billing_subscriptions")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: BillingSubscriptionId,
- pub billing_customer_id: BillingCustomerId,
- pub kind: Option<SubscriptionKind>,
- pub stripe_subscription_id: String,
- pub stripe_subscription_status: StripeSubscriptionStatus,
- pub stripe_cancel_at: Option<DateTime>,
- pub stripe_cancellation_reason: Option<StripeCancellationReason>,
- pub stripe_current_period_start: Option<i64>,
- pub stripe_current_period_end: Option<i64>,
- pub created_at: DateTime,
-}
-
-impl Model {
- pub fn current_period_start_at(&self) -> Option<DateTimeUtc> {
- let period_start = self.stripe_current_period_start?;
- chrono::DateTime::from_timestamp(period_start, 0)
- }
-
- pub fn current_period_end_at(&self) -> Option<DateTimeUtc> {
- let period_end = self.stripe_current_period_end?;
- chrono::DateTime::from_timestamp(period_end, 0)
- }
-
- pub fn current_period(
- subscription: Option<Self>,
- is_staff: bool,
- ) -> Option<(DateTimeUtc, DateTimeUtc)> {
- if is_staff {
- let now = Utc::now();
- let year = now.year();
- let month = now.month();
-
- let first_day_of_this_month =
- NaiveDate::from_ymd_opt(year, month, 1)?.and_hms_opt(0, 0, 0)?;
-
- let next_month = if month == 12 { 1 } else { month + 1 };
- let next_month_year = if month == 12 { year + 1 } else { year };
- let first_day_of_next_month =
- NaiveDate::from_ymd_opt(next_month_year, next_month, 1)?.and_hms_opt(23, 59, 59)?;
-
- let last_day_of_this_month = first_day_of_next_month - chrono::Days::new(1);
-
- Some((
- first_day_of_this_month.and_utc(),
- last_day_of_this_month.and_utc(),
- ))
- } else {
- let subscription = subscription?;
- let period_start_at = subscription.current_period_start_at()?;
- let period_end_at = subscription.current_period_end_at()?;
-
- Some((period_start_at, period_end_at))
- }
- }
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(
- belongs_to = "super::billing_customer::Entity",
- from = "Column::BillingCustomerId",
- to = "super::billing_customer::Column::Id"
- )]
- BillingCustomer,
-}
-
-impl Related<super::billing_customer::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::BillingCustomer.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
-
-#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Hash, Serialize)]
-#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
-#[serde(rename_all = "snake_case")]
-pub enum SubscriptionKind {
- #[sea_orm(string_value = "zed_pro")]
- ZedPro,
- #[sea_orm(string_value = "zed_pro_trial")]
- ZedProTrial,
- #[sea_orm(string_value = "zed_free")]
- ZedFree,
-}
-
-impl From<SubscriptionKind> for cloud_llm_client::Plan {
- fn from(value: SubscriptionKind) -> Self {
- match value {
- SubscriptionKind::ZedPro => Self::ZedPro,
- SubscriptionKind::ZedProTrial => Self::ZedProTrial,
- SubscriptionKind::ZedFree => Self::ZedFree,
- }
- }
-}
-
-/// The status of a Stripe subscription.
-///
-/// [Stripe docs](https://docs.stripe.com/api/subscriptions/object#subscription_object-status)
-#[derive(
- Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash, Serialize,
-)]
-#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
-#[serde(rename_all = "snake_case")]
-pub enum StripeSubscriptionStatus {
- #[default]
- #[sea_orm(string_value = "incomplete")]
- Incomplete,
- #[sea_orm(string_value = "incomplete_expired")]
- IncompleteExpired,
- #[sea_orm(string_value = "trialing")]
- Trialing,
- #[sea_orm(string_value = "active")]
- Active,
- #[sea_orm(string_value = "past_due")]
- PastDue,
- #[sea_orm(string_value = "canceled")]
- Canceled,
- #[sea_orm(string_value = "unpaid")]
- Unpaid,
- #[sea_orm(string_value = "paused")]
- Paused,
-}
-
-impl StripeSubscriptionStatus {
- pub fn is_cancelable(&self) -> bool {
- match self {
- Self::Trialing | Self::Active | Self::PastDue => true,
- Self::Incomplete
- | Self::IncompleteExpired
- | Self::Canceled
- | Self::Unpaid
- | Self::Paused => false,
- }
- }
-}
-
-/// The cancellation reason for a Stripe subscription.
-///
-/// [Stripe docs](https://docs.stripe.com/api/subscriptions/object#subscription_object-cancellation_details-reason)
-#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Hash, Serialize)]
-#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
-#[serde(rename_all = "snake_case")]
-pub enum StripeCancellationReason {
- #[sea_orm(string_value = "cancellation_requested")]
- CancellationRequested,
- #[sea_orm(string_value = "payment_disputed")]
- PaymentDisputed,
- #[sea_orm(string_value = "payment_failed")]
- PaymentFailed,
-}
-
-impl From<stripe_client::StripeCancellationDetailsReason> for StripeCancellationReason {
- fn from(value: stripe_client::StripeCancellationDetailsReason) -> Self {
- match value {
- stripe_client::StripeCancellationDetailsReason::CancellationRequested => {
- Self::CancellationRequested
- }
- stripe_client::StripeCancellationDetailsReason::PaymentDisputed => {
- Self::PaymentDisputed
- }
- stripe_client::StripeCancellationDetailsReason::PaymentFailed => Self::PaymentFailed,
- }
- }
-}
@@ -1,16 +0,0 @@
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
-#[sea_orm(table_name = "processed_stripe_events")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub stripe_event_id: String,
- pub stripe_event_type: String,
- pub stripe_event_created_timestamp: i64,
- pub processed_at: DateTime,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}
@@ -29,8 +29,6 @@ pub struct Model {
pub enum Relation {
#[sea_orm(has_many = "super::access_token::Entity")]
AccessToken,
- #[sea_orm(has_one = "super::billing_customer::Entity")]
- BillingCustomer,
#[sea_orm(has_one = "super::room_participant::Entity")]
RoomParticipant,
#[sea_orm(has_many = "super::project::Entity")]
@@ -68,12 +66,6 @@ impl Related<super::access_token::Entity> for Entity {
}
}
-impl Related<super::billing_customer::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::BillingCustomer.def()
- }
-}
-
impl Related<super::room_participant::Entity> for Entity {
fn to() -> RelationDef {
Relation::RoomParticipant.def()
@@ -8,7 +8,6 @@ mod embedding_tests;
mod extension_tests;
mod feature_flag_tests;
mod message_tests;
-mod processed_stripe_event_tests;
mod user_tests;
use crate::migrations::run_database_migrations;
@@ -1,38 +0,0 @@
-use std::sync::Arc;
-
-use crate::test_both_dbs;
-
-use super::{CreateProcessedStripeEventParams, Database};
-
-test_both_dbs!(
- test_already_processed_stripe_event,
- test_already_processed_stripe_event_postgres,
- test_already_processed_stripe_event_sqlite
-);
-
-async fn test_already_processed_stripe_event(db: &Arc<Database>) {
- let unprocessed_event_id = "evt_1PiJOuRxOf7d5PNaw2zzWiyO".to_string();
- let processed_event_id = "evt_1PiIfMRxOf7d5PNakHrAUe8P".to_string();
-
- db.create_processed_stripe_event(&CreateProcessedStripeEventParams {
- stripe_event_id: processed_event_id.clone(),
- stripe_event_type: "customer.created".into(),
- stripe_event_created_timestamp: 1722355968,
- })
- .await
- .unwrap();
-
- assert!(
- db.already_processed_stripe_event(&processed_event_id)
- .await
- .unwrap(),
- "Expected {processed_event_id} to already be processed"
- );
-
- assert!(
- !db.already_processed_stripe_event(&unprocessed_event_id)
- .await
- .unwrap(),
- "Expected {unprocessed_event_id} to be unprocessed"
- );
-}
@@ -7,8 +7,6 @@ pub mod llm;
pub mod migrations;
pub mod rpc;
pub mod seed;
-pub mod stripe_billing;
-pub mod stripe_client;
pub mod user_backfiller;
#[cfg(test)]
@@ -22,21 +20,16 @@ use axum::{
};
use db::{ChannelId, Database};
use executor::Executor;
-use llm::db::LlmDatabase;
use serde::Deserialize;
use std::{path::PathBuf, sync::Arc};
use util::ResultExt;
-use crate::stripe_billing::StripeBilling;
-use crate::stripe_client::{RealStripeClient, StripeClient};
-
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub enum Error {
Http(StatusCode, String, HeaderMap),
Database(sea_orm::error::DbErr),
Internal(anyhow::Error),
- Stripe(stripe::StripeError),
}
impl From<anyhow::Error> for Error {
@@ -51,12 +44,6 @@ impl From<sea_orm::error::DbErr> for Error {
}
}
-impl From<stripe::StripeError> for Error {
- fn from(error: stripe::StripeError) -> Self {
- Self::Stripe(error)
- }
-}
-
impl From<axum::Error> for Error {
fn from(error: axum::Error) -> Self {
Self::Internal(error.into())
@@ -104,14 +91,6 @@ impl IntoResponse for Error {
);
(StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
}
- Error::Stripe(error) => {
- log::error!(
- "HTTP error {}: {:?}",
- StatusCode::INTERNAL_SERVER_ERROR,
- &error
- );
- (StatusCode::INTERNAL_SERVER_ERROR, format!("{}", &error)).into_response()
- }
}
}
}
@@ -122,7 +101,6 @@ impl std::fmt::Debug for Error {
Error::Http(code, message, _headers) => (code, message).fmt(f),
Error::Database(error) => error.fmt(f),
Error::Internal(error) => error.fmt(f),
- Error::Stripe(error) => error.fmt(f),
}
}
}
@@ -133,7 +111,6 @@ impl std::fmt::Display for Error {
Error::Http(code, message, _) => write!(f, "{code}: {message}"),
Error::Database(error) => error.fmt(f),
Error::Internal(error) => error.fmt(f),
- Error::Stripe(error) => error.fmt(f),
}
}
}
@@ -179,7 +156,6 @@ pub struct Config {
pub zed_client_checksum_seed: Option<String>,
pub slack_panics_webhook: Option<String>,
pub auto_join_channel_id: Option<ChannelId>,
- pub stripe_api_key: Option<String>,
pub supermaven_admin_api_key: Option<Arc<str>>,
pub user_backfiller_github_access_token: Option<Arc<str>>,
}
@@ -234,7 +210,6 @@ impl Config {
auto_join_channel_id: None,
migrations_path: None,
seed_path: None,
- stripe_api_key: None,
supermaven_admin_api_key: None,
user_backfiller_github_access_token: None,
kinesis_region: None,
@@ -266,14 +241,8 @@ impl ServiceMode {
pub struct AppState {
pub db: Arc<Database>,
- pub llm_db: Option<Arc<LlmDatabase>>,
pub livekit_client: Option<Arc<dyn livekit_api::Client>>,
pub blob_store_client: Option<aws_sdk_s3::Client>,
- /// This is a real instance of the Stripe client; we're working to replace references to this with the
- /// [`StripeClient`] trait.
- pub real_stripe_client: Option<Arc<stripe::Client>>,
- pub stripe_client: Option<Arc<dyn StripeClient>>,
- pub stripe_billing: Option<Arc<StripeBilling>>,
pub executor: Executor,
pub kinesis_client: Option<::aws_sdk_kinesis::Client>,
pub config: Config,
@@ -286,20 +255,6 @@ impl AppState {
let mut db = Database::new(db_options).await?;
db.initialize_notification_kinds().await?;
- let llm_db = if let Some((llm_database_url, llm_database_max_connections)) = config
- .llm_database_url
- .clone()
- .zip(config.llm_database_max_connections)
- {
- let mut llm_db_options = db::ConnectOptions::new(llm_database_url);
- llm_db_options.max_connections(llm_database_max_connections);
- let mut llm_db = LlmDatabase::new(llm_db_options, executor.clone()).await?;
- llm_db.initialize().await?;
- Some(Arc::new(llm_db))
- } else {
- None
- };
-
let livekit_client = if let Some(((server, key), secret)) = config
.livekit_server
.as_ref()
@@ -316,18 +271,10 @@ impl AppState {
};
let db = Arc::new(db);
- let stripe_client = build_stripe_client(&config).map(Arc::new).log_err();
let this = Self {
db: db.clone(),
- llm_db,
livekit_client,
blob_store_client: build_blob_store_client(&config).await.log_err(),
- stripe_billing: stripe_client
- .clone()
- .map(|stripe_client| Arc::new(StripeBilling::new(stripe_client))),
- real_stripe_client: stripe_client.clone(),
- stripe_client: stripe_client
- .map(|stripe_client| Arc::new(RealStripeClient::new(stripe_client)) as _),
executor,
kinesis_client: if config.kinesis_access_key.is_some() {
build_kinesis_client(&config).await.log_err()
@@ -340,14 +287,6 @@ impl AppState {
}
}
-fn build_stripe_client(config: &Config) -> anyhow::Result<stripe::Client> {
- let api_key = config
- .stripe_api_key
- .as_ref()
- .context("missing stripe_api_key")?;
- Ok(stripe::Client::new(api_key))
-}
-
async fn build_blob_store_client(config: &Config) -> anyhow::Result<aws_sdk_s3::Client> {
let keys = aws_sdk_s3::config::Credentials::new(
config
@@ -1,12 +1 @@
pub mod db;
-mod token;
-
-pub use token::*;
-
-pub const AGENT_EXTENDED_TRIAL_FEATURE_FLAG: &str = "agent-extended-trial";
-
-/// The name of the feature flag that bypasses the account age check.
-pub const BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG: &str = "bypass-account-age-check";
-
-/// The minimum account age an account must have in order to use the LLM service.
-pub const MIN_ACCOUNT_AGE_FOR_LLM_USE: chrono::Duration = chrono::Duration::days(30);
@@ -1,30 +1,9 @@
-mod ids;
-mod queries;
-mod seed;
-mod tables;
-
-#[cfg(test)]
-mod tests;
-
-use cloud_llm_client::LanguageModelProvider;
-use collections::HashMap;
-pub use ids::*;
-pub use seed::*;
-pub use tables::*;
-
-#[cfg(test)]
-pub use tests::TestLlmDb;
-use usage_measure::UsageMeasure;
-
use std::future::Future;
use std::sync::Arc;
use anyhow::Context;
pub use sea_orm::ConnectOptions;
-use sea_orm::prelude::*;
-use sea_orm::{
- ActiveValue, DatabaseConnection, DatabaseTransaction, IsolationLevel, TransactionTrait,
-};
+use sea_orm::{DatabaseConnection, DatabaseTransaction, IsolationLevel, TransactionTrait};
use crate::Result;
use crate::db::TransactionHandle;
@@ -36,9 +15,6 @@ pub struct LlmDatabase {
pool: DatabaseConnection,
#[allow(unused)]
executor: Executor,
- provider_ids: HashMap<LanguageModelProvider, ProviderId>,
- models: HashMap<(LanguageModelProvider, String), model::Model>,
- usage_measure_ids: HashMap<UsageMeasure, UsageMeasureId>,
#[cfg(test)]
runtime: Option<tokio::runtime::Runtime>,
}
@@ -51,59 +27,11 @@ impl LlmDatabase {
options: options.clone(),
pool: sea_orm::Database::connect(options).await?,
executor,
- provider_ids: HashMap::default(),
- models: HashMap::default(),
- usage_measure_ids: HashMap::default(),
#[cfg(test)]
runtime: None,
})
}
- pub async fn initialize(&mut self) -> Result<()> {
- self.initialize_providers().await?;
- self.initialize_models().await?;
- self.initialize_usage_measures().await?;
- Ok(())
- }
-
- /// Returns the list of all known models, with their [`LanguageModelProvider`].
- pub fn all_models(&self) -> Vec<(LanguageModelProvider, model::Model)> {
- self.models
- .iter()
- .map(|((model_provider, _model_name), model)| (*model_provider, model.clone()))
- .collect::<Vec<_>>()
- }
-
- /// Returns the names of the known models for the given [`LanguageModelProvider`].
- pub fn model_names_for_provider(&self, provider: LanguageModelProvider) -> Vec<String> {
- self.models
- .keys()
- .filter_map(|(model_provider, model_name)| {
- if model_provider == &provider {
- Some(model_name)
- } else {
- None
- }
- })
- .cloned()
- .collect::<Vec<_>>()
- }
-
- pub fn model(&self, provider: LanguageModelProvider, name: &str) -> Result<&model::Model> {
- Ok(self
- .models
- .get(&(provider, name.to_string()))
- .with_context(|| format!("unknown model {provider:?}:{name}"))?)
- }
-
- pub fn model_by_id(&self, id: ModelId) -> Result<&model::Model> {
- Ok(self
- .models
- .values()
- .find(|model| model.id == id)
- .with_context(|| format!("no model for ID {id:?}"))?)
- }
-
pub fn options(&self) -> &ConnectOptions {
&self.options
}
@@ -1,11 +0,0 @@
-use sea_orm::{DbErr, entity::prelude::*};
-use serde::{Deserialize, Serialize};
-
-use crate::id_type;
-
-id_type!(BillingEventId);
-id_type!(ModelId);
-id_type!(ProviderId);
-id_type!(RevokedAccessTokenId);
-id_type!(UsageId);
-id_type!(UsageMeasureId);
@@ -1,5 +0,0 @@
-use super::*;
-
-pub mod providers;
-pub mod subscription_usages;
-pub mod usages;
@@ -1,134 +0,0 @@
-use super::*;
-use sea_orm::{QueryOrder, sea_query::OnConflict};
-use std::str::FromStr;
-use strum::IntoEnumIterator as _;
-
-pub struct ModelParams {
- pub provider: LanguageModelProvider,
- pub name: String,
- pub max_requests_per_minute: i64,
- pub max_tokens_per_minute: i64,
- pub max_tokens_per_day: i64,
- pub price_per_million_input_tokens: i32,
- pub price_per_million_output_tokens: i32,
-}
-
-impl LlmDatabase {
- pub async fn initialize_providers(&mut self) -> Result<()> {
- self.provider_ids = self
- .transaction(|tx| async move {
- let existing_providers = provider::Entity::find().all(&*tx).await?;
-
- let mut new_providers = LanguageModelProvider::iter()
- .filter(|provider| {
- !existing_providers
- .iter()
- .any(|p| p.name == provider.to_string())
- })
- .map(|provider| provider::ActiveModel {
- name: ActiveValue::set(provider.to_string()),
- ..Default::default()
- })
- .peekable();
-
- if new_providers.peek().is_some() {
- provider::Entity::insert_many(new_providers)
- .exec(&*tx)
- .await?;
- }
-
- let all_providers: HashMap<_, _> = provider::Entity::find()
- .all(&*tx)
- .await?
- .iter()
- .filter_map(|provider| {
- LanguageModelProvider::from_str(&provider.name)
- .ok()
- .map(|p| (p, provider.id))
- })
- .collect();
-
- Ok(all_providers)
- })
- .await?;
- Ok(())
- }
-
- pub async fn initialize_models(&mut self) -> Result<()> {
- let all_provider_ids = &self.provider_ids;
- self.models = self
- .transaction(|tx| async move {
- let all_models: HashMap<_, _> = model::Entity::find()
- .all(&*tx)
- .await?
- .into_iter()
- .filter_map(|model| {
- let provider = all_provider_ids.iter().find_map(|(provider, id)| {
- if *id == model.provider_id {
- Some(provider)
- } else {
- None
- }
- })?;
- Some(((*provider, model.name.clone()), model))
- })
- .collect();
- Ok(all_models)
- })
- .await?;
- Ok(())
- }
-
- pub async fn insert_models(&mut self, models: &[ModelParams]) -> Result<()> {
- let all_provider_ids = &self.provider_ids;
- self.transaction(|tx| async move {
- model::Entity::insert_many(models.iter().map(|model_params| {
- let provider_id = all_provider_ids[&model_params.provider];
- model::ActiveModel {
- provider_id: ActiveValue::set(provider_id),
- name: ActiveValue::set(model_params.name.clone()),
- max_requests_per_minute: ActiveValue::set(model_params.max_requests_per_minute),
- max_tokens_per_minute: ActiveValue::set(model_params.max_tokens_per_minute),
- max_tokens_per_day: ActiveValue::set(model_params.max_tokens_per_day),
- price_per_million_input_tokens: ActiveValue::set(
- model_params.price_per_million_input_tokens,
- ),
- price_per_million_output_tokens: ActiveValue::set(
- model_params.price_per_million_output_tokens,
- ),
- ..Default::default()
- }
- }))
- .on_conflict(
- OnConflict::columns([model::Column::ProviderId, model::Column::Name])
- .update_columns([
- model::Column::MaxRequestsPerMinute,
- model::Column::MaxTokensPerMinute,
- model::Column::MaxTokensPerDay,
- model::Column::PricePerMillionInputTokens,
- model::Column::PricePerMillionOutputTokens,
- ])
- .to_owned(),
- )
- .exec_without_returning(&*tx)
- .await?;
- Ok(())
- })
- .await?;
- self.initialize_models().await
- }
-
- /// Returns the list of LLM providers.
- pub async fn list_providers(&self) -> Result<Vec<LanguageModelProvider>> {
- self.transaction(|tx| async move {
- Ok(provider::Entity::find()
- .order_by_asc(provider::Column::Name)
- .all(&*tx)
- .await?
- .into_iter()
- .filter_map(|p| LanguageModelProvider::from_str(&p.name).ok())
- .collect())
- })
- .await
- }
-}
@@ -1,38 +0,0 @@
-use crate::db::UserId;
-
-use super::*;
-
-impl LlmDatabase {
- pub async fn get_subscription_usage_for_period(
- &self,
- user_id: UserId,
- period_start_at: DateTimeUtc,
- period_end_at: DateTimeUtc,
- ) -> Result<Option<subscription_usage::Model>> {
- self.transaction(|tx| async move {
- self.get_subscription_usage_for_period_in_tx(
- user_id,
- period_start_at,
- period_end_at,
- &tx,
- )
- .await
- })
- .await
- }
-
- async fn get_subscription_usage_for_period_in_tx(
- &self,
- user_id: UserId,
- period_start_at: DateTimeUtc,
- period_end_at: DateTimeUtc,
- tx: &DatabaseTransaction,
- ) -> Result<Option<subscription_usage::Model>> {
- Ok(subscription_usage::Entity::find()
- .filter(subscription_usage::Column::UserId.eq(user_id))
- .filter(subscription_usage::Column::PeriodStartAt.eq(period_start_at))
- .filter(subscription_usage::Column::PeriodEndAt.eq(period_end_at))
- .one(tx)
- .await?)
- }
-}
@@ -1,44 +0,0 @@
-use std::str::FromStr;
-use strum::IntoEnumIterator as _;
-
-use super::*;
-
-impl LlmDatabase {
- pub async fn initialize_usage_measures(&mut self) -> Result<()> {
- let all_measures = self
- .transaction(|tx| async move {
- let existing_measures = usage_measure::Entity::find().all(&*tx).await?;
-
- let new_measures = UsageMeasure::iter()
- .filter(|measure| {
- !existing_measures
- .iter()
- .any(|m| m.name == measure.to_string())
- })
- .map(|measure| usage_measure::ActiveModel {
- name: ActiveValue::set(measure.to_string()),
- ..Default::default()
- })
- .collect::<Vec<_>>();
-
- if !new_measures.is_empty() {
- usage_measure::Entity::insert_many(new_measures)
- .exec(&*tx)
- .await?;
- }
-
- Ok(usage_measure::Entity::find().all(&*tx).await?)
- })
- .await?;
-
- self.usage_measure_ids = all_measures
- .into_iter()
- .filter_map(|measure| {
- UsageMeasure::from_str(&measure.name)
- .ok()
- .map(|um| (um, measure.id))
- })
- .collect();
- Ok(())
- }
-}
@@ -1,45 +0,0 @@
-use super::*;
-use crate::{Config, Result};
-use queries::providers::ModelParams;
-
-pub async fn seed_database(_config: &Config, db: &mut LlmDatabase, _force: bool) -> Result<()> {
- db.insert_models(&[
- ModelParams {
- provider: LanguageModelProvider::Anthropic,
- name: "claude-3-5-sonnet".into(),
- max_requests_per_minute: 5,
- max_tokens_per_minute: 20_000,
- max_tokens_per_day: 300_000,
- price_per_million_input_tokens: 300, // $3.00/MTok
- price_per_million_output_tokens: 1500, // $15.00/MTok
- },
- ModelParams {
- provider: LanguageModelProvider::Anthropic,
- name: "claude-3-opus".into(),
- max_requests_per_minute: 5,
- max_tokens_per_minute: 10_000,
- max_tokens_per_day: 300_000,
- price_per_million_input_tokens: 1500, // $15.00/MTok
- price_per_million_output_tokens: 7500, // $75.00/MTok
- },
- ModelParams {
- provider: LanguageModelProvider::Anthropic,
- name: "claude-3-sonnet".into(),
- max_requests_per_minute: 5,
- max_tokens_per_minute: 20_000,
- max_tokens_per_day: 300_000,
- price_per_million_input_tokens: 1500, // $15.00/MTok
- price_per_million_output_tokens: 7500, // $75.00/MTok
- },
- ModelParams {
- provider: LanguageModelProvider::Anthropic,
- name: "claude-3-haiku".into(),
- max_requests_per_minute: 5,
- max_tokens_per_minute: 25_000,
- max_tokens_per_day: 300_000,
- price_per_million_input_tokens: 25, // $0.25/MTok
- price_per_million_output_tokens: 125, // $1.25/MTok
- },
- ])
- .await
-}
@@ -1,6 +0,0 @@
-pub mod model;
-pub mod provider;
-pub mod subscription_usage;
-pub mod subscription_usage_meter;
-pub mod usage;
-pub mod usage_measure;
@@ -1,48 +0,0 @@
-use sea_orm::entity::prelude::*;
-
-use crate::llm::db::{ModelId, ProviderId};
-
-/// An LLM model.
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
-#[sea_orm(table_name = "models")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: ModelId,
- pub provider_id: ProviderId,
- pub name: String,
- pub max_requests_per_minute: i64,
- pub max_tokens_per_minute: i64,
- pub max_input_tokens_per_minute: i64,
- pub max_output_tokens_per_minute: i64,
- pub max_tokens_per_day: i64,
- pub price_per_million_input_tokens: i32,
- pub price_per_million_cache_creation_input_tokens: i32,
- pub price_per_million_cache_read_input_tokens: i32,
- pub price_per_million_output_tokens: i32,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(
- belongs_to = "super::provider::Entity",
- from = "Column::ProviderId",
- to = "super::provider::Column::Id"
- )]
- Provider,
- #[sea_orm(has_many = "super::usage::Entity")]
- Usages,
-}
-
-impl Related<super::provider::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::Provider.def()
- }
-}
-
-impl Related<super::usage::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::Usages.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
@@ -1,25 +0,0 @@
-use crate::llm::db::ProviderId;
-use sea_orm::entity::prelude::*;
-
-/// An LLM provider.
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
-#[sea_orm(table_name = "providers")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: ProviderId,
- pub name: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(has_many = "super::model::Entity")]
- Models,
-}
-
-impl Related<super::model::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::Models.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
@@ -1,22 +0,0 @@
-use crate::db::UserId;
-use crate::db::billing_subscription::SubscriptionKind;
-use sea_orm::entity::prelude::*;
-use time::PrimitiveDateTime;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
-#[sea_orm(table_name = "subscription_usages_v2")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: Uuid,
- pub user_id: UserId,
- pub period_start_at: PrimitiveDateTime,
- pub period_end_at: PrimitiveDateTime,
- pub plan: SubscriptionKind,
- pub model_requests: i32,
- pub edit_predictions: i32,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}
@@ -1,55 +0,0 @@
-use sea_orm::entity::prelude::*;
-use serde::Serialize;
-
-use crate::llm::db::ModelId;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
-#[sea_orm(table_name = "subscription_usage_meters_v2")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: Uuid,
- pub subscription_usage_id: Uuid,
- pub model_id: ModelId,
- pub mode: CompletionMode,
- pub requests: i32,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(
- belongs_to = "super::subscription_usage::Entity",
- from = "Column::SubscriptionUsageId",
- to = "super::subscription_usage::Column::Id"
- )]
- SubscriptionUsage,
- #[sea_orm(
- belongs_to = "super::model::Entity",
- from = "Column::ModelId",
- to = "super::model::Column::Id"
- )]
- Model,
-}
-
-impl Related<super::subscription_usage::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::SubscriptionUsage.def()
- }
-}
-
-impl Related<super::model::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::Model.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
-
-#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Hash, Serialize)]
-#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
-#[serde(rename_all = "snake_case")]
-pub enum CompletionMode {
- #[sea_orm(string_value = "normal")]
- Normal,
- #[sea_orm(string_value = "max")]
- Max,
-}
@@ -1,52 +0,0 @@
-use crate::{
- db::UserId,
- llm::db::{ModelId, UsageId, UsageMeasureId},
-};
-use sea_orm::entity::prelude::*;
-
-/// An LLM usage record.
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
-#[sea_orm(table_name = "usages")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: UsageId,
- /// The ID of the Zed user.
- ///
- /// Corresponds to the `users` table in the primary collab database.
- pub user_id: UserId,
- pub model_id: ModelId,
- pub measure_id: UsageMeasureId,
- pub timestamp: DateTime,
- pub buckets: Vec<i64>,
- pub is_staff: bool,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(
- belongs_to = "super::model::Entity",
- from = "Column::ModelId",
- to = "super::model::Column::Id"
- )]
- Model,
- #[sea_orm(
- belongs_to = "super::usage_measure::Entity",
- from = "Column::MeasureId",
- to = "super::usage_measure::Column::Id"
- )]
- UsageMeasure,
-}
-
-impl Related<super::model::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::Model.def()
- }
-}
-
-impl Related<super::usage_measure::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::UsageMeasure.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
@@ -1,36 +0,0 @@
-use crate::llm::db::UsageMeasureId;
-use sea_orm::entity::prelude::*;
-
-#[derive(
- Copy, Clone, Debug, PartialEq, Eq, Hash, strum::EnumString, strum::Display, strum::EnumIter,
-)]
-#[strum(serialize_all = "snake_case")]
-pub enum UsageMeasure {
- RequestsPerMinute,
- TokensPerMinute,
- InputTokensPerMinute,
- OutputTokensPerMinute,
- TokensPerDay,
-}
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
-#[sea_orm(table_name = "usage_measures")]
-pub struct Model {
- #[sea_orm(primary_key)]
- pub id: UsageMeasureId,
- pub name: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(has_many = "super::usage::Entity")]
- Usages,
-}
-
-impl Related<super::usage::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::Usages.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
@@ -1,107 +0,0 @@
-mod provider_tests;
-
-use gpui::BackgroundExecutor;
-use parking_lot::Mutex;
-use rand::prelude::*;
-use sea_orm::ConnectionTrait;
-use sqlx::migrate::MigrateDatabase;
-use std::time::Duration;
-
-use crate::migrations::run_database_migrations;
-
-use super::*;
-
-pub struct TestLlmDb {
- pub db: Option<LlmDatabase>,
- pub connection: Option<sqlx::AnyConnection>,
-}
-
-impl TestLlmDb {
- pub fn postgres(background: BackgroundExecutor) -> Self {
- static LOCK: Mutex<()> = Mutex::new(());
-
- let _guard = LOCK.lock();
- let mut rng = StdRng::from_entropy();
- let url = format!(
- "postgres://postgres@localhost/zed-llm-test-{}",
- rng.r#gen::<u128>()
- );
- let runtime = tokio::runtime::Builder::new_current_thread()
- .enable_io()
- .enable_time()
- .build()
- .unwrap();
-
- let mut db = runtime.block_on(async {
- sqlx::Postgres::create_database(&url)
- .await
- .expect("failed to create test db");
- let mut options = ConnectOptions::new(url);
- options
- .max_connections(5)
- .idle_timeout(Duration::from_secs(0));
- let db = LlmDatabase::new(options, Executor::Deterministic(background))
- .await
- .unwrap();
- let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations_llm");
- run_database_migrations(db.options(), migrations_path)
- .await
- .unwrap();
- db
- });
-
- db.runtime = Some(runtime);
-
- Self {
- db: Some(db),
- connection: None,
- }
- }
-
- pub fn db(&mut self) -> &mut LlmDatabase {
- self.db.as_mut().unwrap()
- }
-}
-
-#[macro_export]
-macro_rules! test_llm_db {
- ($test_name:ident, $postgres_test_name:ident) => {
- #[gpui::test]
- async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
- if !cfg!(target_os = "macos") {
- return;
- }
-
- let mut test_db = $crate::llm::db::TestLlmDb::postgres(cx.executor().clone());
- $test_name(test_db.db()).await;
- }
- };
-}
-
-impl Drop for TestLlmDb {
- fn drop(&mut self) {
- let db = self.db.take().unwrap();
- if let sea_orm::DatabaseBackend::Postgres = db.pool.get_database_backend() {
- db.runtime.as_ref().unwrap().block_on(async {
- use util::ResultExt;
- let query = "
- SELECT pg_terminate_backend(pg_stat_activity.pid)
- FROM pg_stat_activity
- WHERE
- pg_stat_activity.datname = current_database() AND
- pid <> pg_backend_pid();
- ";
- db.pool
- .execute(sea_orm::Statement::from_string(
- db.pool.get_database_backend(),
- query,
- ))
- .await
- .log_err();
- sqlx::Postgres::drop_database(db.options.get_url())
- .await
- .log_err();
- })
- }
- }
-}
@@ -1,31 +0,0 @@
-use cloud_llm_client::LanguageModelProvider;
-use pretty_assertions::assert_eq;
-
-use crate::llm::db::LlmDatabase;
-use crate::test_llm_db;
-
-test_llm_db!(
- test_initialize_providers,
- test_initialize_providers_postgres
-);
-
-async fn test_initialize_providers(db: &mut LlmDatabase) {
- let initial_providers = db.list_providers().await.unwrap();
- assert_eq!(initial_providers, vec![]);
-
- db.initialize_providers().await.unwrap();
-
- // Do it twice, to make sure the operation is idempotent.
- db.initialize_providers().await.unwrap();
-
- let providers = db.list_providers().await.unwrap();
-
- assert_eq!(
- providers,
- &[
- LanguageModelProvider::Anthropic,
- LanguageModelProvider::Google,
- LanguageModelProvider::OpenAi,
- ]
- )
-}
@@ -1,146 +0,0 @@
-use crate::db::billing_subscription::SubscriptionKind;
-use crate::db::{billing_customer, billing_subscription, user};
-use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG};
-use crate::{Config, db::billing_preference};
-use anyhow::{Context as _, Result};
-use chrono::{NaiveDateTime, Utc};
-use cloud_llm_client::Plan;
-use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
-use serde::{Deserialize, Serialize};
-use std::time::Duration;
-use thiserror::Error;
-use uuid::Uuid;
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct LlmTokenClaims {
- pub iat: u64,
- pub exp: u64,
- pub jti: String,
- pub user_id: u64,
- pub system_id: Option<String>,
- pub metrics_id: Uuid,
- pub github_user_login: String,
- pub account_created_at: NaiveDateTime,
- pub is_staff: bool,
- pub has_llm_closed_beta_feature_flag: bool,
- pub bypass_account_age_check: bool,
- pub use_llm_request_queue: bool,
- pub plan: Plan,
- pub has_extended_trial: bool,
- pub subscription_period: (NaiveDateTime, NaiveDateTime),
- pub enable_model_request_overages: bool,
- pub model_request_overages_spend_limit_in_cents: u32,
- pub can_use_web_search_tool: bool,
- #[serde(default)]
- pub has_overdue_invoices: bool,
-}
-
-const LLM_TOKEN_LIFETIME: Duration = Duration::from_secs(60 * 60);
-
-impl LlmTokenClaims {
- pub fn create(
- user: &user::Model,
- is_staff: bool,
- billing_customer: billing_customer::Model,
- billing_preferences: Option<billing_preference::Model>,
- feature_flags: &Vec<String>,
- subscription: billing_subscription::Model,
- system_id: Option<String>,
- config: &Config,
- ) -> Result<String> {
- let secret = config
- .llm_api_secret
- .as_ref()
- .context("no LLM API secret")?;
-
- let plan = if is_staff {
- Plan::ZedPro
- } else {
- subscription.kind.map_or(Plan::ZedFree, |kind| match kind {
- SubscriptionKind::ZedFree => Plan::ZedFree,
- SubscriptionKind::ZedPro => Plan::ZedPro,
- SubscriptionKind::ZedProTrial => Plan::ZedProTrial,
- })
- };
- let subscription_period =
- billing_subscription::Model::current_period(Some(subscription), is_staff)
- .map(|(start, end)| (start.naive_utc(), end.naive_utc()))
- .context("A plan is required to use Zed's hosted models or edit predictions. Visit https://zed.dev/account to get started.")?;
-
- let now = Utc::now();
- let claims = Self {
- iat: now.timestamp() as u64,
- exp: (now + LLM_TOKEN_LIFETIME).timestamp() as u64,
- jti: uuid::Uuid::new_v4().to_string(),
- user_id: user.id.to_proto(),
- system_id,
- metrics_id: user.metrics_id,
- github_user_login: user.github_login.clone(),
- account_created_at: user.account_created_at(),
- is_staff,
- has_llm_closed_beta_feature_flag: feature_flags
- .iter()
- .any(|flag| flag == "llm-closed-beta"),
- bypass_account_age_check: feature_flags
- .iter()
- .any(|flag| flag == BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG),
- can_use_web_search_tool: true,
- use_llm_request_queue: feature_flags.iter().any(|flag| flag == "llm-request-queue"),
- plan,
- has_extended_trial: feature_flags
- .iter()
- .any(|flag| flag == AGENT_EXTENDED_TRIAL_FEATURE_FLAG),
- subscription_period,
- enable_model_request_overages: billing_preferences
- .as_ref()
- .map_or(false, |preferences| {
- preferences.model_request_overages_enabled
- }),
- model_request_overages_spend_limit_in_cents: billing_preferences
- .as_ref()
- .map_or(0, |preferences| {
- preferences.model_request_overages_spend_limit_in_cents as u32
- }),
- has_overdue_invoices: billing_customer.has_overdue_invoices,
- };
-
- Ok(jsonwebtoken::encode(
- &Header::default(),
- &claims,
- &EncodingKey::from_secret(secret.as_ref()),
- )?)
- }
-
- pub fn validate(token: &str, config: &Config) -> Result<LlmTokenClaims, ValidateLlmTokenError> {
- let secret = config
- .llm_api_secret
- .as_ref()
- .context("no LLM API secret")?;
-
- match jsonwebtoken::decode::<Self>(
- token,
- &DecodingKey::from_secret(secret.as_ref()),
- &Validation::default(),
- ) {
- Ok(token) => Ok(token.claims),
- Err(e) => {
- if e.kind() == &jsonwebtoken::errors::ErrorKind::ExpiredSignature {
- Err(ValidateLlmTokenError::Expired)
- } else {
- Err(ValidateLlmTokenError::JwtError(e))
- }
- }
- }
- }
-}
-
-#[derive(Error, Debug)]
-pub enum ValidateLlmTokenError {
- #[error("access token is expired")]
- Expired,
- #[error("access token validation error: {0}")]
- JwtError(#[from] jsonwebtoken::errors::Error),
- #[error("{0}")]
- Other(#[from] anyhow::Error),
-}
@@ -62,13 +62,6 @@ async fn main() -> Result<()> {
db.initialize_notification_kinds().await?;
collab::seed::seed(&config, &db, false).await?;
-
- if let Some(llm_database_url) = config.llm_database_url.clone() {
- let db_options = db::ConnectOptions::new(llm_database_url);
- let mut db = LlmDatabase::new(db_options.clone(), Executor::Production).await?;
- db.initialize().await?;
- collab::llm::db::seed_database(&config, &mut db, true).await?;
- }
}
Some("serve") => {
let mode = match args.next().as_deref() {
@@ -102,13 +95,6 @@ async fn main() -> Result<()> {
let state = AppState::new(config, Executor::Production).await?;
- if let Some(stripe_billing) = state.stripe_billing.clone() {
- let executor = state.executor.clone();
- executor.spawn_detached(async move {
- stripe_billing.initialize().await.trace_err();
- });
- }
-
if mode.is_collab() {
state.db.purge_old_embeddings().await.trace_err();
@@ -270,9 +256,6 @@ async fn setup_llm_database(config: &Config) -> Result<()> {
.llm_database_migrations_path
.as_deref()
.unwrap_or_else(|| {
- #[cfg(feature = "sqlite")]
- let default_migrations = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations_llm.sqlite");
- #[cfg(not(feature = "sqlite"))]
let default_migrations = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations_llm");
Path::new(default_migrations)
@@ -1,14 +1,6 @@
mod connection_pool;
-use crate::api::billing::find_or_create_billing_customer;
use crate::api::{CloudflareIpCountryHeader, SystemIdHeader};
-use crate::db::billing_subscription::SubscriptionKind;
-use crate::llm::db::LlmDatabase;
-use crate::llm::{
- AGENT_EXTENDED_TRIAL_FEATURE_FLAG, BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG, LlmTokenClaims,
- MIN_ACCOUNT_AGE_FOR_LLM_USE,
-};
-use crate::stripe_client::StripeCustomerId;
use crate::{
AppState, Error, Result, auth,
db::{
@@ -37,7 +29,6 @@ use axum::{
response::IntoResponse,
routing::get,
};
-use chrono::Utc;
use collections::{HashMap, HashSet};
pub use connection_pool::{ConnectionPool, ZedVersion};
use core::fmt::{self, Debug, Formatter};
@@ -148,13 +139,6 @@ pub enum Principal {
}
impl Principal {
- fn user(&self) -> &User {
- match self {
- Principal::User(user) => user,
- Principal::Impersonated { user, .. } => user,
- }
- }
-
fn update_span(&self, span: &tracing::Span) {
match &self {
Principal::User(user) => {
@@ -218,6 +202,7 @@ struct Session {
/// The GeoIP country code for the user.
#[allow(unused)]
geoip_country_code: Option<String>,
+ #[allow(unused)]
system_id: Option<String>,
_executor: Executor,
}
@@ -463,9 +448,6 @@ impl Server {
.add_request_handler(follow)
.add_message_handler(unfollow)
.add_message_handler(update_followers)
- .add_request_handler(get_private_user_info)
- .add_request_handler(get_llm_api_token)
- .add_request_handler(accept_terms_of_service)
.add_message_handler(acknowledge_channel_message)
.add_message_handler(acknowledge_buffer_version)
.add_request_handler(get_supermaven_api_key)
@@ -1000,8 +982,6 @@ impl Server {
.await?;
}
- update_user_plan(session).await?;
-
let contacts = self.app_state.db.get_contacts(user.id).await?;
{
@@ -1081,53 +1061,6 @@ impl Server {
Ok(())
}
- pub async fn update_plan_for_user(
- self: &Arc<Self>,
- user_id: UserId,
- update_user_plan: proto::UpdateUserPlan,
- ) -> Result<()> {
- let pool = self.connection_pool.lock();
- for connection_id in pool.user_connection_ids(user_id) {
- self.peer
- .send(connection_id, update_user_plan.clone())
- .trace_err();
- }
-
- Ok(())
- }
-
- /// This is the legacy way of updating the user's plan, where we fetch the data to construct the `UpdateUserPlan`
- /// message on the Collab server.
- ///
- /// The new way is to receive the data from Cloud via the `POST /users/:id/update_plan` endpoint.
- pub async fn update_plan_for_user_legacy(self: &Arc<Self>, user_id: UserId) -> Result<()> {
- let user = self
- .app_state
- .db
- .get_user_by_id(user_id)
- .await?
- .context("user not found")?;
-
- let update_user_plan = make_update_user_plan_message(
- &user,
- user.admin,
- &self.app_state.db,
- self.app_state.llm_db.clone(),
- )
- .await?;
-
- self.update_plan_for_user(user_id, update_user_plan).await
- }
-
- pub async fn refresh_llm_tokens_for_user(self: &Arc<Self>, user_id: UserId) {
- let pool = self.connection_pool.lock();
- for connection_id in pool.user_connection_ids(user_id) {
- self.peer
- .send(connection_id, proto::RefreshLlmToken {})
- .trace_err();
- }
- }
-
pub async fn snapshot(self: &Arc<Self>) -> ServerSnapshot<'_> {
ServerSnapshot {
connection_pool: ConnectionPoolGuard {
@@ -2882,214 +2815,6 @@ fn should_auto_subscribe_to_channels(version: ZedVersion) -> bool {
version.0.minor() < 139
}
-async fn current_plan(db: &Arc<Database>, user_id: UserId, is_staff: bool) -> Result<proto::Plan> {
- if is_staff {
- return Ok(proto::Plan::ZedPro);
- }
-
- let subscription = db.get_active_billing_subscription(user_id).await?;
- let subscription_kind = subscription.and_then(|subscription| subscription.kind);
-
- let plan = if let Some(subscription_kind) = subscription_kind {
- match subscription_kind {
- SubscriptionKind::ZedPro => proto::Plan::ZedPro,
- SubscriptionKind::ZedProTrial => proto::Plan::ZedProTrial,
- SubscriptionKind::ZedFree => proto::Plan::Free,
- }
- } else {
- proto::Plan::Free
- };
-
- Ok(plan)
-}
-
-async fn make_update_user_plan_message(
- user: &User,
- is_staff: bool,
- db: &Arc<Database>,
- llm_db: Option<Arc<LlmDatabase>>,
-) -> Result<proto::UpdateUserPlan> {
- let feature_flags = db.get_user_flags(user.id).await?;
- let plan = current_plan(db, user.id, is_staff).await?;
- let billing_customer = db.get_billing_customer_by_user_id(user.id).await?;
- let billing_preferences = db.get_billing_preferences(user.id).await?;
-
- let (subscription_period, usage) = if let Some(llm_db) = llm_db {
- let subscription = db.get_active_billing_subscription(user.id).await?;
-
- let subscription_period =
- crate::db::billing_subscription::Model::current_period(subscription, is_staff);
-
- let usage = if let Some((period_start_at, period_end_at)) = subscription_period {
- llm_db
- .get_subscription_usage_for_period(user.id, period_start_at, period_end_at)
- .await?
- } else {
- None
- };
-
- (subscription_period, usage)
- } else {
- (None, None)
- };
-
- let bypass_account_age_check = feature_flags
- .iter()
- .any(|flag| flag == BYPASS_ACCOUNT_AGE_CHECK_FEATURE_FLAG);
- let account_too_young = !matches!(plan, proto::Plan::ZedPro)
- && !bypass_account_age_check
- && user.account_age() < MIN_ACCOUNT_AGE_FOR_LLM_USE;
-
- Ok(proto::UpdateUserPlan {
- plan: plan.into(),
- trial_started_at: billing_customer
- .as_ref()
- .and_then(|billing_customer| billing_customer.trial_started_at)
- .map(|trial_started_at| trial_started_at.and_utc().timestamp() as u64),
- is_usage_based_billing_enabled: if is_staff {
- Some(true)
- } else {
- billing_preferences.map(|preferences| preferences.model_request_overages_enabled)
- },
- subscription_period: subscription_period.map(|(started_at, ended_at)| {
- proto::SubscriptionPeriod {
- started_at: started_at.timestamp() as u64,
- ended_at: ended_at.timestamp() as u64,
- }
- }),
- account_too_young: Some(account_too_young),
- has_overdue_invoices: billing_customer
- .map(|billing_customer| billing_customer.has_overdue_invoices),
- usage: Some(
- usage
- .map(|usage| subscription_usage_to_proto(plan, usage, &feature_flags))
- .unwrap_or_else(|| make_default_subscription_usage(plan, &feature_flags)),
- ),
- })
-}
-
-fn model_requests_limit(
- plan: cloud_llm_client::Plan,
- feature_flags: &Vec<String>,
-) -> cloud_llm_client::UsageLimit {
- match plan.model_requests_limit() {
- cloud_llm_client::UsageLimit::Limited(limit) => {
- let limit = if plan == cloud_llm_client::Plan::ZedProTrial
- && feature_flags
- .iter()
- .any(|flag| flag == AGENT_EXTENDED_TRIAL_FEATURE_FLAG)
- {
- 1_000
- } else {
- limit
- };
-
- cloud_llm_client::UsageLimit::Limited(limit)
- }
- cloud_llm_client::UsageLimit::Unlimited => cloud_llm_client::UsageLimit::Unlimited,
- }
-}
-
-fn subscription_usage_to_proto(
- plan: proto::Plan,
- usage: crate::llm::db::subscription_usage::Model,
- feature_flags: &Vec<String>,
-) -> proto::SubscriptionUsage {
- let plan = match plan {
- proto::Plan::Free => cloud_llm_client::Plan::ZedFree,
- proto::Plan::ZedPro => cloud_llm_client::Plan::ZedPro,
- proto::Plan::ZedProTrial => cloud_llm_client::Plan::ZedProTrial,
- };
-
- proto::SubscriptionUsage {
- model_requests_usage_amount: usage.model_requests as u32,
- model_requests_usage_limit: Some(proto::UsageLimit {
- variant: Some(match model_requests_limit(plan, feature_flags) {
- cloud_llm_client::UsageLimit::Limited(limit) => {
- proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
- limit: limit as u32,
- })
- }
- cloud_llm_client::UsageLimit::Unlimited => {
- proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
- }
- }),
- }),
- edit_predictions_usage_amount: usage.edit_predictions as u32,
- edit_predictions_usage_limit: Some(proto::UsageLimit {
- variant: Some(match plan.edit_predictions_limit() {
- cloud_llm_client::UsageLimit::Limited(limit) => {
- proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
- limit: limit as u32,
- })
- }
- cloud_llm_client::UsageLimit::Unlimited => {
- proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
- }
- }),
- }),
- }
-}
-
-fn make_default_subscription_usage(
- plan: proto::Plan,
- feature_flags: &Vec<String>,
-) -> proto::SubscriptionUsage {
- let plan = match plan {
- proto::Plan::Free => cloud_llm_client::Plan::ZedFree,
- proto::Plan::ZedPro => cloud_llm_client::Plan::ZedPro,
- proto::Plan::ZedProTrial => cloud_llm_client::Plan::ZedProTrial,
- };
-
- proto::SubscriptionUsage {
- model_requests_usage_amount: 0,
- model_requests_usage_limit: Some(proto::UsageLimit {
- variant: Some(match model_requests_limit(plan, feature_flags) {
- cloud_llm_client::UsageLimit::Limited(limit) => {
- proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
- limit: limit as u32,
- })
- }
- cloud_llm_client::UsageLimit::Unlimited => {
- proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
- }
- }),
- }),
- edit_predictions_usage_amount: 0,
- edit_predictions_usage_limit: Some(proto::UsageLimit {
- variant: Some(match plan.edit_predictions_limit() {
- cloud_llm_client::UsageLimit::Limited(limit) => {
- proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
- limit: limit as u32,
- })
- }
- cloud_llm_client::UsageLimit::Unlimited => {
- proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
- }
- }),
- }),
- }
-}
-
-async fn update_user_plan(session: &Session) -> Result<()> {
- let db = session.db().await;
-
- let update_user_plan = make_update_user_plan_message(
- session.principal.user(),
- session.is_staff(),
- &db.0,
- session.app_state.llm_db.clone(),
- )
- .await?;
-
- session
- .peer
- .send(session.connection_id, update_user_plan)
- .trace_err();
-
- Ok(())
-}
-
async fn subscribe_to_channels(
_: proto::SubscribeToChannels,
session: MessageContext,
@@ -4258,139 +3983,6 @@ async fn mark_notification_as_read(
Ok(())
}
-/// Get the current users information
-async fn get_private_user_info(
- _request: proto::GetPrivateUserInfo,
- response: Response<proto::GetPrivateUserInfo>,
- session: MessageContext,
-) -> Result<()> {
- let db = session.db().await;
-
- let metrics_id = db.get_user_metrics_id(session.user_id()).await?;
- let user = db
- .get_user_by_id(session.user_id())
- .await?
- .context("user not found")?;
- let flags = db.get_user_flags(session.user_id()).await?;
-
- response.send(proto::GetPrivateUserInfoResponse {
- metrics_id,
- staff: user.admin,
- flags,
- accepted_tos_at: user.accepted_tos_at.map(|t| t.and_utc().timestamp() as u64),
- })?;
- Ok(())
-}
-
-/// Accept the terms of service (tos) on behalf of the current user
-async fn accept_terms_of_service(
- _request: proto::AcceptTermsOfService,
- response: Response<proto::AcceptTermsOfService>,
- session: MessageContext,
-) -> Result<()> {
- let db = session.db().await;
-
- let accepted_tos_at = Utc::now();
- db.set_user_accepted_tos_at(session.user_id(), Some(accepted_tos_at.naive_utc()))
- .await?;
-
- response.send(proto::AcceptTermsOfServiceResponse {
- accepted_tos_at: accepted_tos_at.timestamp() as u64,
- })?;
-
- // When the user accepts the terms of service, we want to refresh their LLM
- // token to grant access.
- session
- .peer
- .send(session.connection_id, proto::RefreshLlmToken {})?;
-
- Ok(())
-}
-
-async fn get_llm_api_token(
- _request: proto::GetLlmToken,
- response: Response<proto::GetLlmToken>,
- session: MessageContext,
-) -> Result<()> {
- let db = session.db().await;
-
- let flags = db.get_user_flags(session.user_id()).await?;
-
- let user_id = session.user_id();
- let user = db
- .get_user_by_id(user_id)
- .await?
- .with_context(|| format!("user {user_id} not found"))?;
-
- if user.accepted_tos_at.is_none() {
- Err(anyhow!("terms of service not accepted"))?
- }
-
- let stripe_client = session
- .app_state
- .stripe_client
- .as_ref()
- .context("failed to retrieve Stripe client")?;
-
- let stripe_billing = session
- .app_state
- .stripe_billing
- .as_ref()
- .context("failed to retrieve Stripe billing object")?;
-
- let billing_customer = if let Some(billing_customer) =
- db.get_billing_customer_by_user_id(user.id).await?
- {
- billing_customer
- } else {
- let customer_id = stripe_billing
- .find_or_create_customer_by_email(user.email_address.as_deref())
- .await?;
-
- find_or_create_billing_customer(&session.app_state, stripe_client.as_ref(), &customer_id)
- .await?
- .context("billing customer not found")?
- };
-
- let billing_subscription =
- if let Some(billing_subscription) = db.get_active_billing_subscription(user.id).await? {
- billing_subscription
- } else {
- let stripe_customer_id =
- StripeCustomerId(billing_customer.stripe_customer_id.clone().into());
-
- let stripe_subscription = stripe_billing
- .subscribe_to_zed_free(stripe_customer_id)
- .await?;
-
- db.create_billing_subscription(&db::CreateBillingSubscriptionParams {
- billing_customer_id: billing_customer.id,
- kind: Some(SubscriptionKind::ZedFree),
- stripe_subscription_id: stripe_subscription.id.to_string(),
- stripe_subscription_status: stripe_subscription.status.into(),
- stripe_cancellation_reason: None,
- stripe_current_period_start: Some(stripe_subscription.current_period_start),
- stripe_current_period_end: Some(stripe_subscription.current_period_end),
- })
- .await?
- };
-
- let billing_preferences = db.get_billing_preferences(user.id).await?;
-
- let token = LlmTokenClaims::create(
- &user,
- session.is_staff(),
- billing_customer,
- billing_preferences,
- &flags,
- billing_subscription,
- session.system_id.clone(),
- &session.app_state.config,
- )?;
- response.send(proto::GetLlmTokenResponse { token })?;
- Ok(())
-}
-
fn to_axum_message(message: TungsteniteMessage) -> anyhow::Result<AxumMessage> {
let message = match message {
TungsteniteMessage::Text(payload) => AxumMessage::Text(payload.as_str().to_string()),
@@ -30,7 +30,19 @@ impl fmt::Display for ZedVersion {
impl ZedVersion {
pub fn can_collaborate(&self) -> bool {
- self.0 >= SemanticVersion::new(0, 157, 0)
+ // v0.198.4 is the first version where we no longer connect to Collab automatically.
+ // We reject any clients older than that to prevent them from connecting to Collab just for authentication.
+ if self.0 < SemanticVersion::new(0, 198, 4) {
+ return false;
+ }
+
+ // Since we hotfixed the changes to no longer connect to Collab automatically to Preview, we also need to reject
+ // versions in the range [v0.199.0, v0.199.1].
+ if self.0 >= SemanticVersion::new(0, 199, 0) && self.0 < SemanticVersion::new(0, 199, 2) {
+ return false;
+ }
+
+ true
}
}
@@ -1,156 +0,0 @@
-use std::sync::Arc;
-
-use anyhow::anyhow;
-use collections::HashMap;
-use stripe::SubscriptionStatus;
-use tokio::sync::RwLock;
-
-use crate::Result;
-use crate::stripe_client::{
- RealStripeClient, StripeAutomaticTax, StripeClient, StripeCreateSubscriptionItems,
- StripeCreateSubscriptionParams, StripeCustomerId, StripePrice, StripePriceId,
- StripeSubscription,
-};
-
-pub struct StripeBilling {
- state: RwLock<StripeBillingState>,
- client: Arc<dyn StripeClient>,
-}
-
-#[derive(Default)]
-struct StripeBillingState {
- prices_by_lookup_key: HashMap<String, StripePrice>,
-}
-
-impl StripeBilling {
- pub fn new(client: Arc<stripe::Client>) -> Self {
- Self {
- client: Arc::new(RealStripeClient::new(client.clone())),
- state: RwLock::default(),
- }
- }
-
- #[cfg(test)]
- pub fn test(client: Arc<crate::stripe_client::FakeStripeClient>) -> Self {
- Self {
- client,
- state: RwLock::default(),
- }
- }
-
- pub fn client(&self) -> &Arc<dyn StripeClient> {
- &self.client
- }
-
- pub async fn initialize(&self) -> Result<()> {
- log::info!("StripeBilling: initializing");
-
- let mut state = self.state.write().await;
-
- let prices = self.client.list_prices().await?;
-
- for price in prices {
- if let Some(lookup_key) = price.lookup_key.clone() {
- state.prices_by_lookup_key.insert(lookup_key, price);
- }
- }
-
- log::info!("StripeBilling: initialized");
-
- Ok(())
- }
-
- pub async fn zed_pro_price_id(&self) -> Result<StripePriceId> {
- self.find_price_id_by_lookup_key("zed-pro").await
- }
-
- pub async fn zed_free_price_id(&self) -> Result<StripePriceId> {
- self.find_price_id_by_lookup_key("zed-free").await
- }
-
- pub async fn find_price_id_by_lookup_key(&self, lookup_key: &str) -> Result<StripePriceId> {
- self.state
- .read()
- .await
- .prices_by_lookup_key
- .get(lookup_key)
- .map(|price| price.id.clone())
- .ok_or_else(|| crate::Error::Internal(anyhow!("no price ID found for {lookup_key:?}")))
- }
-
- pub async fn find_price_by_lookup_key(&self, lookup_key: &str) -> Result<StripePrice> {
- self.state
- .read()
- .await
- .prices_by_lookup_key
- .get(lookup_key)
- .cloned()
- .ok_or_else(|| crate::Error::Internal(anyhow!("no price found for {lookup_key:?}")))
- }
-
- /// Returns the Stripe customer associated with the provided email address, or creates a new customer, if one does
- /// not already exist.
- ///
- /// Always returns a new Stripe customer if the email address is `None`.
- pub async fn find_or_create_customer_by_email(
- &self,
- email_address: Option<&str>,
- ) -> Result<StripeCustomerId> {
- let existing_customer = if let Some(email) = email_address {
- let customers = self.client.list_customers_by_email(email).await?;
-
- customers.first().cloned()
- } else {
- None
- };
-
- let customer_id = if let Some(existing_customer) = existing_customer {
- existing_customer.id
- } else {
- let customer = self
- .client
- .create_customer(crate::stripe_client::CreateCustomerParams {
- email: email_address,
- })
- .await?;
-
- customer.id
- };
-
- Ok(customer_id)
- }
-
- pub async fn subscribe_to_zed_free(
- &self,
- customer_id: StripeCustomerId,
- ) -> Result<StripeSubscription> {
- let zed_free_price_id = self.zed_free_price_id().await?;
-
- let existing_subscriptions = self
- .client
- .list_subscriptions_for_customer(&customer_id)
- .await?;
-
- let existing_active_subscription =
- existing_subscriptions.into_iter().find(|subscription| {
- subscription.status == SubscriptionStatus::Active
- || subscription.status == SubscriptionStatus::Trialing
- });
- if let Some(subscription) = existing_active_subscription {
- return Ok(subscription);
- }
-
- let params = StripeCreateSubscriptionParams {
- customer: customer_id,
- items: vec![StripeCreateSubscriptionItems {
- price: Some(zed_free_price_id),
- quantity: Some(1),
- }],
- automatic_tax: Some(StripeAutomaticTax { enabled: true }),
- };
-
- let subscription = self.client.create_subscription(params).await?;
-
- Ok(subscription)
- }
-}
@@ -1,285 +0,0 @@
-#[cfg(test)]
-mod fake_stripe_client;
-mod real_stripe_client;
-
-use std::collections::HashMap;
-use std::sync::Arc;
-
-use anyhow::Result;
-use async_trait::async_trait;
-
-#[cfg(test)]
-pub use fake_stripe_client::*;
-pub use real_stripe_client::*;
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display, Serialize)]
-pub struct StripeCustomerId(pub Arc<str>);
-
-#[derive(Debug, Clone)]
-pub struct StripeCustomer {
- pub id: StripeCustomerId,
- pub email: Option<String>,
-}
-
-#[derive(Debug)]
-pub struct CreateCustomerParams<'a> {
- pub email: Option<&'a str>,
-}
-
-#[derive(Debug)]
-pub struct UpdateCustomerParams<'a> {
- pub email: Option<&'a str>,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
-pub struct StripeSubscriptionId(pub Arc<str>);
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeSubscription {
- pub id: StripeSubscriptionId,
- pub customer: StripeCustomerId,
- // TODO: Create our own version of this enum.
- pub status: stripe::SubscriptionStatus,
- pub current_period_end: i64,
- pub current_period_start: i64,
- pub items: Vec<StripeSubscriptionItem>,
- pub cancel_at: Option<i64>,
- pub cancellation_details: Option<StripeCancellationDetails>,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
-pub struct StripeSubscriptionItemId(pub Arc<str>);
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeSubscriptionItem {
- pub id: StripeSubscriptionItemId,
- pub price: Option<StripePrice>,
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub struct StripeCancellationDetails {
- pub reason: Option<StripeCancellationDetailsReason>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCancellationDetailsReason {
- CancellationRequested,
- PaymentDisputed,
- PaymentFailed,
-}
-
-#[derive(Debug)]
-pub struct StripeCreateSubscriptionParams {
- pub customer: StripeCustomerId,
- pub items: Vec<StripeCreateSubscriptionItems>,
- pub automatic_tax: Option<StripeAutomaticTax>,
-}
-
-#[derive(Debug)]
-pub struct StripeCreateSubscriptionItems {
- pub price: Option<StripePriceId>,
- pub quantity: Option<u64>,
-}
-
-#[derive(Debug, Clone)]
-pub struct UpdateSubscriptionParams {
- pub items: Option<Vec<UpdateSubscriptionItems>>,
- pub trial_settings: Option<StripeSubscriptionTrialSettings>,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct UpdateSubscriptionItems {
- pub price: Option<StripePriceId>,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeSubscriptionTrialSettings {
- pub end_behavior: StripeSubscriptionTrialSettingsEndBehavior,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeSubscriptionTrialSettingsEndBehavior {
- pub missing_payment_method: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod {
- Cancel,
- CreateInvoice,
- Pause,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display)]
-pub struct StripePriceId(pub Arc<str>);
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripePrice {
- pub id: StripePriceId,
- pub unit_amount: Option<i64>,
- pub lookup_key: Option<String>,
- pub recurring: Option<StripePriceRecurring>,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripePriceRecurring {
- pub meter: Option<String>,
-}
-
-#[derive(Debug, PartialEq, Eq, Hash, Clone, derive_more::Display, Deserialize)]
-pub struct StripeMeterId(pub Arc<str>);
-
-#[derive(Debug, Clone, Deserialize)]
-pub struct StripeMeter {
- pub id: StripeMeterId,
- pub event_name: String,
-}
-
-#[derive(Debug, Serialize)]
-pub struct StripeCreateMeterEventParams<'a> {
- pub identifier: &'a str,
- pub event_name: &'a str,
- pub payload: StripeCreateMeterEventPayload<'a>,
- pub timestamp: Option<i64>,
-}
-
-#[derive(Debug, Serialize)]
-pub struct StripeCreateMeterEventPayload<'a> {
- pub value: u64,
- pub stripe_customer_id: &'a StripeCustomerId,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeBillingAddressCollection {
- Auto,
- Required,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeCustomerUpdate {
- pub address: Option<StripeCustomerUpdateAddress>,
- pub name: Option<StripeCustomerUpdateName>,
- pub shipping: Option<StripeCustomerUpdateShipping>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCustomerUpdateAddress {
- Auto,
- Never,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCustomerUpdateName {
- Auto,
- Never,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCustomerUpdateShipping {
- Auto,
- Never,
-}
-
-#[derive(Debug, Default)]
-pub struct StripeCreateCheckoutSessionParams<'a> {
- pub customer: Option<&'a StripeCustomerId>,
- pub client_reference_id: Option<&'a str>,
- pub mode: Option<StripeCheckoutSessionMode>,
- pub line_items: Option<Vec<StripeCreateCheckoutSessionLineItems>>,
- pub payment_method_collection: Option<StripeCheckoutSessionPaymentMethodCollection>,
- pub subscription_data: Option<StripeCreateCheckoutSessionSubscriptionData>,
- pub success_url: Option<&'a str>,
- pub billing_address_collection: Option<StripeBillingAddressCollection>,
- pub customer_update: Option<StripeCustomerUpdate>,
- pub tax_id_collection: Option<StripeTaxIdCollection>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCheckoutSessionMode {
- Payment,
- Setup,
- Subscription,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeCreateCheckoutSessionLineItems {
- pub price: Option<String>,
- pub quantity: Option<u64>,
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum StripeCheckoutSessionPaymentMethodCollection {
- Always,
- IfRequired,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeCreateCheckoutSessionSubscriptionData {
- pub metadata: Option<HashMap<String, String>>,
- pub trial_period_days: Option<u32>,
- pub trial_settings: Option<StripeSubscriptionTrialSettings>,
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct StripeTaxIdCollection {
- pub enabled: bool,
-}
-
-#[derive(Debug, Clone)]
-pub struct StripeAutomaticTax {
- pub enabled: bool,
-}
-
-#[derive(Debug)]
-pub struct StripeCheckoutSession {
- pub url: Option<String>,
-}
-
-#[async_trait]
-pub trait StripeClient: Send + Sync {
- async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>>;
-
- async fn get_customer(&self, customer_id: &StripeCustomerId) -> Result<StripeCustomer>;
-
- async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer>;
-
- async fn update_customer(
- &self,
- customer_id: &StripeCustomerId,
- params: UpdateCustomerParams<'_>,
- ) -> Result<StripeCustomer>;
-
- async fn list_subscriptions_for_customer(
- &self,
- customer_id: &StripeCustomerId,
- ) -> Result<Vec<StripeSubscription>>;
-
- async fn get_subscription(
- &self,
- subscription_id: &StripeSubscriptionId,
- ) -> Result<StripeSubscription>;
-
- async fn create_subscription(
- &self,
- params: StripeCreateSubscriptionParams,
- ) -> Result<StripeSubscription>;
-
- async fn update_subscription(
- &self,
- subscription_id: &StripeSubscriptionId,
- params: UpdateSubscriptionParams,
- ) -> Result<()>;
-
- async fn cancel_subscription(&self, subscription_id: &StripeSubscriptionId) -> Result<()>;
-
- async fn list_prices(&self) -> Result<Vec<StripePrice>>;
-
- async fn list_meters(&self) -> Result<Vec<StripeMeter>>;
-
- async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()>;
-
- async fn create_checkout_session(
- &self,
- params: StripeCreateCheckoutSessionParams<'_>,
- ) -> Result<StripeCheckoutSession>;
-}
@@ -1,247 +0,0 @@
-use std::sync::Arc;
-
-use anyhow::{Result, anyhow};
-use async_trait::async_trait;
-use chrono::{Duration, Utc};
-use collections::HashMap;
-use parking_lot::Mutex;
-use uuid::Uuid;
-
-use crate::stripe_client::{
- CreateCustomerParams, StripeBillingAddressCollection, StripeCheckoutSession,
- StripeCheckoutSessionMode, StripeCheckoutSessionPaymentMethodCollection, StripeClient,
- StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
- StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
- StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeCustomerUpdate,
- StripeMeter, StripeMeterId, StripePrice, StripePriceId, StripeSubscription,
- StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId, StripeTaxIdCollection,
- UpdateCustomerParams, UpdateSubscriptionParams,
-};
-
-#[derive(Debug, Clone)]
-pub struct StripeCreateMeterEventCall {
- pub identifier: Arc<str>,
- pub event_name: Arc<str>,
- pub value: u64,
- pub stripe_customer_id: StripeCustomerId,
- pub timestamp: Option<i64>,
-}
-
-#[derive(Debug, Clone)]
-pub struct StripeCreateCheckoutSessionCall {
- pub customer: Option<StripeCustomerId>,
- pub client_reference_id: Option<String>,
- pub mode: Option<StripeCheckoutSessionMode>,
- pub line_items: Option<Vec<StripeCreateCheckoutSessionLineItems>>,
- pub payment_method_collection: Option<StripeCheckoutSessionPaymentMethodCollection>,
- pub subscription_data: Option<StripeCreateCheckoutSessionSubscriptionData>,
- pub success_url: Option<String>,
- pub billing_address_collection: Option<StripeBillingAddressCollection>,
- pub customer_update: Option<StripeCustomerUpdate>,
- pub tax_id_collection: Option<StripeTaxIdCollection>,
-}
-
-pub struct FakeStripeClient {
- pub customers: Arc<Mutex<HashMap<StripeCustomerId, StripeCustomer>>>,
- pub subscriptions: Arc<Mutex<HashMap<StripeSubscriptionId, StripeSubscription>>>,
- pub update_subscription_calls:
- Arc<Mutex<Vec<(StripeSubscriptionId, UpdateSubscriptionParams)>>>,
- pub prices: Arc<Mutex<HashMap<StripePriceId, StripePrice>>>,
- pub meters: Arc<Mutex<HashMap<StripeMeterId, StripeMeter>>>,
- pub create_meter_event_calls: Arc<Mutex<Vec<StripeCreateMeterEventCall>>>,
- pub create_checkout_session_calls: Arc<Mutex<Vec<StripeCreateCheckoutSessionCall>>>,
-}
-
-impl FakeStripeClient {
- pub fn new() -> Self {
- Self {
- customers: Arc::new(Mutex::new(HashMap::default())),
- subscriptions: Arc::new(Mutex::new(HashMap::default())),
- update_subscription_calls: Arc::new(Mutex::new(Vec::new())),
- prices: Arc::new(Mutex::new(HashMap::default())),
- meters: Arc::new(Mutex::new(HashMap::default())),
- create_meter_event_calls: Arc::new(Mutex::new(Vec::new())),
- create_checkout_session_calls: Arc::new(Mutex::new(Vec::new())),
- }
- }
-}
-
-#[async_trait]
-impl StripeClient for FakeStripeClient {
- async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>> {
- Ok(self
- .customers
- .lock()
- .values()
- .filter(|customer| customer.email.as_deref() == Some(email))
- .cloned()
- .collect())
- }
-
- async fn get_customer(&self, customer_id: &StripeCustomerId) -> Result<StripeCustomer> {
- self.customers
- .lock()
- .get(customer_id)
- .cloned()
- .ok_or_else(|| anyhow!("no customer found for {customer_id:?}"))
- }
-
- async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer> {
- let customer = StripeCustomer {
- id: StripeCustomerId(format!("cus_{}", Uuid::new_v4()).into()),
- email: params.email.map(|email| email.to_string()),
- };
-
- self.customers
- .lock()
- .insert(customer.id.clone(), customer.clone());
-
- Ok(customer)
- }
-
- async fn update_customer(
- &self,
- customer_id: &StripeCustomerId,
- params: UpdateCustomerParams<'_>,
- ) -> Result<StripeCustomer> {
- let mut customers = self.customers.lock();
- if let Some(customer) = customers.get_mut(customer_id) {
- if let Some(email) = params.email {
- customer.email = Some(email.to_string());
- }
- Ok(customer.clone())
- } else {
- Err(anyhow!("no customer found for {customer_id:?}"))
- }
- }
-
- async fn list_subscriptions_for_customer(
- &self,
- customer_id: &StripeCustomerId,
- ) -> Result<Vec<StripeSubscription>> {
- let subscriptions = self
- .subscriptions
- .lock()
- .values()
- .filter(|subscription| subscription.customer == *customer_id)
- .cloned()
- .collect();
-
- Ok(subscriptions)
- }
-
- async fn get_subscription(
- &self,
- subscription_id: &StripeSubscriptionId,
- ) -> Result<StripeSubscription> {
- self.subscriptions
- .lock()
- .get(subscription_id)
- .cloned()
- .ok_or_else(|| anyhow!("no subscription found for {subscription_id:?}"))
- }
-
- async fn create_subscription(
- &self,
- params: StripeCreateSubscriptionParams,
- ) -> Result<StripeSubscription> {
- let now = Utc::now();
-
- let subscription = StripeSubscription {
- id: StripeSubscriptionId(format!("sub_{}", Uuid::new_v4()).into()),
- customer: params.customer,
- status: stripe::SubscriptionStatus::Active,
- current_period_start: now.timestamp(),
- current_period_end: (now + Duration::days(30)).timestamp(),
- items: params
- .items
- .into_iter()
- .map(|item| StripeSubscriptionItem {
- id: StripeSubscriptionItemId(format!("si_{}", Uuid::new_v4()).into()),
- price: item
- .price
- .and_then(|price_id| self.prices.lock().get(&price_id).cloned()),
- })
- .collect(),
- cancel_at: None,
- cancellation_details: None,
- };
-
- self.subscriptions
- .lock()
- .insert(subscription.id.clone(), subscription.clone());
-
- Ok(subscription)
- }
-
- async fn update_subscription(
- &self,
- subscription_id: &StripeSubscriptionId,
- params: UpdateSubscriptionParams,
- ) -> Result<()> {
- let subscription = self.get_subscription(subscription_id).await?;
-
- self.update_subscription_calls
- .lock()
- .push((subscription.id, params));
-
- Ok(())
- }
-
- async fn cancel_subscription(&self, subscription_id: &StripeSubscriptionId) -> Result<()> {
- // TODO: Implement fake subscription cancellation.
- let _ = subscription_id;
-
- Ok(())
- }
-
- async fn list_prices(&self) -> Result<Vec<StripePrice>> {
- let prices = self.prices.lock().values().cloned().collect();
-
- Ok(prices)
- }
-
- async fn list_meters(&self) -> Result<Vec<StripeMeter>> {
- let meters = self.meters.lock().values().cloned().collect();
-
- Ok(meters)
- }
-
- async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()> {
- self.create_meter_event_calls
- .lock()
- .push(StripeCreateMeterEventCall {
- identifier: params.identifier.into(),
- event_name: params.event_name.into(),
- value: params.payload.value,
- stripe_customer_id: params.payload.stripe_customer_id.clone(),
- timestamp: params.timestamp,
- });
-
- Ok(())
- }
-
- async fn create_checkout_session(
- &self,
- params: StripeCreateCheckoutSessionParams<'_>,
- ) -> Result<StripeCheckoutSession> {
- self.create_checkout_session_calls
- .lock()
- .push(StripeCreateCheckoutSessionCall {
- customer: params.customer.cloned(),
- client_reference_id: params.client_reference_id.map(|id| id.to_string()),
- mode: params.mode,
- line_items: params.line_items,
- payment_method_collection: params.payment_method_collection,
- subscription_data: params.subscription_data,
- success_url: params.success_url.map(|url| url.to_string()),
- billing_address_collection: params.billing_address_collection,
- customer_update: params.customer_update,
- tax_id_collection: params.tax_id_collection,
- });
-
- Ok(StripeCheckoutSession {
- url: Some("https://checkout.stripe.com/c/pay/cs_test_1".to_string()),
- })
- }
-}
@@ -1,612 +0,0 @@
-use std::str::FromStr as _;
-use std::sync::Arc;
-
-use anyhow::{Context as _, Result, anyhow};
-use async_trait::async_trait;
-use serde::{Deserialize, Serialize};
-use stripe::{
- CancellationDetails, CancellationDetailsReason, CheckoutSession, CheckoutSessionMode,
- CheckoutSessionPaymentMethodCollection, CreateCheckoutSession, CreateCheckoutSessionLineItems,
- CreateCheckoutSessionSubscriptionData, CreateCheckoutSessionSubscriptionDataTrialSettings,
- CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior,
- CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod,
- CreateCustomer, CreateSubscriptionAutomaticTax, Customer, CustomerId, ListCustomers, Price,
- PriceId, Recurring, Subscription, SubscriptionId, SubscriptionItem, SubscriptionItemId,
- UpdateCustomer, UpdateSubscriptionItems, UpdateSubscriptionTrialSettings,
- UpdateSubscriptionTrialSettingsEndBehavior,
- UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
-};
-
-use crate::stripe_client::{
- CreateCustomerParams, StripeAutomaticTax, StripeBillingAddressCollection,
- StripeCancellationDetails, StripeCancellationDetailsReason, StripeCheckoutSession,
- StripeCheckoutSessionMode, StripeCheckoutSessionPaymentMethodCollection, StripeClient,
- StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
- StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
- StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeCustomerUpdate,
- StripeCustomerUpdateAddress, StripeCustomerUpdateName, StripeCustomerUpdateShipping,
- StripeMeter, StripePrice, StripePriceId, StripePriceRecurring, StripeSubscription,
- StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId,
- StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior,
- StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, StripeTaxIdCollection,
- UpdateCustomerParams, UpdateSubscriptionParams,
-};
-
-pub struct RealStripeClient {
- client: Arc<stripe::Client>,
-}
-
-impl RealStripeClient {
- pub fn new(client: Arc<stripe::Client>) -> Self {
- Self { client }
- }
-}
-
-#[async_trait]
-impl StripeClient for RealStripeClient {
- async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>> {
- let response = Customer::list(
- &self.client,
- &ListCustomers {
- email: Some(email),
- ..Default::default()
- },
- )
- .await?;
-
- Ok(response
- .data
- .into_iter()
- .map(StripeCustomer::from)
- .collect())
- }
-
- async fn get_customer(&self, customer_id: &StripeCustomerId) -> Result<StripeCustomer> {
- let customer_id = customer_id.try_into()?;
-
- let customer = Customer::retrieve(&self.client, &customer_id, &[]).await?;
-
- Ok(StripeCustomer::from(customer))
- }
-
- async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer> {
- let customer = Customer::create(
- &self.client,
- CreateCustomer {
- email: params.email,
- ..Default::default()
- },
- )
- .await?;
-
- Ok(StripeCustomer::from(customer))
- }
-
- async fn update_customer(
- &self,
- customer_id: &StripeCustomerId,
- params: UpdateCustomerParams<'_>,
- ) -> Result<StripeCustomer> {
- let customer = Customer::update(
- &self.client,
- &customer_id.try_into()?,
- UpdateCustomer {
- email: params.email,
- ..Default::default()
- },
- )
- .await?;
-
- Ok(StripeCustomer::from(customer))
- }
-
- async fn list_subscriptions_for_customer(
- &self,
- customer_id: &StripeCustomerId,
- ) -> Result<Vec<StripeSubscription>> {
- let customer_id = customer_id.try_into()?;
-
- let subscriptions = stripe::Subscription::list(
- &self.client,
- &stripe::ListSubscriptions {
- customer: Some(customer_id),
- status: None,
- ..Default::default()
- },
- )
- .await?;
-
- Ok(subscriptions
- .data
- .into_iter()
- .map(StripeSubscription::from)
- .collect())
- }
-
- async fn get_subscription(
- &self,
- subscription_id: &StripeSubscriptionId,
- ) -> Result<StripeSubscription> {
- let subscription_id = subscription_id.try_into()?;
-
- let subscription = Subscription::retrieve(&self.client, &subscription_id, &[]).await?;
-
- Ok(StripeSubscription::from(subscription))
- }
-
- async fn create_subscription(
- &self,
- params: StripeCreateSubscriptionParams,
- ) -> Result<StripeSubscription> {
- let customer_id = params.customer.try_into()?;
-
- let mut create_subscription = stripe::CreateSubscription::new(customer_id);
- create_subscription.items = Some(
- params
- .items
- .into_iter()
- .map(|item| stripe::CreateSubscriptionItems {
- price: item.price.map(|price| price.to_string()),
- quantity: item.quantity,
- ..Default::default()
- })
- .collect(),
- );
- create_subscription.automatic_tax = params.automatic_tax.map(Into::into);
-
- let subscription = Subscription::create(&self.client, create_subscription).await?;
-
- Ok(StripeSubscription::from(subscription))
- }
-
- async fn update_subscription(
- &self,
- subscription_id: &StripeSubscriptionId,
- params: UpdateSubscriptionParams,
- ) -> Result<()> {
- let subscription_id = subscription_id.try_into()?;
-
- stripe::Subscription::update(
- &self.client,
- &subscription_id,
- stripe::UpdateSubscription {
- items: params.items.map(|items| {
- items
- .into_iter()
- .map(|item| UpdateSubscriptionItems {
- price: item.price.map(|price| price.to_string()),
- ..Default::default()
- })
- .collect()
- }),
- trial_settings: params.trial_settings.map(Into::into),
- ..Default::default()
- },
- )
- .await?;
-
- Ok(())
- }
-
- async fn cancel_subscription(&self, subscription_id: &StripeSubscriptionId) -> Result<()> {
- let subscription_id = subscription_id.try_into()?;
-
- Subscription::cancel(
- &self.client,
- &subscription_id,
- stripe::CancelSubscription {
- invoice_now: None,
- ..Default::default()
- },
- )
- .await?;
-
- Ok(())
- }
-
- async fn list_prices(&self) -> Result<Vec<StripePrice>> {
- let response = stripe::Price::list(
- &self.client,
- &stripe::ListPrices {
- limit: Some(100),
- ..Default::default()
- },
- )
- .await?;
-
- Ok(response.data.into_iter().map(StripePrice::from).collect())
- }
-
- async fn list_meters(&self) -> Result<Vec<StripeMeter>> {
- #[derive(Serialize)]
- struct Params {
- #[serde(skip_serializing_if = "Option::is_none")]
- limit: Option<u64>,
- }
-
- let response = self
- .client
- .get_query::<stripe::List<StripeMeter>, _>(
- "/billing/meters",
- Params { limit: Some(100) },
- )
- .await?;
-
- Ok(response.data)
- }
-
- async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()> {
- #[derive(Deserialize)]
- struct StripeMeterEvent {
- pub identifier: String,
- }
-
- let identifier = params.identifier;
- match self
- .client
- .post_form::<StripeMeterEvent, _>("/billing/meter_events", params)
- .await
- {
- Ok(_event) => Ok(()),
- Err(stripe::StripeError::Stripe(error)) => {
- if error.http_status == 400
- && error
- .message
- .as_ref()
- .map_or(false, |message| message.contains(identifier))
- {
- Ok(())
- } else {
- Err(anyhow!(stripe::StripeError::Stripe(error)))
- }
- }
- Err(error) => Err(anyhow!("failed to create meter event: {error:?}")),
- }
- }
-
- async fn create_checkout_session(
- &self,
- params: StripeCreateCheckoutSessionParams<'_>,
- ) -> Result<StripeCheckoutSession> {
- let params = params.try_into()?;
- let session = CheckoutSession::create(&self.client, params).await?;
-
- Ok(session.into())
- }
-}
-
-impl From<CustomerId> for StripeCustomerId {
- fn from(value: CustomerId) -> Self {
- Self(value.as_str().into())
- }
-}
-
-impl TryFrom<StripeCustomerId> for CustomerId {
- type Error = anyhow::Error;
-
- fn try_from(value: StripeCustomerId) -> Result<Self, Self::Error> {
- Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
- }
-}
-
-impl TryFrom<&StripeCustomerId> for CustomerId {
- type Error = anyhow::Error;
-
- fn try_from(value: &StripeCustomerId) -> Result<Self, Self::Error> {
- Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
- }
-}
-
-impl From<Customer> for StripeCustomer {
- fn from(value: Customer) -> Self {
- StripeCustomer {
- id: value.id.into(),
- email: value.email,
- }
- }
-}
-
-impl From<SubscriptionId> for StripeSubscriptionId {
- fn from(value: SubscriptionId) -> Self {
- Self(value.as_str().into())
- }
-}
-
-impl TryFrom<&StripeSubscriptionId> for SubscriptionId {
- type Error = anyhow::Error;
-
- fn try_from(value: &StripeSubscriptionId) -> Result<Self, Self::Error> {
- Self::from_str(value.0.as_ref()).context("failed to parse Stripe subscription ID")
- }
-}
-
-impl From<Subscription> for StripeSubscription {
- fn from(value: Subscription) -> Self {
- Self {
- id: value.id.into(),
- customer: value.customer.id().into(),
- status: value.status,
- current_period_start: value.current_period_start,
- current_period_end: value.current_period_end,
- items: value.items.data.into_iter().map(Into::into).collect(),
- cancel_at: value.cancel_at,
- cancellation_details: value.cancellation_details.map(Into::into),
- }
- }
-}
-
-impl From<CancellationDetails> for StripeCancellationDetails {
- fn from(value: CancellationDetails) -> Self {
- Self {
- reason: value.reason.map(Into::into),
- }
- }
-}
-
-impl From<CancellationDetailsReason> for StripeCancellationDetailsReason {
- fn from(value: CancellationDetailsReason) -> Self {
- match value {
- CancellationDetailsReason::CancellationRequested => Self::CancellationRequested,
- CancellationDetailsReason::PaymentDisputed => Self::PaymentDisputed,
- CancellationDetailsReason::PaymentFailed => Self::PaymentFailed,
- }
- }
-}
-
-impl From<SubscriptionItemId> for StripeSubscriptionItemId {
- fn from(value: SubscriptionItemId) -> Self {
- Self(value.as_str().into())
- }
-}
-
-impl From<SubscriptionItem> for StripeSubscriptionItem {
- fn from(value: SubscriptionItem) -> Self {
- Self {
- id: value.id.into(),
- price: value.price.map(Into::into),
- }
- }
-}
-
-impl From<StripeAutomaticTax> for CreateSubscriptionAutomaticTax {
- fn from(value: StripeAutomaticTax) -> Self {
- Self {
- enabled: value.enabled,
- liability: None,
- }
- }
-}
-
-impl From<StripeSubscriptionTrialSettings> for UpdateSubscriptionTrialSettings {
- fn from(value: StripeSubscriptionTrialSettings) -> Self {
- Self {
- end_behavior: value.end_behavior.into(),
- }
- }
-}
-
-impl From<StripeSubscriptionTrialSettingsEndBehavior>
- for UpdateSubscriptionTrialSettingsEndBehavior
-{
- fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
- Self {
- missing_payment_method: value.missing_payment_method.into(),
- }
- }
-}
-
-impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
- for UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod
-{
- fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
- match value {
- StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
- StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
- Self::CreateInvoice
- }
- StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
- }
- }
-}
-
-impl From<PriceId> for StripePriceId {
- fn from(value: PriceId) -> Self {
- Self(value.as_str().into())
- }
-}
-
-impl TryFrom<StripePriceId> for PriceId {
- type Error = anyhow::Error;
-
- fn try_from(value: StripePriceId) -> Result<Self, Self::Error> {
- Self::from_str(value.0.as_ref()).context("failed to parse Stripe price ID")
- }
-}
-
-impl From<Price> for StripePrice {
- fn from(value: Price) -> Self {
- Self {
- id: value.id.into(),
- unit_amount: value.unit_amount,
- lookup_key: value.lookup_key,
- recurring: value.recurring.map(StripePriceRecurring::from),
- }
- }
-}
-
-impl From<Recurring> for StripePriceRecurring {
- fn from(value: Recurring) -> Self {
- Self { meter: value.meter }
- }
-}
-
-impl<'a> TryFrom<StripeCreateCheckoutSessionParams<'a>> for CreateCheckoutSession<'a> {
- type Error = anyhow::Error;
-
- fn try_from(value: StripeCreateCheckoutSessionParams<'a>) -> Result<Self, Self::Error> {
- Ok(Self {
- customer: value
- .customer
- .map(|customer_id| customer_id.try_into())
- .transpose()?,
- client_reference_id: value.client_reference_id,
- mode: value.mode.map(Into::into),
- line_items: value
- .line_items
- .map(|line_items| line_items.into_iter().map(Into::into).collect()),
- payment_method_collection: value.payment_method_collection.map(Into::into),
- subscription_data: value.subscription_data.map(Into::into),
- success_url: value.success_url,
- billing_address_collection: value.billing_address_collection.map(Into::into),
- customer_update: value.customer_update.map(Into::into),
- tax_id_collection: value.tax_id_collection.map(Into::into),
- ..Default::default()
- })
- }
-}
-
-impl From<StripeCheckoutSessionMode> for CheckoutSessionMode {
- fn from(value: StripeCheckoutSessionMode) -> Self {
- match value {
- StripeCheckoutSessionMode::Payment => Self::Payment,
- StripeCheckoutSessionMode::Setup => Self::Setup,
- StripeCheckoutSessionMode::Subscription => Self::Subscription,
- }
- }
-}
-
-impl From<StripeCreateCheckoutSessionLineItems> for CreateCheckoutSessionLineItems {
- fn from(value: StripeCreateCheckoutSessionLineItems) -> Self {
- Self {
- price: value.price,
- quantity: value.quantity,
- ..Default::default()
- }
- }
-}
-
-impl From<StripeCheckoutSessionPaymentMethodCollection> for CheckoutSessionPaymentMethodCollection {
- fn from(value: StripeCheckoutSessionPaymentMethodCollection) -> Self {
- match value {
- StripeCheckoutSessionPaymentMethodCollection::Always => Self::Always,
- StripeCheckoutSessionPaymentMethodCollection::IfRequired => Self::IfRequired,
- }
- }
-}
-
-impl From<StripeCreateCheckoutSessionSubscriptionData> for CreateCheckoutSessionSubscriptionData {
- fn from(value: StripeCreateCheckoutSessionSubscriptionData) -> Self {
- Self {
- trial_period_days: value.trial_period_days,
- trial_settings: value.trial_settings.map(Into::into),
- metadata: value.metadata,
- ..Default::default()
- }
- }
-}
-
-impl From<StripeSubscriptionTrialSettings> for CreateCheckoutSessionSubscriptionDataTrialSettings {
- fn from(value: StripeSubscriptionTrialSettings) -> Self {
- Self {
- end_behavior: value.end_behavior.into(),
- }
- }
-}
-
-impl From<StripeSubscriptionTrialSettingsEndBehavior>
- for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior
-{
- fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
- Self {
- missing_payment_method: value.missing_payment_method.into(),
- }
- }
-}
-
-impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
- for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod
-{
- fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
- match value {
- StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
- StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
- Self::CreateInvoice
- }
- StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
- }
- }
-}
-
-impl From<CheckoutSession> for StripeCheckoutSession {
- fn from(value: CheckoutSession) -> Self {
- Self { url: value.url }
- }
-}
-
-impl From<StripeBillingAddressCollection> for stripe::CheckoutSessionBillingAddressCollection {
- fn from(value: StripeBillingAddressCollection) -> Self {
- match value {
- StripeBillingAddressCollection::Auto => {
- stripe::CheckoutSessionBillingAddressCollection::Auto
- }
- StripeBillingAddressCollection::Required => {
- stripe::CheckoutSessionBillingAddressCollection::Required
- }
- }
- }
-}
-
-impl From<StripeCustomerUpdateAddress> for stripe::CreateCheckoutSessionCustomerUpdateAddress {
- fn from(value: StripeCustomerUpdateAddress) -> Self {
- match value {
- StripeCustomerUpdateAddress::Auto => {
- stripe::CreateCheckoutSessionCustomerUpdateAddress::Auto
- }
- StripeCustomerUpdateAddress::Never => {
- stripe::CreateCheckoutSessionCustomerUpdateAddress::Never
- }
- }
- }
-}
-
-impl From<StripeCustomerUpdateName> for stripe::CreateCheckoutSessionCustomerUpdateName {
- fn from(value: StripeCustomerUpdateName) -> Self {
- match value {
- StripeCustomerUpdateName::Auto => stripe::CreateCheckoutSessionCustomerUpdateName::Auto,
- StripeCustomerUpdateName::Never => {
- stripe::CreateCheckoutSessionCustomerUpdateName::Never
- }
- }
- }
-}
-
-impl From<StripeCustomerUpdateShipping> for stripe::CreateCheckoutSessionCustomerUpdateShipping {
- fn from(value: StripeCustomerUpdateShipping) -> Self {
- match value {
- StripeCustomerUpdateShipping::Auto => {
- stripe::CreateCheckoutSessionCustomerUpdateShipping::Auto
- }
- StripeCustomerUpdateShipping::Never => {
- stripe::CreateCheckoutSessionCustomerUpdateShipping::Never
- }
- }
- }
-}
-
-impl From<StripeCustomerUpdate> for stripe::CreateCheckoutSessionCustomerUpdate {
- fn from(value: StripeCustomerUpdate) -> Self {
- stripe::CreateCheckoutSessionCustomerUpdate {
- address: value.address.map(Into::into),
- name: value.name.map(Into::into),
- shipping: value.shipping.map(Into::into),
- }
- }
-}
-
-impl From<StripeTaxIdCollection> for stripe::CreateCheckoutSessionTaxIdCollection {
- fn from(value: StripeTaxIdCollection) -> Self {
- stripe::CreateCheckoutSessionTaxIdCollection {
- enabled: value.enabled,
- }
- }
-}
@@ -8,7 +8,6 @@ mod channel_buffer_tests;
mod channel_guest_tests;
mod channel_message_tests;
mod channel_tests;
-// mod debug_panel_tests;
mod editor_tests;
mod following_tests;
mod git_tests;
@@ -18,7 +17,6 @@ mod random_channel_buffer_tests;
mod random_project_collaboration_tests;
mod randomized_test_helpers;
mod remote_editing_collaboration_tests;
-mod stripe_billing_tests;
mod test_server;
use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
@@ -1,123 +0,0 @@
-use std::sync::Arc;
-
-use pretty_assertions::assert_eq;
-
-use crate::stripe_billing::StripeBilling;
-use crate::stripe_client::{FakeStripeClient, StripePrice, StripePriceId, StripePriceRecurring};
-
-fn make_stripe_billing() -> (StripeBilling, Arc<FakeStripeClient>) {
- let stripe_client = Arc::new(FakeStripeClient::new());
- let stripe_billing = StripeBilling::test(stripe_client.clone());
-
- (stripe_billing, stripe_client)
-}
-
-#[gpui::test]
-async fn test_initialize() {
- let (stripe_billing, stripe_client) = make_stripe_billing();
-
- // Add test prices
- let price1 = StripePrice {
- id: StripePriceId("price_1".into()),
- unit_amount: Some(1_000),
- lookup_key: Some("zed-pro".to_string()),
- recurring: None,
- };
- let price2 = StripePrice {
- id: StripePriceId("price_2".into()),
- unit_amount: Some(0),
- lookup_key: Some("zed-free".to_string()),
- recurring: None,
- };
- let price3 = StripePrice {
- id: StripePriceId("price_3".into()),
- unit_amount: Some(500),
- lookup_key: None,
- recurring: Some(StripePriceRecurring {
- meter: Some("meter_1".to_string()),
- }),
- };
- stripe_client
- .prices
- .lock()
- .insert(price1.id.clone(), price1);
- stripe_client
- .prices
- .lock()
- .insert(price2.id.clone(), price2);
- stripe_client
- .prices
- .lock()
- .insert(price3.id.clone(), price3);
-
- // Initialize the billing system
- stripe_billing.initialize().await.unwrap();
-
- // Verify that prices can be found by lookup key
- let zed_pro_price_id = stripe_billing.zed_pro_price_id().await.unwrap();
- assert_eq!(zed_pro_price_id.to_string(), "price_1");
-
- let zed_free_price_id = stripe_billing.zed_free_price_id().await.unwrap();
- assert_eq!(zed_free_price_id.to_string(), "price_2");
-
- // Verify that a price can be found by lookup key
- let zed_pro_price = stripe_billing
- .find_price_by_lookup_key("zed-pro")
- .await
- .unwrap();
- assert_eq!(zed_pro_price.id.to_string(), "price_1");
- assert_eq!(zed_pro_price.unit_amount, Some(1_000));
-
- // Verify that finding a non-existent lookup key returns an error
- let result = stripe_billing
- .find_price_by_lookup_key("non-existent")
- .await;
- assert!(result.is_err());
-}
-
-#[gpui::test]
-async fn test_find_or_create_customer_by_email() {
- let (stripe_billing, stripe_client) = make_stripe_billing();
-
- // Create a customer with an email that doesn't yet correspond to a customer.
- {
- let email = "user@example.com";
-
- let customer_id = stripe_billing
- .find_or_create_customer_by_email(Some(email))
- .await
- .unwrap();
-
- let customer = stripe_client
- .customers
- .lock()
- .get(&customer_id)
- .unwrap()
- .clone();
- assert_eq!(customer.email.as_deref(), Some(email));
- }
-
- // Create a customer with an email that corresponds to an existing customer.
- {
- let email = "user2@example.com";
-
- let existing_customer_id = stripe_billing
- .find_or_create_customer_by_email(Some(email))
- .await
- .unwrap();
-
- let customer_id = stripe_billing
- .find_or_create_customer_by_email(Some(email))
- .await
- .unwrap();
- assert_eq!(customer_id, existing_customer_id);
-
- let customer = stripe_client
- .customers
- .lock()
- .get(&customer_id)
- .unwrap()
- .clone();
- assert_eq!(customer.email.as_deref(), Some(email));
- }
-}
@@ -1,4 +1,3 @@
-use crate::stripe_client::FakeStripeClient;
use crate::{
AppState, Config,
db::{NewUserParams, UserId, tests::TestDb},
@@ -566,12 +565,8 @@ impl TestServer {
) -> Arc<AppState> {
Arc::new(AppState {
db: test_db.db().clone(),
- llm_db: None,
livekit_client: Some(Arc::new(livekit_test_server.create_api_client())),
blob_store_client: None,
- real_stripe_client: None,
- stripe_client: Some(Arc::new(FakeStripeClient::new())),
- stripe_billing: None,
executor,
kinesis_client: None,
config: Config {
@@ -608,7 +603,6 @@ impl TestServer {
auto_join_channel_id: None,
migrations_path: None,
seed_path: None,
- stripe_api_key: None,
supermaven_admin_api_key: None,
user_backfiller_github_access_token: None,
kinesis_region: None,
@@ -674,7 +674,7 @@ impl ChatPanel {
})
})
.when_some(message_id, |el, message_id| {
- let this = cx.entity().clone();
+ let this = cx.entity();
el.child(
self.render_popover_button(
@@ -95,7 +95,7 @@ pub fn init(cx: &mut App) {
.and_then(|room| room.read(cx).channel_id());
if let Some(channel_id) = channel_id {
- let workspace = cx.entity().clone();
+ let workspace = cx.entity();
window.defer(cx, move |window, cx| {
ChannelView::open(channel_id, None, workspace, window, cx)
.detach_and_log_err(cx)
@@ -1142,7 +1142,7 @@ impl CollabPanel {
window: &mut Window,
cx: &mut Context<Self>,
) {
- let this = cx.entity().clone();
+ let this = cx.entity();
if !(role == proto::ChannelRole::Guest
|| role == proto::ChannelRole::Talker
|| role == proto::ChannelRole::Member)
@@ -1272,7 +1272,7 @@ impl CollabPanel {
.channel_for_id(clipboard.channel_id)
.map(|channel| channel.name.clone())
});
- let this = cx.entity().clone();
+ let this = cx.entity();
let context_menu = ContextMenu::build(window, cx, |mut context_menu, window, cx| {
if self.has_subchannels(ix) {
@@ -1439,7 +1439,7 @@ impl CollabPanel {
window: &mut Window,
cx: &mut Context<Self>,
) {
- let this = cx.entity().clone();
+ let this = cx.entity();
let in_room = ActiveCall::global(cx).read(cx).room().is_some();
let context_menu = ContextMenu::build(window, cx, |mut context_menu, _, _| {
@@ -586,7 +586,7 @@ impl ChannelModalDelegate {
return;
};
let user_id = membership.user.id;
- let picker = cx.entity().clone();
+ let picker = cx.entity();
let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
let role = membership.role;
@@ -321,7 +321,7 @@ impl NotificationPanel {
.justify_end()
.child(Button::new("decline", "Decline").on_click({
let notification = notification.clone();
- let entity = cx.entity().clone();
+ let entity = cx.entity();
move |_, _, cx| {
entity.update(cx, |this, cx| {
this.respond_to_notification(
@@ -334,7 +334,7 @@ impl NotificationPanel {
}))
.child(Button::new("accept", "Accept").on_click({
let notification = notification.clone();
- let entity = cx.entity().clone();
+ let entity = cx.entity();
move |_, _, cx| {
entity.update(cx, |this, cx| {
this.respond_to_notification(
@@ -12,6 +12,8 @@ minidumper.workspace = true
paths.workspace = true
release_channel.workspace = true
smol.workspace = true
+serde.workspace = true
+serde_json.workspace = true
workspace-hack.workspace = true
[lints]
@@ -2,15 +2,17 @@ use crash_handler::CrashHandler;
use log::info;
use minidumper::{Client, LoopAction, MinidumpBinary};
use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
+use serde::{Deserialize, Serialize};
use std::{
env,
- fs::File,
+ fs::{self, File},
io,
+ panic::Location,
path::{Path, PathBuf},
process::{self, Command},
sync::{
- LazyLock, OnceLock,
+ Arc, OnceLock,
atomic::{AtomicBool, Ordering},
},
thread,
@@ -18,19 +20,17 @@ use std::{
};
// set once the crash handler has initialized and the client has connected to it
-pub static CRASH_HANDLER: AtomicBool = AtomicBool::new(false);
+pub static CRASH_HANDLER: OnceLock<Arc<Client>> = OnceLock::new();
// set when the first minidump request is made to avoid generating duplicate crash reports
pub static REQUESTED_MINIDUMP: AtomicBool = AtomicBool::new(false);
-const CRASH_HANDLER_TIMEOUT: Duration = Duration::from_secs(60);
+const CRASH_HANDLER_PING_TIMEOUT: Duration = Duration::from_secs(60);
+const CRASH_HANDLER_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
-pub static GENERATE_MINIDUMPS: LazyLock<bool> = LazyLock::new(|| {
- *RELEASE_CHANNEL != ReleaseChannel::Dev || env::var("ZED_GENERATE_MINIDUMPS").is_ok()
-});
-
-pub async fn init(id: String) {
- if !*GENERATE_MINIDUMPS {
+pub async fn init(crash_init: InitCrashHandler) {
+ if *RELEASE_CHANNEL == ReleaseChannel::Dev && env::var("ZED_GENERATE_MINIDUMPS").is_err() {
return;
}
+
let exe = env::current_exe().expect("unable to find ourselves");
let zed_pid = process::id();
// TODO: we should be able to get away with using 1 crash-handler process per machine,
@@ -61,9 +61,11 @@ pub async fn init(id: String) {
smol::Timer::after(retry_frequency).await;
}
let client = maybe_client.unwrap();
- client.send_message(1, id).unwrap(); // set session id on the server
+ client
+ .send_message(1, serde_json::to_vec(&crash_init).unwrap())
+ .unwrap();
- let client = std::sync::Arc::new(client);
+ let client = Arc::new(client);
let handler = crash_handler::CrashHandler::attach(unsafe {
let client = client.clone();
crash_handler::make_crash_event(move |crash_context: &crash_handler::CrashContext| {
@@ -72,7 +74,6 @@ pub async fn init(id: String) {
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
- client.send_message(2, "mistakes were made").unwrap();
client.ping().unwrap();
client.request_dump(crash_context).is_ok()
} else {
@@ -87,7 +88,7 @@ pub async fn init(id: String) {
{
handler.set_ptracer(Some(server_pid));
}
- CRASH_HANDLER.store(true, Ordering::Release);
+ CRASH_HANDLER.set(client.clone()).ok();
std::mem::forget(handler);
info!("crash handler registered");
@@ -98,14 +99,43 @@ pub async fn init(id: String) {
}
pub struct CrashServer {
- session_id: OnceLock<String>,
+ initialization_params: OnceLock<InitCrashHandler>,
+ panic_info: OnceLock<CrashPanic>,
+ has_connection: Arc<AtomicBool>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+pub struct CrashInfo {
+ pub init: InitCrashHandler,
+ pub panic: Option<CrashPanic>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+pub struct InitCrashHandler {
+ pub session_id: String,
+ pub zed_version: String,
+ pub release_channel: String,
+ pub commit_sha: String,
+ // pub gpu: String,
+}
+
+#[derive(Deserialize, Serialize, Debug, Clone)]
+pub struct CrashPanic {
+ pub message: String,
+ pub span: String,
}
impl minidumper::ServerHandler for CrashServer {
fn create_minidump_file(&self) -> Result<(File, PathBuf), io::Error> {
- let err_message = "Need to send a message with the ID upon starting the crash handler";
+ let err_message = "Missing initialization data";
let dump_path = paths::logs_dir()
- .join(self.session_id.get().expect(err_message))
+ .join(
+ &self
+ .initialization_params
+ .get()
+ .expect(err_message)
+ .session_id,
+ )
.with_extension("dmp");
let file = File::create(&dump_path)?;
Ok((file, dump_path))
@@ -122,38 +152,71 @@ impl minidumper::ServerHandler for CrashServer {
info!("failed to write minidump: {:#}", e);
}
}
+
+ let crash_info = CrashInfo {
+ init: self
+ .initialization_params
+ .get()
+ .expect("not initialized")
+ .clone(),
+ panic: self.panic_info.get().cloned(),
+ };
+
+ let crash_data_path = paths::logs_dir()
+ .join(&crash_info.init.session_id)
+ .with_extension("json");
+
+ fs::write(crash_data_path, serde_json::to_vec(&crash_info).unwrap()).ok();
+
LoopAction::Exit
}
fn on_message(&self, kind: u32, buffer: Vec<u8>) {
- let message = String::from_utf8(buffer).expect("invalid utf-8");
- info!("kind: {kind}, message: {message}",);
- if kind == 1 {
- self.session_id
- .set(message)
- .expect("session id already initialized");
+ match kind {
+ 1 => {
+ let init_data =
+ serde_json::from_slice::<InitCrashHandler>(&buffer).expect("invalid init data");
+ self.initialization_params
+ .set(init_data)
+ .expect("already initialized");
+ }
+ 2 => {
+ let panic_data =
+ serde_json::from_slice::<CrashPanic>(&buffer).expect("invalid panic data");
+ self.panic_info.set(panic_data).expect("already panicked");
+ }
+ _ => {
+ panic!("invalid message kind");
+ }
}
}
- fn on_client_disconnected(&self, clients: usize) -> LoopAction {
- info!("client disconnected, {clients} remaining");
- if clients == 0 {
- LoopAction::Exit
- } else {
- LoopAction::Continue
- }
+ fn on_client_disconnected(&self, _clients: usize) -> LoopAction {
+ LoopAction::Exit
}
-}
-pub fn handle_panic() {
- if !*GENERATE_MINIDUMPS {
- return;
+ fn on_client_connected(&self, _clients: usize) -> LoopAction {
+ self.has_connection.store(true, Ordering::SeqCst);
+ LoopAction::Continue
}
+}
+
+pub fn handle_panic(message: String, span: Option<&Location>) {
+ let span = span
+ .map(|loc| format!("{}:{}", loc.file(), loc.line()))
+ .unwrap_or_default();
+
// wait 500ms for the crash handler process to start up
// if it's still not there just write panic info and no minidump
let retry_frequency = Duration::from_millis(100);
for _ in 0..5 {
- if CRASH_HANDLER.load(Ordering::Acquire) {
+ if let Some(client) = CRASH_HANDLER.get() {
+ client
+ .send_message(
+ 2,
+ serde_json::to_vec(&CrashPanic { message, span }).unwrap(),
+ )
+ .ok();
log::error!("triggering a crash to generate a minidump...");
#[cfg(target_os = "linux")]
CrashHandler.simulate_signal(crash_handler::Signal::Trap as u32);
@@ -170,14 +233,30 @@ pub fn crash_server(socket: &Path) {
log::info!("Couldn't create socket, there may already be a running crash server");
return;
};
- let ab = AtomicBool::new(false);
+
+ let shutdown = Arc::new(AtomicBool::new(false));
+ let has_connection = Arc::new(AtomicBool::new(false));
+
+ std::thread::spawn({
+ let shutdown = shutdown.clone();
+ let has_connection = has_connection.clone();
+ move || {
+ std::thread::sleep(CRASH_HANDLER_CONNECT_TIMEOUT);
+ if !has_connection.load(Ordering::SeqCst) {
+ shutdown.store(true, Ordering::SeqCst);
+ }
+ }
+ });
+
server
.run(
Box::new(CrashServer {
- session_id: OnceLock::new(),
+ initialization_params: OnceLock::new(),
+ panic_info: OnceLock::new(),
+ has_connection,
}),
- &ab,
- Some(CRASH_HANDLER_TIMEOUT),
+ &shutdown,
+ Some(CRASH_HANDLER_PING_TIMEOUT),
)
.expect("failed to run server");
}
@@ -291,7 +291,7 @@ pub(crate) fn new_debugger_pane(
let Some(project) = project.upgrade() else {
return ControlFlow::Break(());
};
- let this_pane = cx.entity().clone();
+ let this_pane = cx.entity();
let item = if tab.pane == this_pane {
pane.item_for_index(tab.ix)
} else {
@@ -502,7 +502,7 @@ pub(crate) fn new_debugger_pane(
.on_drag(
DraggedTab {
item: item.boxed_clone(),
- pane: cx.entity().clone(),
+ pane: cx.entity(),
detail: 0,
is_active: selected,
ix,
@@ -971,7 +971,7 @@ async fn active_diagnostics_dismiss_after_invalidation(cx: &mut TestAppContext)
let mut cx = EditorTestContext::new(cx).await;
let lsp_store =
- cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+ cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
cx.set_state(indoc! {"
Λfn func(abc def: i32) -> u32 {
@@ -1065,7 +1065,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let lsp_store =
- cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+ cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
cx.set_state(indoc! {"
Λfn func(abc def: i32) -> u32 {
@@ -1239,7 +1239,7 @@ async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
}
"});
let lsp_store =
- cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+ cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
cx.update(|_, cx| {
lsp_store.update(cx, |lsp_store, cx| {
@@ -1293,7 +1293,7 @@ async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext)
fn Β«testΒ»() { println!(); }
"});
let lsp_store =
- cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+ cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
cx.update(|_, cx| {
lsp_store.update(cx, |lsp_store, cx| {
lsp_store.update_diagnostics(
@@ -1450,7 +1450,7 @@ async fn go_to_diagnostic_with_severity(cx: &mut TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let lsp_store =
- cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+ cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
cx.set_state(indoc! {"error warning info hiΛnt"});
@@ -127,7 +127,7 @@ impl Render for EditPredictionButton {
}),
);
}
- let this = cx.entity().clone();
+ let this = cx.entity();
div().child(
PopoverMenu::new("copilot")
@@ -182,7 +182,7 @@ impl Render for EditPredictionButton {
let icon = status.to_icon();
let tooltip_text = status.to_tooltip();
let has_menu = status.has_menu();
- let this = cx.entity().clone();
+ let this = cx.entity();
let fs = self.fs.clone();
return div().child(
@@ -331,7 +331,7 @@ impl Render for EditPredictionButton {
})
});
- let this = cx.entity().clone();
+ let this = cx.entity();
let mut popover_menu = PopoverMenu::new("zeta")
.menu(move |window, cx| {
@@ -1039,9 +1039,7 @@ pub struct Editor {
inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
hard_wrap: Option<usize>,
-
- // TODO: make this a access method
- pub project: Option<Entity<Project>>,
+ project: Option<Entity<Project>>,
semantics_provider: Option<Rc<dyn SemanticsProvider>>,
completion_provider: Option<Rc<dyn CompletionProvider>>,
collaboration_hub: Option<Box<dyn CollaborationHub>>,
@@ -2326,7 +2324,7 @@ impl Editor {
editor.go_to_active_debug_line(window, cx);
if let Some(buffer) = buffer.read(cx).as_singleton() {
- if let Some(project) = editor.project.as_ref() {
+ if let Some(project) = editor.project() {
let handle = project.update(cx, |project, cx| {
project.register_buffer_with_language_servers(&buffer, cx)
});
@@ -2371,6 +2369,34 @@ impl Editor {
.is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
}
+ pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
+ if self
+ .selections
+ .pending
+ .as_ref()
+ .is_some_and(|pending_selection| {
+ let snapshot = self.buffer().read(cx).snapshot(cx);
+ pending_selection
+ .selection
+ .range()
+ .includes(&range, &snapshot)
+ })
+ {
+ return true;
+ }
+
+ self.selections
+ .disjoint_in_range::<usize>(range.clone(), cx)
+ .into_iter()
+ .any(|selection| {
+ // This is needed to cover a corner case, if we just check for an existing
+ // selection in the fold range, having a cursor at the start of the fold
+ // marks it as selected. Non-empty selections don't cause this.
+ let length = selection.end - selection.start;
+ length > 0
+ })
+ }
+
pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
self.key_context_internal(self.has_active_edit_prediction(), window, cx)
}
@@ -2626,6 +2652,10 @@ impl Editor {
&self.buffer
}
+ pub fn project(&self) -> Option<&Entity<Project>> {
+ self.project.as_ref()
+ }
+
pub fn workspace(&self) -> Option<Entity<Workspace>> {
self.workspace.as_ref()?.0.upgrade()
}
@@ -5212,7 +5242,7 @@ impl Editor {
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
cx: &mut Context<Editor>,
) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
- let Some(project) = self.project.as_ref() else {
+ let Some(project) = self.project() else {
return HashMap::default();
};
let project = project.read(cx);
@@ -5294,7 +5324,7 @@ impl Editor {
return None;
}
- let project = self.project.as_ref()?;
+ let project = self.project()?;
let position = self.selections.newest_anchor().head();
let (buffer, buffer_position) = self
.buffer
@@ -6141,7 +6171,7 @@ impl Editor {
cx: &mut App,
) -> Task<Vec<task::DebugScenario>> {
maybe!({
- let project = self.project.as_ref()?;
+ let project = self.project()?;
let dap_store = project.read(cx).dap_store();
let mut scenarios = vec![];
let resolved_tasks = resolved_tasks.as_ref()?;
@@ -7907,7 +7937,7 @@ impl Editor {
let snapshot = self.snapshot(window, cx);
let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
- let Some(project) = self.project.as_ref() else {
+ let Some(project) = self.project() else {
return breakpoint_display_points;
};
@@ -10501,7 +10531,7 @@ impl Editor {
) {
if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
let project_path = buffer.read(cx).project_path(cx)?;
- let project = self.project.as_ref()?.read(cx);
+ let project = self.project()?.read(cx);
let entry = project.entry_for_path(&project_path, cx)?;
let parent = match &entry.canonical_path {
Some(canonical_path) => canonical_path.to_path_buf(),
@@ -14875,7 +14905,7 @@ impl Editor {
self.clear_tasks();
return Task::ready(());
}
- let project = self.project.as_ref().map(Entity::downgrade);
+ let project = self.project().map(Entity::downgrade);
let task_sources = self.lsp_task_sources(cx);
let multi_buffer = self.buffer.downgrade();
cx.spawn_in(window, async move |editor, cx| {
@@ -15879,10 +15909,15 @@ impl Editor {
.text_for_range(location.range.clone())
.collect::<String>()
})
+ .filter(|text| !text.contains('\n'))
.unique()
.take(3)
.join(", ");
- format!("{tab_kind} for {target}")
+ if target.is_empty() {
+ tab_kind.to_owned()
+ } else {
+ format!("{tab_kind} for {target}")
+ }
})
.context("buffer title")?;
@@ -16087,10 +16122,15 @@ impl Editor {
.text_for_range(location.range.clone())
.collect::<String>()
})
+ .filter(|text| !text.contains('\n'))
.unique()
.take(3)
.join(", ");
- let title = format!("References to {target}");
+ let title = if target.is_empty() {
+ "References".to_owned()
+ } else {
+ format!("References to {target}")
+ };
Self::open_locations_in_multibuffer(
workspace,
locations,
@@ -17054,7 +17094,7 @@ impl Editor {
if !pull_diagnostics_settings.enabled {
return None;
}
- let project = self.project.as_ref()?.downgrade();
+ let project = self.project()?.downgrade();
let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
let mut buffers = self.buffer.read(cx).all_buffers();
if let Some(buffer_id) = buffer_id {
@@ -18018,7 +18058,7 @@ impl Editor {
hunks: impl Iterator<Item = MultiBufferDiffHunk>,
cx: &mut App,
) -> Option<()> {
- let project = self.project.as_ref()?;
+ let project = self.project()?;
let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
let diff = self.buffer.read(cx).diff_for(buffer_id)?;
let buffer_snapshot = buffer.read(cx).snapshot();
@@ -18678,7 +18718,7 @@ impl Editor {
self.active_excerpt(cx).and_then(|(_, buffer, _)| {
let buffer = buffer.read(cx);
if let Some(project_path) = buffer.project_path(cx) {
- let project = self.project.as_ref()?.read(cx);
+ let project = self.project()?.read(cx);
project.absolute_path(&project_path, cx)
} else {
buffer
@@ -18691,7 +18731,7 @@ impl Editor {
fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
self.active_excerpt(cx).and_then(|(_, buffer, _)| {
let project_path = buffer.read(cx).project_path(cx)?;
- let project = self.project.as_ref()?.read(cx);
+ let project = self.project()?.read(cx);
let entry = project.entry_for_path(&project_path, cx)?;
let path = entry.path.to_path_buf();
Some(path)
@@ -18912,7 +18952,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
- if let Some(project) = self.project.as_ref() {
+ if let Some(project) = self.project() {
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
return;
};
@@ -19028,7 +19068,7 @@ impl Editor {
return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
};
- let Some(project) = self.project.as_ref() else {
+ let Some(project) = self.project() else {
return Task::ready(Err(anyhow!("editor does not have project")));
};
@@ -21015,7 +21055,7 @@ impl Editor {
cx: &mut Context<Self>,
) {
let workspace = self.workspace();
- let project = self.project.as_ref();
+ let project = self.project();
let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
let mut tasks = Vec::new();
for (buffer_id, changes) in revert_changes {
@@ -132,6 +132,10 @@ pub struct StatusBar {
///
/// Default: true
pub active_language_button: bool,
+ /// Whether to show the cursor position button in the status bar.
+ ///
+ /// Default: true
+ pub cursor_position_button: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@@ -585,6 +589,10 @@ pub struct StatusBarContent {
///
/// Default: true
pub active_language_button: Option<bool>,
+ /// Whether to show the cursor position button in the status bar.
+ ///
+ /// Default: true
+ pub cursor_position_button: Option<bool>,
}
// Toolbar related settings
@@ -74,7 +74,7 @@ fn test_edit_events(cx: &mut TestAppContext) {
let editor1 = cx.add_window({
let events = events.clone();
|window, cx| {
- let entity = cx.entity().clone();
+ let entity = cx.entity();
cx.subscribe_in(
&entity,
window,
@@ -95,7 +95,7 @@ fn test_edit_events(cx: &mut TestAppContext) {
let events = events.clone();
|window, cx| {
cx.subscribe_in(
- &cx.entity().clone(),
+ &cx.entity(),
window,
move |_, _, event: &EditorEvent, _, _| match event {
EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
@@ -15082,7 +15082,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
let mut cx = EditorTestContext::new(cx).await;
let lsp_store =
- cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+ cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
cx.set_state(indoc! {"
Λfn func(abc def: i32) -> u32 {
@@ -19634,13 +19634,8 @@ fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
editor.insert_creases(Some(crease), cx);
let snapshot = editor.snapshot(window, cx);
- let _div = snapshot.render_crease_toggle(
- MultiBufferRow(1),
- false,
- cx.entity().clone(),
- window,
- cx,
- );
+ let _div =
+ snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
snapshot
})
.unwrap();
@@ -7815,7 +7815,7 @@ impl Element for EditorElement {
min_lines,
max_lines,
} => {
- let editor_handle = cx.entity().clone();
+ let editor_handle = cx.entity();
let max_line_number_width =
self.max_line_number_width(&editor.snapshot(window, cx), window);
window.request_measured_layout(
@@ -251,7 +251,7 @@ fn show_hover(
let (excerpt_id, _, _) = editor.buffer().read(cx).excerpt_containing(anchor, cx)?;
- let language_registry = editor.project.as_ref()?.read(cx).languages().clone();
+ let language_registry = editor.project()?.read(cx).languages().clone();
let provider = editor.semantics_provider.clone()?;
if !ignore_timeout {
@@ -654,6 +654,10 @@ impl Item for Editor {
}
}
+ fn suggested_filename(&self, cx: &App) -> SharedString {
+ self.buffer.read(cx).title(cx).to_string().into()
+ }
+
fn tab_icon(&self, _: &Window, cx: &App) -> Option<Icon> {
ItemSettings::get_global(cx)
.file_icons
@@ -674,7 +678,7 @@ impl Item for Editor {
let buffer = buffer.read(cx);
let path = buffer.project_path(cx)?;
let buffer_id = buffer.remote_id();
- let project = self.project.as_ref()?.read(cx);
+ let project = self.project()?.read(cx);
let entry = project.entry_for_path(&path, cx)?;
let (repo, repo_path) = project
.git_store()
@@ -51,7 +51,7 @@ pub(super) fn refresh_linked_ranges(
if editor.pending_rename.is_some() {
return None;
}
- let project = editor.project.as_ref()?.downgrade();
+ let project = editor.project()?.downgrade();
editor.linked_editing_range_task = Some(cx.spawn_in(window, async move |editor, cx| {
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
@@ -169,7 +169,7 @@ impl Editor {
else {
return;
};
- let Some(lsp_store) = self.project.as_ref().map(|p| p.read(cx).lsp_store()) else {
+ let Some(lsp_store) = self.project().map(|p| p.read(cx).lsp_store()) else {
return;
};
let task = lsp_store.update(cx, |lsp_store, cx| {
@@ -297,9 +297,8 @@ impl EditorTestContext {
pub fn set_head_text(&mut self, diff_base: &str) {
self.cx.run_until_parked();
- let fs = self.update_editor(|editor, _, cx| {
- editor.project.as_ref().unwrap().read(cx).fs().as_fake()
- });
+ let fs =
+ self.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
fs.set_head_for_repo(
&Self::root_path().join(".git"),
@@ -311,18 +310,16 @@ impl EditorTestContext {
pub fn clear_index_text(&mut self) {
self.cx.run_until_parked();
- let fs = self.update_editor(|editor, _, cx| {
- editor.project.as_ref().unwrap().read(cx).fs().as_fake()
- });
+ let fs =
+ self.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
fs.set_index_for_repo(&Self::root_path().join(".git"), &[]);
self.cx.run_until_parked();
}
pub fn set_index_text(&mut self, diff_base: &str) {
self.cx.run_until_parked();
- let fs = self.update_editor(|editor, _, cx| {
- editor.project.as_ref().unwrap().read(cx).fs().as_fake()
- });
+ let fs =
+ self.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
fs.set_index_for_repo(
&Self::root_path().join(".git"),
@@ -333,9 +330,8 @@ impl EditorTestContext {
#[track_caller]
pub fn assert_index_text(&mut self, expected: Option<&str>) {
- let fs = self.update_editor(|editor, _, cx| {
- editor.project.as_ref().unwrap().read(cx).fs().as_fake()
- });
+ let fs =
+ self.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
let mut found = None;
fs.with_git_state(&Self::root_path().join(".git"), false, |git_state| {
@@ -28,7 +28,6 @@ pub struct ExtensionHostProxy {
snippet_proxy: RwLock<Option<Arc<dyn ExtensionSnippetProxy>>>,
slash_command_proxy: RwLock<Option<Arc<dyn ExtensionSlashCommandProxy>>>,
context_server_proxy: RwLock<Option<Arc<dyn ExtensionContextServerProxy>>>,
- indexed_docs_provider_proxy: RwLock<Option<Arc<dyn ExtensionIndexedDocsProviderProxy>>>,
debug_adapter_provider_proxy: RwLock<Option<Arc<dyn ExtensionDebugAdapterProviderProxy>>>,
}
@@ -54,7 +53,6 @@ impl ExtensionHostProxy {
snippet_proxy: RwLock::default(),
slash_command_proxy: RwLock::default(),
context_server_proxy: RwLock::default(),
- indexed_docs_provider_proxy: RwLock::default(),
debug_adapter_provider_proxy: RwLock::default(),
}
}
@@ -87,14 +85,6 @@ impl ExtensionHostProxy {
self.context_server_proxy.write().replace(Arc::new(proxy));
}
- pub fn register_indexed_docs_provider_proxy(
- &self,
- proxy: impl ExtensionIndexedDocsProviderProxy,
- ) {
- self.indexed_docs_provider_proxy
- .write()
- .replace(Arc::new(proxy));
- }
pub fn register_debug_adapter_proxy(&self, proxy: impl ExtensionDebugAdapterProviderProxy) {
self.debug_adapter_provider_proxy
.write()
@@ -408,30 +398,6 @@ impl ExtensionContextServerProxy for ExtensionHostProxy {
}
}
-pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static {
- fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>);
-
- fn unregister_indexed_docs_provider(&self, provider_id: Arc<str>);
-}
-
-impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy {
- fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
- let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else {
- return;
- };
-
- proxy.register_indexed_docs_provider(extension, provider_id)
- }
-
- fn unregister_indexed_docs_provider(&self, provider_id: Arc<str>) {
- let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else {
- return;
- };
-
- proxy.unregister_indexed_docs_provider(provider_id)
- }
-}
-
pub trait ExtensionDebugAdapterProviderProxy: Send + Sync + 'static {
fn register_debug_adapter(
&self,
@@ -84,8 +84,6 @@ pub struct ExtensionManifest {
#[serde(default)]
pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
#[serde(default)]
- pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
- #[serde(default)]
pub snippets: Option<PathBuf>,
#[serde(default)]
pub capabilities: Vec<ExtensionCapability>,
@@ -195,9 +193,6 @@ pub struct SlashCommandManifestEntry {
pub requires_argument: bool,
}
-#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
-pub struct IndexedDocsProviderEntry {}
-
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct DebugAdapterManifestEntry {
pub schema_path: Option<PathBuf>,
@@ -271,7 +266,6 @@ fn manifest_from_old_manifest(
language_servers: Default::default(),
context_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
- indexed_docs_providers: BTreeMap::default(),
snippets: None,
capabilities: Vec::new(),
debug_adapters: Default::default(),
@@ -304,7 +298,6 @@ mod tests {
language_servers: BTreeMap::default(),
context_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
- indexed_docs_providers: BTreeMap::default(),
snippets: None,
capabilities: vec![],
debug_adapters: Default::default(),
@@ -144,10 +144,6 @@ fn extension_provides(manifest: &ExtensionManifest) -> BTreeSet<ExtensionProvide
provides.insert(ExtensionProvides::ContextServers);
}
- if !manifest.indexed_docs_providers.is_empty() {
- provides.insert(ExtensionProvides::IndexedDocsProviders);
- }
-
if manifest.snippets.is_some() {
provides.insert(ExtensionProvides::Snippets);
}
@@ -132,7 +132,6 @@ fn manifest() -> ExtensionManifest {
.collect(),
context_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
- indexed_docs_providers: BTreeMap::default(),
snippets: None,
capabilities: vec![ExtensionCapability::ProcessExec(
extension::ProcessExecCapability {
@@ -108,7 +108,6 @@ mod tests {
language_servers: BTreeMap::default(),
context_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
- indexed_docs_providers: BTreeMap::default(),
snippets: None,
capabilities: vec![],
debug_adapters: Default::default(),
@@ -16,9 +16,9 @@ pub use extension::ExtensionManifest;
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
use extension::{
ExtensionContextServerProxy, ExtensionDebugAdapterProviderProxy, ExtensionEvents,
- ExtensionGrammarProxy, ExtensionHostProxy, ExtensionIndexedDocsProviderProxy,
- ExtensionLanguageProxy, ExtensionLanguageServerProxy, ExtensionSlashCommandProxy,
- ExtensionSnippetProxy, ExtensionThemeProxy,
+ ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy,
+ ExtensionLanguageServerProxy, ExtensionSlashCommandProxy, ExtensionSnippetProxy,
+ ExtensionThemeProxy,
};
use fs::{Fs, RemoveOptions};
use futures::future::join_all;
@@ -1192,10 +1192,6 @@ impl ExtensionStore {
for (command_name, _) in &extension.manifest.slash_commands {
self.proxy.unregister_slash_command(command_name.clone());
}
- for (provider_id, _) in &extension.manifest.indexed_docs_providers {
- self.proxy
- .unregister_indexed_docs_provider(provider_id.clone());
- }
}
self.wasm_extensions
@@ -1399,11 +1395,6 @@ impl ExtensionStore {
.register_context_server(extension.clone(), id.clone(), cx);
}
- for (provider_id, _provider) in &manifest.indexed_docs_providers {
- this.proxy
- .register_indexed_docs_provider(extension.clone(), provider_id.clone());
- }
-
for (debug_adapter, meta) in &manifest.debug_adapters {
let mut path = root_dir.clone();
path.push(Path::new(manifest.id.as_ref()));
@@ -160,7 +160,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
context_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
- indexed_docs_providers: BTreeMap::default(),
snippets: None,
capabilities: Vec::new(),
debug_adapters: Default::default(),
@@ -191,7 +190,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
context_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
- indexed_docs_providers: BTreeMap::default(),
snippets: None,
capabilities: Vec::new(),
debug_adapters: Default::default(),
@@ -371,7 +369,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
context_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
- indexed_docs_providers: BTreeMap::default(),
snippets: None,
capabilities: Vec::new(),
debug_adapters: Default::default(),
@@ -703,7 +703,7 @@ impl ExtensionsPage {
extension: &ExtensionMetadata,
cx: &mut Context<Self>,
) -> ExtensionCard {
- let this = cx.entity().clone();
+ let this = cx.entity();
let status = Self::extension_status(&extension.id, cx);
let has_dev_extension = Self::dev_extension_exists(&extension.id, cx);
@@ -112,7 +112,7 @@ fn excerpt_for_buffer_updated(
}
fn buffer_added(editor: &mut Editor, buffer: Entity<Buffer>, cx: &mut Context<Editor>) {
- let Some(project) = &editor.project else {
+ let Some(project) = editor.project() else {
return;
};
let git_store = project.read(cx).git_store().clone();
@@ -469,7 +469,7 @@ pub(crate) fn resolve_conflict(
let Some((workspace, project, multibuffer, buffer)) = editor
.update(cx, |editor, cx| {
let workspace = editor.workspace()?;
- let project = editor.project.clone()?;
+ let project = editor.project()?.clone();
let multibuffer = editor.buffer().clone();
let buffer_id = resolved_conflict.ours.end.buffer_id?;
let buffer = multibuffer.read(cx).buffer(buffer_id)?;
@@ -3410,7 +3410,7 @@ impl GitPanel {
* MAX_PANEL_EDITOR_LINES
+ gap;
- let git_panel = cx.entity().clone();
+ let git_panel = cx.entity();
let display_name = SharedString::from(Arc::from(
active_repository
.read(cx)
@@ -3,7 +3,7 @@ use std::any::Any;
use ::settings::Settings;
use command_palette_hooks::CommandPaletteFilter;
use commit_modal::CommitModal;
-use editor::{Editor, EditorElement, EditorStyle, actions::DiffClipboardWithSelectionData};
+use editor::{Editor, actions::DiffClipboardWithSelectionData};
mod blame_ui;
use git::{
repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
@@ -11,12 +11,11 @@ use git::{
};
use git_panel_settings::GitPanelSettings;
use gpui::{
- Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, TextStyle,
- Window, actions,
+ Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Window,
+ actions,
};
use onboarding::GitOnboardingModal;
use project_diff::ProjectDiff;
-use theme::ThemeSettings;
use ui::prelude::*;
use workspace::{ModalView, Workspace};
use zed_actions;
@@ -637,7 +636,7 @@ impl GitCloneModal {
pub fn show(panel: Entity<GitPanel>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let repo_input = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
- editor.set_placeholder_text("Enter repository", cx);
+ editor.set_placeholder_text("Enter repository URLβ¦", cx);
editor
});
let focus_handle = repo_input.focus_handle(cx);
@@ -650,46 +649,6 @@ impl GitCloneModal {
focus_handle,
}
}
-
- fn render_editor(&self, window: &Window, cx: &App) -> impl IntoElement {
- let settings = ThemeSettings::get_global(cx);
- let theme = cx.theme();
-
- let text_style = TextStyle {
- color: cx.theme().colors().text,
- font_family: settings.buffer_font.family.clone(),
- font_features: settings.buffer_font.features.clone(),
- font_size: settings.buffer_font_size(cx).into(),
- font_weight: settings.buffer_font.weight,
- line_height: relative(settings.buffer_line_height.value()),
- background_color: Some(theme.colors().editor_background),
- ..Default::default()
- };
-
- let element = EditorElement::new(
- &self.repo_input,
- EditorStyle {
- background: theme.colors().editor_background,
- local_player: theme.players().local(),
- text: text_style,
- ..Default::default()
- },
- );
-
- div()
- .rounded_md()
- .p_1()
- .border_1()
- .border_color(theme.colors().border_variant)
- .when(
- self.repo_input
- .focus_handle(cx)
- .contains_focused(window, cx),
- |this| this.border_color(theme.colors().border_focused),
- )
- .child(element)
- .bg(theme.colors().editor_background)
- }
}
impl Focusable for GitCloneModal {
@@ -699,12 +658,42 @@ impl Focusable for GitCloneModal {
}
impl Render for GitCloneModal {
- fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
- .size_full()
- .w(rems(34.))
.elevation_3(cx)
- .child(self.render_editor(window, cx))
+ .w(rems(34.))
+ .flex_1()
+ .overflow_hidden()
+ .child(
+ div()
+ .w_full()
+ .p_2()
+ .border_b_1()
+ .border_color(cx.theme().colors().border_variant)
+ .child(self.repo_input.clone()),
+ )
+ .child(
+ h_flex()
+ .w_full()
+ .p_2()
+ .gap_0p5()
+ .rounded_b_sm()
+ .bg(cx.theme().colors().editor_background)
+ .child(
+ Label::new("Clone a repository from GitHub or other sources.")
+ .color(Color::Muted)
+ .size(LabelSize::Small),
+ )
+ .child(
+ Button::new("learn-more", "Learn More")
+ .label_size(LabelSize::Small)
+ .icon(IconName::ArrowUpRight)
+ .icon_size(IconSize::XSmall)
+ .on_click(|_, _, cx| {
+ cx.open_url("https://github.com/git-guides/git-clone");
+ }),
+ ),
+ )
.on_action(cx.listener(|_, _: &menu::Cancel, _, cx| {
cx.emit(DismissEvent);
}))
@@ -1,4 +1,4 @@
-use editor::{Editor, MultiBufferSnapshot};
+use editor::{Editor, EditorSettings, MultiBufferSnapshot};
use gpui::{App, Entity, FocusHandle, Focusable, Subscription, Task, WeakEntity};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -209,6 +209,13 @@ impl CursorPosition {
impl Render for CursorPosition {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ if !EditorSettings::get_global(cx)
+ .status_bar
+ .cursor_position_button
+ {
+ return div();
+ }
+
div().when_some(self.position, |el, position| {
let mut text = format!(
"{}{FILE_ROW_COLUMN_DELIMITER}{}",
@@ -595,9 +595,7 @@ impl Render for TextInput {
.w_full()
.p(px(4.))
.bg(white())
- .child(TextElement {
- input: cx.entity().clone(),
- }),
+ .child(TextElement { input: cx.entity() }),
)
}
}
@@ -816,8 +816,9 @@ impl App {
pub fn prompt_for_new_path(
&self,
directory: &Path,
+ suggested_name: Option<&str>,
) -> oneshot::Receiver<Result<Option<PathBuf>>> {
- self.platform.prompt_for_new_path(directory)
+ self.platform.prompt_for_new_path(directory, suggested_name)
}
/// Reveals the specified path at the platform level, such as in Finder on macOS.
@@ -220,7 +220,11 @@ pub(crate) trait Platform: 'static {
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
- fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
+ fn prompt_for_new_path(
+ &self,
+ directory: &Path,
+ suggested_name: Option<&str>,
+ ) -> oneshot::Receiver<Result<Option<PathBuf>>>;
fn can_select_mixed_files_and_dirs(&self) -> bool;
fn reveal_path(&self, path: &Path);
fn open_with_system(&self, path: &Path);
@@ -327,26 +327,35 @@ impl<P: LinuxClient + 'static> Platform for P {
done_rx
}
- fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>> {
+ fn prompt_for_new_path(
+ &self,
+ directory: &Path,
+ suggested_name: Option<&str>,
+ ) -> oneshot::Receiver<Result<Option<PathBuf>>> {
let (done_tx, done_rx) = oneshot::channel();
#[cfg(not(any(feature = "wayland", feature = "x11")))]
- let _ = (done_tx.send(Ok(None)), directory);
+ let _ = (done_tx.send(Ok(None)), directory, suggested_name);
#[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor()
.spawn({
let directory = directory.to_owned();
+ let suggested_name = suggested_name.map(|s| s.to_owned());
async move {
- let request = match ashpd::desktop::file_chooser::SaveFileRequest::default()
- .modal(true)
- .title("Save File")
- .current_folder(directory)
- .expect("pathbuf should not be nul terminated")
- .send()
- .await
- {
+ let mut request_builder =
+ ashpd::desktop::file_chooser::SaveFileRequest::default()
+ .modal(true)
+ .title("Save File")
+ .current_folder(directory)
+ .expect("pathbuf should not be nul terminated");
+
+ if let Some(suggested_name) = suggested_name {
+ request_builder = request_builder.current_name(suggested_name.as_str());
+ }
+
+ let request = match request_builder.send().await {
Ok(request) => request,
Err(err) => {
let result = match err {
@@ -314,6 +314,15 @@ impl MetalRenderer {
}
fn update_path_intermediate_textures(&mut self, size: Size<DevicePixels>) {
+ // We are uncertain when this happens, but sometimes size can be 0 here. Most likely before
+ // the layout pass on window creation. Zero-sized texture creation causes SIGABRT.
+ // https://github.com/zed-industries/zed/issues/36229
+ if size.width.0 <= 0 || size.height.0 <= 0 {
+ self.path_intermediate_texture = None;
+ self.path_intermediate_msaa_texture = None;
+ return;
+ }
+
let texture_descriptor = metal::TextureDescriptor::new();
texture_descriptor.set_width(size.width.0 as u64);
texture_descriptor.set_height(size.height.0 as u64);
@@ -737,8 +737,13 @@ impl Platform for MacPlatform {
done_rx
}
- fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>> {
+ fn prompt_for_new_path(
+ &self,
+ directory: &Path,
+ suggested_name: Option<&str>,
+ ) -> oneshot::Receiver<Result<Option<PathBuf>>> {
let directory = directory.to_owned();
+ let suggested_name = suggested_name.map(|s| s.to_owned());
let (done_tx, done_rx) = oneshot::channel();
self.foreground_executor()
.spawn(async move {
@@ -748,6 +753,11 @@ impl Platform for MacPlatform {
let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
panel.setDirectoryURL(url);
+ if let Some(suggested_name) = suggested_name {
+ let name_string = ns_string(&suggested_name);
+ let _: () = msg_send![panel, setNameFieldStringValue: name_string];
+ }
+
let done_tx = Cell::new(Some(done_tx));
let block = ConcreteBlock::new(move |response: NSModalResponse| {
let mut result = None;
@@ -336,6 +336,7 @@ impl Platform for TestPlatform {
fn prompt_for_new_path(
&self,
directory: &std::path::Path,
+ _suggested_name: Option<&str>,
) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
let (tx, rx) = oneshot::channel();
self.background_executor()
@@ -490,13 +490,18 @@ impl Platform for WindowsPlatform {
rx
}
- fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Result<Option<PathBuf>>> {
+ fn prompt_for_new_path(
+ &self,
+ directory: &Path,
+ suggested_name: Option<&str>,
+ ) -> Receiver<Result<Option<PathBuf>>> {
let directory = directory.to_owned();
+ let suggested_name = suggested_name.map(|s| s.to_owned());
let (tx, rx) = oneshot::channel();
let window = self.find_current_active_window();
self.foreground_executor()
.spawn(async move {
- let _ = tx.send(file_save_dialog(directory, window));
+ let _ = tx.send(file_save_dialog(directory, suggested_name, window));
})
.detach();
@@ -804,7 +809,11 @@ fn file_open_dialog(
Ok(Some(paths))
}
-fn file_save_dialog(directory: PathBuf, window: Option<HWND>) -> Result<Option<PathBuf>> {
+fn file_save_dialog(
+ directory: PathBuf,
+ suggested_name: Option<String>,
+ window: Option<HWND>,
+) -> Result<Option<PathBuf>> {
let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
if !directory.to_string_lossy().is_empty() {
if let Some(full_path) = directory.canonicalize().log_err() {
@@ -815,6 +824,11 @@ fn file_save_dialog(directory: PathBuf, window: Option<HWND>) -> Result<Option<P
unsafe { dialog.SetFolder(&path_item).log_err() };
}
}
+
+ if let Some(suggested_name) = suggested_name {
+ unsafe { dialog.SetFileName(&HSTRING::from(suggested_name)).log_err() };
+ }
+
unsafe {
dialog.SetFileTypes(&[Common::COMDLG_FILTERSPEC {
pszName: windows::core::w!("All files"),
@@ -6,7 +6,7 @@ Icons are a big part of Zed, and they're how we convey hundreds of actions witho
When introducing a new icon, it's important to ensure consistency with the existing set, which follows these guidelines:
1. The SVG view box should be 16x16.
-2. For outlined icons, use a 1.5px stroke width.
+2. For outlined icons, use a 1.2px stroke width.
3. Not all icons are mathematically aligned; there's quite a bit of optical adjustment. However, try to keep the icon within an internal 12x12 bounding box as much as possible while ensuring proper visibility.
4. Use the `filled` and `outlined` terminology when introducing icons that will have these two variants.
5. Icons that are deeply contextual may have the feature context as their name prefix. For example, `ToolWeb`, `ReplPlay`, `DebugStepInto`, etc.
@@ -1,38 +0,0 @@
-[package]
-name = "indexed_docs"
-version = "0.1.0"
-edition.workspace = true
-publish.workspace = true
-license = "GPL-3.0-or-later"
-
-[lints]
-workspace = true
-
-[lib]
-path = "src/indexed_docs.rs"
-
-[dependencies]
-anyhow.workspace = true
-async-trait.workspace = true
-cargo_metadata.workspace = true
-collections.workspace = true
-derive_more.workspace = true
-extension.workspace = true
-fs.workspace = true
-futures.workspace = true
-fuzzy.workspace = true
-gpui.workspace = true
-heed.workspace = true
-html_to_markdown.workspace = true
-http_client.workspace = true
-indexmap.workspace = true
-parking_lot.workspace = true
-paths.workspace = true
-serde.workspace = true
-strum.workspace = true
-util.workspace = true
-workspace-hack.workspace = true
-
-[dev-dependencies]
-indoc.workspace = true
-pretty_assertions.workspace = true
@@ -1 +0,0 @@
-../../LICENSE-GPL
@@ -1,81 +0,0 @@
-use std::path::PathBuf;
-use std::sync::Arc;
-
-use anyhow::Result;
-use async_trait::async_trait;
-use extension::{Extension, ExtensionHostProxy, ExtensionIndexedDocsProviderProxy};
-use gpui::App;
-
-use crate::{
- IndexedDocsDatabase, IndexedDocsProvider, IndexedDocsRegistry, PackageName, ProviderId,
-};
-
-pub fn init(cx: &mut App) {
- let proxy = ExtensionHostProxy::default_global(cx);
- proxy.register_indexed_docs_provider_proxy(IndexedDocsRegistryProxy {
- indexed_docs_registry: IndexedDocsRegistry::global(cx),
- });
-}
-
-struct IndexedDocsRegistryProxy {
- indexed_docs_registry: Arc<IndexedDocsRegistry>,
-}
-
-impl ExtensionIndexedDocsProviderProxy for IndexedDocsRegistryProxy {
- fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
- self.indexed_docs_registry
- .register_provider(Box::new(ExtensionIndexedDocsProvider::new(
- extension,
- ProviderId(provider_id),
- )));
- }
-
- fn unregister_indexed_docs_provider(&self, provider_id: Arc<str>) {
- self.indexed_docs_registry
- .unregister_provider(&ProviderId(provider_id));
- }
-}
-
-pub struct ExtensionIndexedDocsProvider {
- extension: Arc<dyn Extension>,
- id: ProviderId,
-}
-
-impl ExtensionIndexedDocsProvider {
- pub fn new(extension: Arc<dyn Extension>, id: ProviderId) -> Self {
- Self { extension, id }
- }
-}
-
-#[async_trait]
-impl IndexedDocsProvider for ExtensionIndexedDocsProvider {
- fn id(&self) -> ProviderId {
- self.id.clone()
- }
-
- fn database_path(&self) -> PathBuf {
- let mut database_path = PathBuf::from(self.extension.work_dir().as_ref());
- database_path.push("docs");
- database_path.push(format!("{}.0.mdb", self.id));
-
- database_path
- }
-
- async fn suggest_packages(&self) -> Result<Vec<PackageName>> {
- let packages = self
- .extension
- .suggest_docs_packages(self.id.0.clone())
- .await?;
-
- Ok(packages
- .into_iter()
- .map(|package| PackageName::from(package.as_str()))
- .collect())
- }
-
- async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
- self.extension
- .index_docs(self.id.0.clone(), package.as_ref().into(), database)
- .await
- }
-}
@@ -1,16 +0,0 @@
-mod extension_indexed_docs_provider;
-mod providers;
-mod registry;
-mod store;
-
-use gpui::App;
-
-pub use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
-pub use crate::providers::rustdoc::*;
-pub use crate::registry::*;
-pub use crate::store::*;
-
-pub fn init(cx: &mut App) {
- IndexedDocsRegistry::init_global(cx);
- extension_indexed_docs_provider::init(cx);
-}
@@ -1 +0,0 @@
-pub mod rustdoc;
@@ -1,291 +0,0 @@
-mod item;
-mod to_markdown;
-
-use cargo_metadata::MetadataCommand;
-use futures::future::BoxFuture;
-pub use item::*;
-use parking_lot::RwLock;
-pub use to_markdown::convert_rustdoc_to_markdown;
-
-use std::collections::BTreeSet;
-use std::path::PathBuf;
-use std::sync::{Arc, LazyLock};
-use std::time::{Duration, Instant};
-
-use anyhow::{Context as _, Result, bail};
-use async_trait::async_trait;
-use collections::{HashSet, VecDeque};
-use fs::Fs;
-use futures::{AsyncReadExt, FutureExt};
-use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
-
-use crate::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId};
-
-#[derive(Debug)]
-struct RustdocItemWithHistory {
- pub item: RustdocItem,
- #[cfg(debug_assertions)]
- pub history: Vec<String>,
-}
-
-pub struct LocalRustdocProvider {
- fs: Arc<dyn Fs>,
- cargo_workspace_root: PathBuf,
-}
-
-impl LocalRustdocProvider {
- pub fn id() -> ProviderId {
- ProviderId("rustdoc".into())
- }
-
- pub fn new(fs: Arc<dyn Fs>, cargo_workspace_root: PathBuf) -> Self {
- Self {
- fs,
- cargo_workspace_root,
- }
- }
-}
-
-#[async_trait]
-impl IndexedDocsProvider for LocalRustdocProvider {
- fn id(&self) -> ProviderId {
- Self::id()
- }
-
- fn database_path(&self) -> PathBuf {
- paths::data_dir().join("docs/rust/rustdoc-db.1.mdb")
- }
-
- async fn suggest_packages(&self) -> Result<Vec<PackageName>> {
- static WORKSPACE_CRATES: LazyLock<RwLock<Option<(BTreeSet<PackageName>, Instant)>>> =
- LazyLock::new(|| RwLock::new(None));
-
- if let Some((crates, fetched_at)) = &*WORKSPACE_CRATES.read() {
- if fetched_at.elapsed() < Duration::from_secs(300) {
- return Ok(crates.iter().cloned().collect());
- }
- }
-
- let workspace = MetadataCommand::new()
- .manifest_path(self.cargo_workspace_root.join("Cargo.toml"))
- .exec()
- .context("failed to load cargo metadata")?;
-
- let workspace_crates = workspace
- .packages
- .into_iter()
- .map(|package| PackageName::from(package.name.as_str()))
- .collect::<BTreeSet<_>>();
-
- *WORKSPACE_CRATES.write() = Some((workspace_crates.clone(), Instant::now()));
-
- Ok(workspace_crates.into_iter().collect())
- }
-
- async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
- index_rustdoc(package, database, {
- move |crate_name, item| {
- let fs = self.fs.clone();
- let cargo_workspace_root = self.cargo_workspace_root.clone();
- let crate_name = crate_name.clone();
- let item = item.cloned();
- async move {
- let target_doc_path = cargo_workspace_root.join("target/doc");
- let mut local_cargo_doc_path = target_doc_path.join(crate_name.as_ref().replace('-', "_"));
-
- if !fs.is_dir(&local_cargo_doc_path).await {
- let cargo_doc_exists_at_all = fs.is_dir(&target_doc_path).await;
- if cargo_doc_exists_at_all {
- bail!(
- "no docs directory for '{crate_name}'. if this is a valid crate name, try running `cargo doc`"
- );
- } else {
- bail!("no cargo doc directory. run `cargo doc`");
- }
- }
-
- if let Some(item) = item {
- local_cargo_doc_path.push(item.url_path());
- } else {
- local_cargo_doc_path.push("index.html");
- }
-
- let Ok(contents) = fs.load(&local_cargo_doc_path).await else {
- return Ok(None);
- };
-
- Ok(Some(contents))
- }
- .boxed()
- }
- })
- .await
- }
-}
-
-pub struct DocsDotRsProvider {
- http_client: Arc<HttpClientWithUrl>,
-}
-
-impl DocsDotRsProvider {
- pub fn id() -> ProviderId {
- ProviderId("docs-rs".into())
- }
-
- pub fn new(http_client: Arc<HttpClientWithUrl>) -> Self {
- Self { http_client }
- }
-}
-
-#[async_trait]
-impl IndexedDocsProvider for DocsDotRsProvider {
- fn id(&self) -> ProviderId {
- Self::id()
- }
-
- fn database_path(&self) -> PathBuf {
- paths::data_dir().join("docs/rust/docs-rs-db.1.mdb")
- }
-
- async fn suggest_packages(&self) -> Result<Vec<PackageName>> {
- static POPULAR_CRATES: LazyLock<Vec<PackageName>> = LazyLock::new(|| {
- include_str!("./rustdoc/popular_crates.txt")
- .lines()
- .filter(|line| !line.starts_with('#'))
- .map(|line| PackageName::from(line.trim()))
- .collect()
- });
-
- Ok(POPULAR_CRATES.clone())
- }
-
- async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
- index_rustdoc(package, database, {
- move |crate_name, item| {
- let http_client = self.http_client.clone();
- let crate_name = crate_name.clone();
- let item = item.cloned();
- async move {
- let version = "latest";
- let path = format!(
- "{crate_name}/{version}/{crate_name}{item_path}",
- item_path = item
- .map(|item| format!("/{}", item.url_path()))
- .unwrap_or_default()
- );
-
- let mut response = http_client
- .get(
- &format!("https://docs.rs/{path}"),
- AsyncBody::default(),
- true,
- )
- .await?;
-
- let mut body = Vec::new();
- response
- .body_mut()
- .read_to_end(&mut body)
- .await
- .context("error reading docs.rs response body")?;
-
- if response.status().is_client_error() {
- let text = String::from_utf8_lossy(body.as_slice());
- bail!(
- "status error {}, response: {text:?}",
- response.status().as_u16()
- );
- }
-
- Ok(Some(String::from_utf8(body)?))
- }
- .boxed()
- }
- })
- .await
- }
-}
-
-async fn index_rustdoc(
- package: PackageName,
- database: Arc<IndexedDocsDatabase>,
- fetch_page: impl Fn(
- &PackageName,
- Option<&RustdocItem>,
- ) -> BoxFuture<'static, Result<Option<String>>>
- + Send
- + Sync,
-) -> Result<()> {
- let Some(package_root_content) = fetch_page(&package, None).await? else {
- return Ok(());
- };
-
- let (crate_root_markdown, items) =
- convert_rustdoc_to_markdown(package_root_content.as_bytes())?;
-
- database
- .insert(package.to_string(), crate_root_markdown)
- .await?;
-
- let mut seen_items = HashSet::from_iter(items.clone());
- let mut items_to_visit: VecDeque<RustdocItemWithHistory> =
- VecDeque::from_iter(items.into_iter().map(|item| RustdocItemWithHistory {
- item,
- #[cfg(debug_assertions)]
- history: Vec::new(),
- }));
-
- while let Some(item_with_history) = items_to_visit.pop_front() {
- let item = &item_with_history.item;
-
- let Some(result) = fetch_page(&package, Some(item)).await.with_context(|| {
- #[cfg(debug_assertions)]
- {
- format!(
- "failed to fetch {item:?}: {history:?}",
- history = item_with_history.history
- )
- }
-
- #[cfg(not(debug_assertions))]
- {
- format!("failed to fetch {item:?}")
- }
- })?
- else {
- continue;
- };
-
- let (markdown, referenced_items) = convert_rustdoc_to_markdown(result.as_bytes())?;
-
- database
- .insert(format!("{package}::{}", item.display()), markdown)
- .await?;
-
- let parent_item = item;
- for mut item in referenced_items {
- if seen_items.contains(&item) {
- continue;
- }
-
- seen_items.insert(item.clone());
-
- item.path.extend(parent_item.path.clone());
- if parent_item.kind == RustdocItemKind::Mod {
- item.path.push(parent_item.name.clone());
- }
-
- items_to_visit.push_back(RustdocItemWithHistory {
- #[cfg(debug_assertions)]
- history: {
- let mut history = item_with_history.history.clone();
- history.push(item.url_path());
- history
- },
- item,
- });
- }
- }
-
- Ok(())
-}
@@ -1,82 +0,0 @@
-use std::sync::Arc;
-
-use serde::{Deserialize, Serialize};
-use strum::EnumIter;
-
-#[derive(
- Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize, EnumIter,
-)]
-#[serde(rename_all = "snake_case")]
-pub enum RustdocItemKind {
- Mod,
- Macro,
- Struct,
- Enum,
- Constant,
- Trait,
- Function,
- TypeAlias,
- AttributeMacro,
- DeriveMacro,
-}
-
-impl RustdocItemKind {
- pub(crate) const fn class(&self) -> &'static str {
- match self {
- Self::Mod => "mod",
- Self::Macro => "macro",
- Self::Struct => "struct",
- Self::Enum => "enum",
- Self::Constant => "constant",
- Self::Trait => "trait",
- Self::Function => "fn",
- Self::TypeAlias => "type",
- Self::AttributeMacro => "attr",
- Self::DeriveMacro => "derive",
- }
- }
-}
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
-pub struct RustdocItem {
- pub kind: RustdocItemKind,
- /// The item path, up until the name of the item.
- pub path: Vec<Arc<str>>,
- /// The name of the item.
- pub name: Arc<str>,
-}
-
-impl RustdocItem {
- pub fn display(&self) -> String {
- let mut path_segments = self.path.clone();
- path_segments.push(self.name.clone());
-
- path_segments.join("::")
- }
-
- pub fn url_path(&self) -> String {
- let name = &self.name;
- let mut path_components = self.path.clone();
-
- match self.kind {
- RustdocItemKind::Mod => {
- path_components.push(name.clone());
- path_components.push("index.html".into());
- }
- RustdocItemKind::Macro
- | RustdocItemKind::Struct
- | RustdocItemKind::Enum
- | RustdocItemKind::Constant
- | RustdocItemKind::Trait
- | RustdocItemKind::Function
- | RustdocItemKind::TypeAlias
- | RustdocItemKind::AttributeMacro
- | RustdocItemKind::DeriveMacro => {
- path_components
- .push(format!("{kind}.{name}.html", kind = self.kind.class()).into());
- }
- }
-
- path_components.join("/")
- }
-}
@@ -1,252 +0,0 @@
-# A list of the most popular Rust crates.
-# Sourced from https://lib.rs/std.
-serde
-serde_json
-syn
-clap
-thiserror
-rand
-log
-tokio
-anyhow
-regex
-quote
-proc-macro2
-base64
-itertools
-chrono
-lazy_static
-once_cell
-libc
-reqwest
-futures
-bitflags
-tracing
-url
-bytes
-toml
-tempfile
-uuid
-indexmap
-env_logger
-num-traits
-async-trait
-sha2
-hex
-tracing-subscriber
-http
-parking_lot
-cfg-if
-futures-util
-cc
-hashbrown
-rayon
-hyper
-getrandom
-semver
-strum
-flate2
-tokio-util
-smallvec
-criterion
-paste
-heck
-rand_core
-nom
-rustls
-nix
-glob
-time
-byteorder
-strum_macros
-serde_yaml
-wasm-bindgen
-ahash
-either
-num_cpus
-rand_chacha
-prost
-percent-encoding
-pin-project-lite
-tokio-stream
-bincode
-walkdir
-bindgen
-axum
-windows-sys
-futures-core
-ring
-digest
-num-bigint
-rustls-pemfile
-serde_with
-crossbeam-channel
-tokio-rustls
-hmac
-fastrand
-dirs
-zeroize
-socket2
-pin-project
-tower
-derive_more
-memchr
-toml_edit
-static_assertions
-pretty_assertions
-js-sys
-convert_case
-unicode-width
-pkg-config
-itoa
-colored
-rustc-hash
-darling
-mime
-web-sys
-image
-bytemuck
-which
-sha1
-dashmap
-arrayvec
-fnv
-tonic
-humantime
-libloading
-winapi
-rustc_version
-http-body
-indoc
-num
-home
-serde_urlencoded
-http-body-util
-unicode-segmentation
-num-integer
-webpki-roots
-phf
-futures-channel
-indicatif
-petgraph
-ordered-float
-strsim
-zstd
-console
-encoding_rs
-wasm-bindgen-futures
-urlencoding
-subtle
-crc32fast
-slab
-rustix
-predicates
-spin
-hyper-rustls
-backtrace
-rustversion
-mio
-scopeguard
-proc-macro-error
-hyper-util
-ryu
-prost-types
-textwrap
-memmap2
-zip
-zerocopy
-generic-array
-tar
-pyo3
-async-stream
-quick-xml
-memoffset
-csv
-crossterm
-windows
-num_enum
-tokio-tungstenite
-crossbeam-utils
-async-channel
-lru
-aes
-futures-lite
-tracing-core
-prettyplease
-httparse
-serde_bytes
-tracing-log
-tower-service
-cargo_metadata
-pest
-mime_guess
-tower-http
-data-encoding
-native-tls
-prost-build
-proptest
-derivative
-serial_test
-libm
-half
-futures-io
-bitvec
-rustls-native-certs
-ureq
-object
-anstyle
-tonic-build
-form_urlencoded
-num-derive
-pest_derive
-schemars
-proc-macro-crate
-rstest
-futures-executor
-assert_cmd
-termcolor
-serde_repr
-ctrlc
-sha3
-clap_complete
-flume
-mockall
-ipnet
-aho-corasick
-atty
-signal-hook
-async-std
-filetime
-num-complex
-opentelemetry
-cmake
-arc-swap
-derive_builder
-async-recursion
-dyn-clone
-bumpalo
-fs_extra
-git2
-sysinfo
-shlex
-instant
-approx
-rmp-serde
-rand_distr
-rustls-pki-types
-maplit
-sqlx
-blake3
-hyper-tls
-dotenvy
-jsonwebtoken
-openssl-sys
-crossbeam
-camino
-winreg
-config
-rsa
-bit-vec
-chrono-tz
-async-lock
-bstr
@@ -1,618 +0,0 @@
-use std::cell::RefCell;
-use std::io::Read;
-use std::rc::Rc;
-
-use anyhow::Result;
-use html_to_markdown::markdown::{
- HeadingHandler, ListHandler, ParagraphHandler, StyledTextHandler, TableHandler,
-};
-use html_to_markdown::{
- HandleTag, HandlerOutcome, HtmlElement, MarkdownWriter, StartTagOutcome, TagHandler,
- convert_html_to_markdown,
-};
-use indexmap::IndexSet;
-use strum::IntoEnumIterator;
-
-use crate::{RustdocItem, RustdocItemKind};
-
-/// Converts the provided rustdoc HTML to Markdown.
-pub fn convert_rustdoc_to_markdown(html: impl Read) -> Result<(String, Vec<RustdocItem>)> {
- let item_collector = Rc::new(RefCell::new(RustdocItemCollector::new()));
-
- let mut handlers: Vec<TagHandler> = vec![
- Rc::new(RefCell::new(ParagraphHandler)),
- Rc::new(RefCell::new(HeadingHandler)),
- Rc::new(RefCell::new(ListHandler)),
- Rc::new(RefCell::new(TableHandler::new())),
- Rc::new(RefCell::new(StyledTextHandler)),
- Rc::new(RefCell::new(RustdocChromeRemover)),
- Rc::new(RefCell::new(RustdocHeadingHandler)),
- Rc::new(RefCell::new(RustdocCodeHandler)),
- Rc::new(RefCell::new(RustdocItemHandler)),
- item_collector.clone(),
- ];
-
- let markdown = convert_html_to_markdown(html, &mut handlers)?;
-
- let items = item_collector
- .borrow()
- .items
- .iter()
- .cloned()
- .collect::<Vec<_>>();
-
- Ok((markdown, items))
-}
-
-pub struct RustdocHeadingHandler;
-
-impl HandleTag for RustdocHeadingHandler {
- fn should_handle(&self, _tag: &str) -> bool {
- // We're only handling text, so we don't need to visit any tags.
- false
- }
-
- fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome {
- if writer.is_inside("h1")
- || writer.is_inside("h2")
- || writer.is_inside("h3")
- || writer.is_inside("h4")
- || writer.is_inside("h5")
- || writer.is_inside("h6")
- {
- let text = text
- .trim_matches(|char| char == '\n' || char == '\r')
- .replace('\n', " ");
- writer.push_str(&text);
-
- return HandlerOutcome::Handled;
- }
-
- HandlerOutcome::NoOp
- }
-}
-
-pub struct RustdocCodeHandler;
-
-impl HandleTag for RustdocCodeHandler {
- fn should_handle(&self, tag: &str) -> bool {
- matches!(tag, "pre" | "code")
- }
-
- fn handle_tag_start(
- &mut self,
- tag: &HtmlElement,
- writer: &mut MarkdownWriter,
- ) -> StartTagOutcome {
- match tag.tag() {
- "code" => {
- if !writer.is_inside("pre") {
- writer.push_str("`");
- }
- }
- "pre" => {
- let classes = tag.classes();
- let is_rust = classes.iter().any(|class| class == "rust");
- let language = is_rust
- .then_some("rs")
- .or_else(|| {
- classes.iter().find_map(|class| {
- if let Some((_, language)) = class.split_once("language-") {
- Some(language.trim())
- } else {
- None
- }
- })
- })
- .unwrap_or("");
-
- writer.push_str(&format!("\n\n```{language}\n"));
- }
- _ => {}
- }
-
- StartTagOutcome::Continue
- }
-
- fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {
- match tag.tag() {
- "code" => {
- if !writer.is_inside("pre") {
- writer.push_str("`");
- }
- }
- "pre" => writer.push_str("\n```\n"),
- _ => {}
- }
- }
-
- fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome {
- if writer.is_inside("pre") {
- writer.push_str(text);
- return HandlerOutcome::Handled;
- }
-
- HandlerOutcome::NoOp
- }
-}
-
-const RUSTDOC_ITEM_NAME_CLASS: &str = "item-name";
-
-pub struct RustdocItemHandler;
-
-impl RustdocItemHandler {
- /// Returns whether we're currently inside of an `.item-name` element, which
- /// rustdoc uses to display Rust items in a list.
- fn is_inside_item_name(writer: &MarkdownWriter) -> bool {
- writer
- .current_element_stack()
- .iter()
- .any(|element| element.has_class(RUSTDOC_ITEM_NAME_CLASS))
- }
-}
-
-impl HandleTag for RustdocItemHandler {
- fn should_handle(&self, tag: &str) -> bool {
- matches!(tag, "div" | "span")
- }
-
- fn handle_tag_start(
- &mut self,
- tag: &HtmlElement,
- writer: &mut MarkdownWriter,
- ) -> StartTagOutcome {
- match tag.tag() {
- "div" | "span" => {
- if Self::is_inside_item_name(writer) && tag.has_class("stab") {
- writer.push_str(" [");
- }
- }
- _ => {}
- }
-
- StartTagOutcome::Continue
- }
-
- fn handle_tag_end(&mut self, tag: &HtmlElement, writer: &mut MarkdownWriter) {
- match tag.tag() {
- "div" | "span" => {
- if tag.has_class(RUSTDOC_ITEM_NAME_CLASS) {
- writer.push_str(": ");
- }
-
- if Self::is_inside_item_name(writer) && tag.has_class("stab") {
- writer.push_str("]");
- }
- }
- _ => {}
- }
- }
-
- fn handle_text(&mut self, text: &str, writer: &mut MarkdownWriter) -> HandlerOutcome {
- if Self::is_inside_item_name(writer)
- && !writer.is_inside("span")
- && !writer.is_inside("code")
- {
- writer.push_str(&format!("`{text}`"));
- return HandlerOutcome::Handled;
- }
-
- HandlerOutcome::NoOp
- }
-}
-
-pub struct RustdocChromeRemover;
-
-impl HandleTag for RustdocChromeRemover {
- fn should_handle(&self, tag: &str) -> bool {
- matches!(
- tag,
- "head" | "script" | "nav" | "summary" | "button" | "a" | "div" | "span"
- )
- }
-
- fn handle_tag_start(
- &mut self,
- tag: &HtmlElement,
- _writer: &mut MarkdownWriter,
- ) -> StartTagOutcome {
- match tag.tag() {
- "head" | "script" | "nav" => return StartTagOutcome::Skip,
- "summary" => {
- if tag.has_class("hideme") {
- return StartTagOutcome::Skip;
- }
- }
- "button" => {
- if tag.attr("id").as_deref() == Some("copy-path") {
- return StartTagOutcome::Skip;
- }
- }
- "a" => {
- if tag.has_any_classes(&["anchor", "doc-anchor", "src"]) {
- return StartTagOutcome::Skip;
- }
- }
- "div" | "span" => {
- if tag.has_any_classes(&["nav-container", "sidebar-elems", "out-of-band"]) {
- return StartTagOutcome::Skip;
- }
- }
-
- _ => {}
- }
-
- StartTagOutcome::Continue
- }
-}
-
-pub struct RustdocItemCollector {
- pub items: IndexSet<RustdocItem>,
-}
-
-impl RustdocItemCollector {
- pub fn new() -> Self {
- Self {
- items: IndexSet::new(),
- }
- }
-
- fn parse_item(tag: &HtmlElement) -> Option<RustdocItem> {
- if tag.tag() != "a" {
- return None;
- }
-
- let href = tag.attr("href")?;
- if href.starts_with('#') || href.starts_with("https://") || href.starts_with("../") {
- return None;
- }
-
- for kind in RustdocItemKind::iter() {
- if tag.has_class(kind.class()) {
- let mut parts = href.trim_end_matches("/index.html").split('/');
-
- if let Some(last_component) = parts.next_back() {
- let last_component = match last_component.split_once('#') {
- Some((component, _fragment)) => component,
- None => last_component,
- };
-
- let name = last_component
- .trim_start_matches(&format!("{}.", kind.class()))
- .trim_end_matches(".html");
-
- return Some(RustdocItem {
- kind,
- name: name.into(),
- path: parts.map(Into::into).collect(),
- });
- }
- }
- }
-
- None
- }
-}
-
-impl HandleTag for RustdocItemCollector {
- fn should_handle(&self, tag: &str) -> bool {
- tag == "a"
- }
-
- fn handle_tag_start(
- &mut self,
- tag: &HtmlElement,
- writer: &mut MarkdownWriter,
- ) -> StartTagOutcome {
- if tag.tag() == "a" {
- let is_reexport = writer.current_element_stack().iter().any(|element| {
- if let Some(id) = element.attr("id") {
- id.starts_with("reexport.") || id.starts_with("method.")
- } else {
- false
- }
- });
-
- if !is_reexport {
- if let Some(item) = Self::parse_item(tag) {
- self.items.insert(item);
- }
- }
- }
-
- StartTagOutcome::Continue
- }
-}
-
-#[cfg(test)]
-mod tests {
- use html_to_markdown::{TagHandler, convert_html_to_markdown};
- use indoc::indoc;
- use pretty_assertions::assert_eq;
-
- use super::*;
-
- fn rustdoc_handlers() -> Vec<TagHandler> {
- vec![
- Rc::new(RefCell::new(ParagraphHandler)),
- Rc::new(RefCell::new(HeadingHandler)),
- Rc::new(RefCell::new(ListHandler)),
- Rc::new(RefCell::new(TableHandler::new())),
- Rc::new(RefCell::new(StyledTextHandler)),
- Rc::new(RefCell::new(RustdocChromeRemover)),
- Rc::new(RefCell::new(RustdocHeadingHandler)),
- Rc::new(RefCell::new(RustdocCodeHandler)),
- Rc::new(RefCell::new(RustdocItemHandler)),
- ]
- }
-
- #[test]
- fn test_main_heading_buttons_get_removed() {
- let html = indoc! {r##"
- <div class="main-heading">
- <h1>Crate <a class="mod" href="#">serde</a><button id="copy-path" title="Copy item path to clipboard">Copy item path</button></h1>
- <span class="out-of-band">
- <a class="src" href="../src/serde/lib.rs.html#1-340">source</a> Β· <button id="toggle-all-docs" title="collapse all docs">[<span>β</span>]</button>
- </span>
- </div>
- "##};
- let expected = indoc! {"
- # Crate serde
- "}
- .trim();
-
- assert_eq!(
- convert_html_to_markdown(html.as_bytes(), &mut rustdoc_handlers()).unwrap(),
- expected
- )
- }
-
- #[test]
- fn test_single_paragraph() {
- let html = indoc! {r#"
- <p>In particular, the last point is what sets <code>axum</code> apart from other frameworks.
- <code>axum</code> doesnβt have its own middleware system but instead uses
- <a href="https://docs.rs/tower-service/0.3.2/x86_64-unknown-linux-gnu/tower_service/trait.Service.html" title="trait tower_service::Service"><code>tower::Service</code></a>. This means <code>axum</code> gets timeouts, tracing, compression,
- authorization, and more, for free. It also enables you to share middleware with
- applications written using <a href="http://crates.io/crates/hyper"><code>hyper</code></a> or <a href="http://crates.io/crates/tonic"><code>tonic</code></a>.</p>
- "#};
- let expected = indoc! {"
- In particular, the last point is what sets `axum` apart from other frameworks. `axum` doesnβt have its own middleware system but instead uses `tower::Service`. This means `axum` gets timeouts, tracing, compression, authorization, and more, for free. It also enables you to share middleware with applications written using `hyper` or `tonic`.
- "}
- .trim();
-
- assert_eq!(
- convert_html_to_markdown(html.as_bytes(), &mut rustdoc_handlers()).unwrap(),
- expected
- )
- }
-
- #[test]
- fn test_multiple_paragraphs() {
- let html = indoc! {r##"
- <h2 id="serde"><a class="doc-anchor" href="#serde">Β§</a>Serde</h2>
- <p>Serde is a framework for <em><strong>ser</strong></em>ializing and <em><strong>de</strong></em>serializing Rust data
- structures efficiently and generically.</p>
- <p>The Serde ecosystem consists of data structures that know how to serialize
- and deserialize themselves along with data formats that know how to
- serialize and deserialize other things. Serde provides the layer by which
- these two groups interact with each other, allowing any supported data
- structure to be serialized and deserialized using any supported data format.</p>
- <p>See the Serde website <a href="https://serde.rs/">https://serde.rs/</a> for additional documentation and
- usage examples.</p>
- <h3 id="design"><a class="doc-anchor" href="#design">Β§</a>Design</h3>
- <p>Where many other languages rely on runtime reflection for serializing data,
- Serde is instead built on Rustβs powerful trait system. A data structure
- that knows how to serialize and deserialize itself is one that implements
- Serdeβs <code>Serialize</code> and <code>Deserialize</code> traits (or uses Serdeβs derive
- attribute to automatically generate implementations at compile time). This
- avoids any overhead of reflection or runtime type information. In fact in
- many situations the interaction between data structure and data format can
- be completely optimized away by the Rust compiler, leaving Serde
- serialization to perform the same speed as a handwritten serializer for the
- specific selection of data structure and data format.</p>
- "##};
- let expected = indoc! {"
- ## Serde
-
- Serde is a framework for _**ser**_ializing and _**de**_serializing Rust data structures efficiently and generically.
-
- The Serde ecosystem consists of data structures that know how to serialize and deserialize themselves along with data formats that know how to serialize and deserialize other things. Serde provides the layer by which these two groups interact with each other, allowing any supported data structure to be serialized and deserialized using any supported data format.
-
- See the Serde website https://serde.rs/ for additional documentation and usage examples.
-
- ### Design
-
- Where many other languages rely on runtime reflection for serializing data, Serde is instead built on Rustβs powerful trait system. A data structure that knows how to serialize and deserialize itself is one that implements Serdeβs `Serialize` and `Deserialize` traits (or uses Serdeβs derive attribute to automatically generate implementations at compile time). This avoids any overhead of reflection or runtime type information. In fact in many situations the interaction between data structure and data format can be completely optimized away by the Rust compiler, leaving Serde serialization to perform the same speed as a handwritten serializer for the specific selection of data structure and data format.
- "}
- .trim();
-
- assert_eq!(
- convert_html_to_markdown(html.as_bytes(), &mut rustdoc_handlers()).unwrap(),
- expected
- )
- }
-
- #[test]
- fn test_styled_text() {
- let html = indoc! {r#"
- <p>This text is <strong>bolded</strong>.</p>
- <p>This text is <em>italicized</em>.</p>
- "#};
- let expected = indoc! {"
- This text is **bolded**.
-
- This text is _italicized_.
- "}
- .trim();
-
- assert_eq!(
- convert_html_to_markdown(html.as_bytes(), &mut rustdoc_handlers()).unwrap(),
- expected
- )
- }
-
- #[test]
- fn test_rust_code_block() {
- let html = indoc! {r#"
- <pre class="rust rust-example-rendered"><code><span class="kw">use </span>axum::extract::{Path, Query, Json};
- <span class="kw">use </span>std::collections::HashMap;
-
- <span class="comment">// `Path` gives you the path parameters and deserializes them.
- </span><span class="kw">async fn </span>path(Path(user_id): Path<u32>) {}
-
- <span class="comment">// `Query` gives you the query parameters and deserializes them.
- </span><span class="kw">async fn </span>query(Query(params): Query<HashMap<String, String>>) {}
-
- <span class="comment">// Buffer the request body and deserialize it as JSON into a
- // `serde_json::Value`. `Json` supports any type that implements
- // `serde::Deserialize`.
- </span><span class="kw">async fn </span>json(Json(payload): Json<serde_json::Value>) {}</code></pre>
- "#};
- let expected = indoc! {"
- ```rs
- use axum::extract::{Path, Query, Json};
- use std::collections::HashMap;
-
- // `Path` gives you the path parameters and deserializes them.
- async fn path(Path(user_id): Path<u32>) {}
-
- // `Query` gives you the query parameters and deserializes them.
- async fn query(Query(params): Query<HashMap<String, String>>) {}
-
- // Buffer the request body and deserialize it as JSON into a
- // `serde_json::Value`. `Json` supports any type that implements
- // `serde::Deserialize`.
- async fn json(Json(payload): Json<serde_json::Value>) {}
- ```
- "}
- .trim();
-
- assert_eq!(
- convert_html_to_markdown(html.as_bytes(), &mut rustdoc_handlers()).unwrap(),
- expected
- )
- }
-
- #[test]
- fn test_toml_code_block() {
- let html = indoc! {r##"
- <h2 id="required-dependencies"><a class="doc-anchor" href="#required-dependencies">Β§</a>Required dependencies</h2>
- <p>To use axum there are a few dependencies you have to pull in as well:</p>
- <div class="example-wrap"><pre class="language-toml"><code>[dependencies]
- axum = "<latest-version>"
- tokio = { version = "<latest-version>", features = ["full"] }
- tower = "<latest-version>"
- </code></pre></div>
- "##};
- let expected = indoc! {r#"
- ## Required dependencies
-
- To use axum there are a few dependencies you have to pull in as well:
-
- ```toml
- [dependencies]
- axum = "<latest-version>"
- tokio = { version = "<latest-version>", features = ["full"] }
- tower = "<latest-version>"
-
- ```
- "#}
- .trim();
-
- assert_eq!(
- convert_html_to_markdown(html.as_bytes(), &mut rustdoc_handlers()).unwrap(),
- expected
- )
- }
-
- #[test]
- fn test_item_table() {
- let html = indoc! {r##"
- <h2 id="structs" class="section-header">Structs<a href="#structs" class="anchor">Β§</a></h2>
- <ul class="item-table">
- <li><div class="item-name"><a class="struct" href="struct.Error.html" title="struct axum::Error">Error</a></div><div class="desc docblock-short">Errors that can happen when using axum.</div></li>
- <li><div class="item-name"><a class="struct" href="struct.Extension.html" title="struct axum::Extension">Extension</a></div><div class="desc docblock-short">Extractor and response for extensions.</div></li>
- <li><div class="item-name"><a class="struct" href="struct.Form.html" title="struct axum::Form">Form</a><span class="stab portability" title="Available on crate feature `form` only"><code>form</code></span></div><div class="desc docblock-short">URL encoded extractor and response.</div></li>
- <li><div class="item-name"><a class="struct" href="struct.Json.html" title="struct axum::Json">Json</a><span class="stab portability" title="Available on crate feature `json` only"><code>json</code></span></div><div class="desc docblock-short">JSON Extractor / Response.</div></li>
- <li><div class="item-name"><a class="struct" href="struct.Router.html" title="struct axum::Router">Router</a></div><div class="desc docblock-short">The router type for composing handlers and services.</div></li></ul>
- <h2 id="functions" class="section-header">Functions<a href="#functions" class="anchor">Β§</a></h2>
- <ul class="item-table">
- <li><div class="item-name"><a class="fn" href="fn.serve.html" title="fn axum::serve">serve</a><span class="stab portability" title="Available on crate feature `tokio` and (crate features `http1` or `http2`) only"><code>tokio</code> and (<code>http1</code> or <code>http2</code>)</span></div><div class="desc docblock-short">Serve the service with the supplied listener.</div></li>
- </ul>
- "##};
- let expected = indoc! {r#"
- ## Structs
-
- - `Error`: Errors that can happen when using axum.
- - `Extension`: Extractor and response for extensions.
- - `Form` [`form`]: URL encoded extractor and response.
- - `Json` [`json`]: JSON Extractor / Response.
- - `Router`: The router type for composing handlers and services.
-
- ## Functions
-
- - `serve` [`tokio` and (`http1` or `http2`)]: Serve the service with the supplied listener.
- "#}
- .trim();
-
- assert_eq!(
- convert_html_to_markdown(html.as_bytes(), &mut rustdoc_handlers()).unwrap(),
- expected
- )
- }
-
- #[test]
- fn test_table() {
- let html = indoc! {r##"
- <h2 id="feature-flags"><a class="doc-anchor" href="#feature-flags">Β§</a>Feature flags</h2>
- <p>axum uses a set of <a href="https://doc.rust-lang.org/cargo/reference/features.html#the-features-section">feature flags</a> to reduce the amount of compiled and
- optional dependencies.</p>
- <p>The following optional features are available:</p>
- <div><table><thead><tr><th>Name</th><th>Description</th><th>Default?</th></tr></thead><tbody>
- <tr><td><code>http1</code></td><td>Enables hyperβs <code>http1</code> feature</td><td>Yes</td></tr>
- <tr><td><code>http2</code></td><td>Enables hyperβs <code>http2</code> feature</td><td>No</td></tr>
- <tr><td><code>json</code></td><td>Enables the <a href="struct.Json.html" title="struct axum::Json"><code>Json</code></a> type and some similar convenience functionality</td><td>Yes</td></tr>
- <tr><td><code>macros</code></td><td>Enables optional utility macros</td><td>No</td></tr>
- <tr><td><code>matched-path</code></td><td>Enables capturing of every requestβs router path and the <a href="extract/struct.MatchedPath.html" title="struct axum::extract::MatchedPath"><code>MatchedPath</code></a> extractor</td><td>Yes</td></tr>
- <tr><td><code>multipart</code></td><td>Enables parsing <code>multipart/form-data</code> requests with <a href="extract/struct.Multipart.html" title="struct axum::extract::Multipart"><code>Multipart</code></a></td><td>No</td></tr>
- <tr><td><code>original-uri</code></td><td>Enables capturing of every requestβs original URI and the <a href="extract/struct.OriginalUri.html" title="struct axum::extract::OriginalUri"><code>OriginalUri</code></a> extractor</td><td>Yes</td></tr>
- <tr><td><code>tokio</code></td><td>Enables <code>tokio</code> as a dependency and <code>axum::serve</code>, <code>SSE</code> and <code>extract::connect_info</code> types.</td><td>Yes</td></tr>
- <tr><td><code>tower-log</code></td><td>Enables <code>tower</code>βs <code>log</code> feature</td><td>Yes</td></tr>
- <tr><td><code>tracing</code></td><td>Log rejections from built-in extractors</td><td>Yes</td></tr>
- <tr><td><code>ws</code></td><td>Enables WebSockets support via <a href="extract/ws/index.html" title="mod axum::extract::ws"><code>extract::ws</code></a></td><td>No</td></tr>
- <tr><td><code>form</code></td><td>Enables the <code>Form</code> extractor</td><td>Yes</td></tr>
- <tr><td><code>query</code></td><td>Enables the <code>Query</code> extractor</td><td>Yes</td></tr>
- </tbody></table>
- "##};
- let expected = indoc! {r#"
- ## Feature flags
-
- axum uses a set of feature flags to reduce the amount of compiled and optional dependencies.
-
- The following optional features are available:
-
- | Name | Description | Default? |
- | --- | --- | --- |
- | `http1` | Enables hyperβs `http1` feature | Yes |
- | `http2` | Enables hyperβs `http2` feature | No |
- | `json` | Enables the `Json` type and some similar convenience functionality | Yes |
- | `macros` | Enables optional utility macros | No |
- | `matched-path` | Enables capturing of every requestβs router path and the `MatchedPath` extractor | Yes |
- | `multipart` | Enables parsing `multipart/form-data` requests with `Multipart` | No |
- | `original-uri` | Enables capturing of every requestβs original URI and the `OriginalUri` extractor | Yes |
- | `tokio` | Enables `tokio` as a dependency and `axum::serve`, `SSE` and `extract::connect_info` types. | Yes |
- | `tower-log` | Enables `tower`βs `log` feature | Yes |
- | `tracing` | Log rejections from built-in extractors | Yes |
- | `ws` | Enables WebSockets support via `extract::ws` | No |
- | `form` | Enables the `Form` extractor | Yes |
- | `query` | Enables the `Query` extractor | Yes |
- "#}
- .trim();
-
- assert_eq!(
- convert_html_to_markdown(html.as_bytes(), &mut rustdoc_handlers()).unwrap(),
- expected
- )
- }
-}
@@ -1,62 +0,0 @@
-use std::sync::Arc;
-
-use collections::HashMap;
-use gpui::{App, BackgroundExecutor, Global, ReadGlobal, UpdateGlobal};
-use parking_lot::RwLock;
-
-use crate::{IndexedDocsProvider, IndexedDocsStore, ProviderId};
-
-struct GlobalIndexedDocsRegistry(Arc<IndexedDocsRegistry>);
-
-impl Global for GlobalIndexedDocsRegistry {}
-
-pub struct IndexedDocsRegistry {
- executor: BackgroundExecutor,
- stores_by_provider: RwLock<HashMap<ProviderId, Arc<IndexedDocsStore>>>,
-}
-
-impl IndexedDocsRegistry {
- pub fn global(cx: &App) -> Arc<Self> {
- GlobalIndexedDocsRegistry::global(cx).0.clone()
- }
-
- pub(crate) fn init_global(cx: &mut App) {
- GlobalIndexedDocsRegistry::set_global(
- cx,
- GlobalIndexedDocsRegistry(Arc::new(Self::new(cx.background_executor().clone()))),
- );
- }
-
- pub fn new(executor: BackgroundExecutor) -> Self {
- Self {
- executor,
- stores_by_provider: RwLock::new(HashMap::default()),
- }
- }
-
- pub fn list_providers(&self) -> Vec<ProviderId> {
- self.stores_by_provider
- .read()
- .keys()
- .cloned()
- .collect::<Vec<_>>()
- }
-
- pub fn register_provider(
- &self,
- provider: Box<dyn IndexedDocsProvider + Send + Sync + 'static>,
- ) {
- self.stores_by_provider.write().insert(
- provider.id(),
- Arc::new(IndexedDocsStore::new(provider, self.executor.clone())),
- );
- }
-
- pub fn unregister_provider(&self, provider_id: &ProviderId) {
- self.stores_by_provider.write().remove(provider_id);
- }
-
- pub fn get_provider_store(&self, provider_id: ProviderId) -> Option<Arc<IndexedDocsStore>> {
- self.stores_by_provider.read().get(&provider_id).cloned()
- }
-}
@@ -1,346 +0,0 @@
-use std::path::PathBuf;
-use std::sync::Arc;
-use std::sync::atomic::AtomicBool;
-
-use anyhow::{Context as _, Result, anyhow};
-use async_trait::async_trait;
-use collections::HashMap;
-use derive_more::{Deref, Display};
-use futures::FutureExt;
-use futures::future::{self, BoxFuture, Shared};
-use fuzzy::StringMatchCandidate;
-use gpui::{App, BackgroundExecutor, Task};
-use heed::Database;
-use heed::types::SerdeBincode;
-use parking_lot::RwLock;
-use serde::{Deserialize, Serialize};
-use util::ResultExt;
-
-use crate::IndexedDocsRegistry;
-
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Deref, Display)]
-pub struct ProviderId(pub Arc<str>);
-
-/// The name of a package.
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Deref, Display)]
-pub struct PackageName(Arc<str>);
-
-impl From<&str> for PackageName {
- fn from(value: &str) -> Self {
- Self(value.into())
- }
-}
-
-#[async_trait]
-pub trait IndexedDocsProvider {
- /// Returns the ID of this provider.
- fn id(&self) -> ProviderId;
-
- /// Returns the path to the database for this provider.
- fn database_path(&self) -> PathBuf;
-
- /// Returns a list of packages as suggestions to be included in the search
- /// results.
- ///
- /// This can be used to provide completions for known packages (e.g., from the
- /// local project or a registry) before a package has been indexed.
- async fn suggest_packages(&self) -> Result<Vec<PackageName>>;
-
- /// Indexes the package with the given name.
- async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()>;
-}
-
-/// A store for indexed docs.
-pub struct IndexedDocsStore {
- executor: BackgroundExecutor,
- database_future:
- Shared<BoxFuture<'static, Result<Arc<IndexedDocsDatabase>, Arc<anyhow::Error>>>>,
- provider: Box<dyn IndexedDocsProvider + Send + Sync + 'static>,
- indexing_tasks_by_package:
- RwLock<HashMap<PackageName, Shared<Task<Result<(), Arc<anyhow::Error>>>>>>,
- latest_errors_by_package: RwLock<HashMap<PackageName, Arc<str>>>,
-}
-
-impl IndexedDocsStore {
- pub fn try_global(provider: ProviderId, cx: &App) -> Result<Arc<Self>> {
- let registry = IndexedDocsRegistry::global(cx);
- registry
- .get_provider_store(provider.clone())
- .with_context(|| format!("no indexed docs store found for {provider}"))
- }
-
- pub fn new(
- provider: Box<dyn IndexedDocsProvider + Send + Sync + 'static>,
- executor: BackgroundExecutor,
- ) -> Self {
- let database_future = executor
- .spawn({
- let executor = executor.clone();
- let database_path = provider.database_path();
- async move { IndexedDocsDatabase::new(database_path, executor) }
- })
- .then(|result| future::ready(result.map(Arc::new).map_err(Arc::new)))
- .boxed()
- .shared();
-
- Self {
- executor,
- database_future,
- provider,
- indexing_tasks_by_package: RwLock::new(HashMap::default()),
- latest_errors_by_package: RwLock::new(HashMap::default()),
- }
- }
-
- pub fn latest_error_for_package(&self, package: &PackageName) -> Option<Arc<str>> {
- self.latest_errors_by_package.read().get(package).cloned()
- }
-
- /// Returns whether the package with the given name is currently being indexed.
- pub fn is_indexing(&self, package: &PackageName) -> bool {
- self.indexing_tasks_by_package.read().contains_key(package)
- }
-
- pub async fn load(&self, key: String) -> Result<MarkdownDocs> {
- self.database_future
- .clone()
- .await
- .map_err(|err| anyhow!(err))?
- .load(key)
- .await
- }
-
- pub async fn load_many_by_prefix(&self, prefix: String) -> Result<Vec<(String, MarkdownDocs)>> {
- self.database_future
- .clone()
- .await
- .map_err(|err| anyhow!(err))?
- .load_many_by_prefix(prefix)
- .await
- }
-
- /// Returns whether any entries exist with the given prefix.
- pub async fn any_with_prefix(&self, prefix: String) -> Result<bool> {
- self.database_future
- .clone()
- .await
- .map_err(|err| anyhow!(err))?
- .any_with_prefix(prefix)
- .await
- }
-
- pub fn suggest_packages(self: Arc<Self>) -> Task<Result<Vec<PackageName>>> {
- let this = self.clone();
- self.executor
- .spawn(async move { this.provider.suggest_packages().await })
- }
-
- pub fn index(
- self: Arc<Self>,
- package: PackageName,
- ) -> Shared<Task<Result<(), Arc<anyhow::Error>>>> {
- if let Some(existing_task) = self.indexing_tasks_by_package.read().get(&package) {
- return existing_task.clone();
- }
-
- let indexing_task = self
- .executor
- .spawn({
- let this = self.clone();
- let package = package.clone();
- async move {
- let _finally = util::defer({
- let this = this.clone();
- let package = package.clone();
- move || {
- this.indexing_tasks_by_package.write().remove(&package);
- }
- });
-
- let index_task = {
- let package = package.clone();
- async {
- let database = this
- .database_future
- .clone()
- .await
- .map_err(|err| anyhow!(err))?;
- this.provider.index(package, database).await
- }
- };
-
- let result = index_task.await.map_err(Arc::new);
- match &result {
- Ok(_) => {
- this.latest_errors_by_package.write().remove(&package);
- }
- Err(err) => {
- this.latest_errors_by_package
- .write()
- .insert(package, err.to_string().into());
- }
- }
-
- result
- }
- })
- .shared();
-
- self.indexing_tasks_by_package
- .write()
- .insert(package, indexing_task.clone());
-
- indexing_task
- }
-
- pub fn search(&self, query: String) -> Task<Vec<String>> {
- let executor = self.executor.clone();
- let database_future = self.database_future.clone();
- self.executor.spawn(async move {
- let Some(database) = database_future.await.map_err(|err| anyhow!(err)).log_err() else {
- return Vec::new();
- };
-
- let Some(items) = database.keys().await.log_err() else {
- return Vec::new();
- };
-
- let candidates = items
- .iter()
- .enumerate()
- .map(|(ix, item_path)| StringMatchCandidate::new(ix, &item_path))
- .collect::<Vec<_>>();
-
- let matches = fuzzy::match_strings(
- &candidates,
- &query,
- false,
- true,
- 100,
- &AtomicBool::default(),
- executor,
- )
- .await;
-
- matches
- .into_iter()
- .map(|mat| items[mat.candidate_id].clone())
- .collect()
- })
- }
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Display, Serialize, Deserialize)]
-pub struct MarkdownDocs(pub String);
-
-pub struct IndexedDocsDatabase {
- executor: BackgroundExecutor,
- env: heed::Env,
- entries: Database<SerdeBincode<String>, SerdeBincode<MarkdownDocs>>,
-}
-
-impl IndexedDocsDatabase {
- pub fn new(path: PathBuf, executor: BackgroundExecutor) -> Result<Self> {
- std::fs::create_dir_all(&path)?;
-
- const ONE_GB_IN_BYTES: usize = 1024 * 1024 * 1024;
- let env = unsafe {
- heed::EnvOpenOptions::new()
- .map_size(ONE_GB_IN_BYTES)
- .max_dbs(1)
- .open(path)?
- };
-
- let mut txn = env.write_txn()?;
- let entries = env.create_database(&mut txn, Some("rustdoc_entries"))?;
- txn.commit()?;
-
- Ok(Self {
- executor,
- env,
- entries,
- })
- }
-
- pub fn keys(&self) -> Task<Result<Vec<String>>> {
- let env = self.env.clone();
- let entries = self.entries;
-
- self.executor.spawn(async move {
- let txn = env.read_txn()?;
- let mut iter = entries.iter(&txn)?;
- let mut keys = Vec::new();
- while let Some((key, _value)) = iter.next().transpose()? {
- keys.push(key);
- }
-
- Ok(keys)
- })
- }
-
- pub fn load(&self, key: String) -> Task<Result<MarkdownDocs>> {
- let env = self.env.clone();
- let entries = self.entries;
-
- self.executor.spawn(async move {
- let txn = env.read_txn()?;
- entries
- .get(&txn, &key)?
- .with_context(|| format!("no docs found for {key}"))
- })
- }
-
- pub fn load_many_by_prefix(&self, prefix: String) -> Task<Result<Vec<(String, MarkdownDocs)>>> {
- let env = self.env.clone();
- let entries = self.entries;
-
- self.executor.spawn(async move {
- let txn = env.read_txn()?;
- let results = entries
- .iter(&txn)?
- .filter_map(|entry| {
- let (key, value) = entry.ok()?;
- if key.starts_with(&prefix) {
- Some((key, value))
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
-
- Ok(results)
- })
- }
-
- /// Returns whether any entries exist with the given prefix.
- pub fn any_with_prefix(&self, prefix: String) -> Task<Result<bool>> {
- let env = self.env.clone();
- let entries = self.entries;
-
- self.executor.spawn(async move {
- let txn = env.read_txn()?;
- let any = entries
- .iter(&txn)?
- .any(|entry| entry.map_or(false, |(key, _value)| key.starts_with(&prefix)));
- Ok(any)
- })
- }
-
- pub fn insert(&self, key: String, docs: String) -> Task<Result<()>> {
- let env = self.env.clone();
- let entries = self.entries;
-
- self.executor.spawn(async move {
- let mut txn = env.write_txn()?;
- entries.put(&mut txn, &key, &MarkdownDocs(docs))?;
- txn.commit()?;
- Ok(())
- })
- }
-}
-
-impl extension::KeyValueStoreDelegate for IndexedDocsDatabase {
- fn insert(&self, key: String, docs: String) -> Task<Result<()>> {
- IndexedDocsDatabase::insert(&self, key, docs)
- }
-}
@@ -1358,7 +1358,7 @@ impl Render for LspLogToolbarItemView {
})
.collect();
- let log_toolbar_view = cx.entity().clone();
+ let log_toolbar_view = cx.entity();
let lsp_menu = PopoverMenu::new("LspLogView")
.anchor(Corner::TopLeft)
@@ -1007,7 +1007,7 @@ impl Render for LspTool {
(None, "All Servers Operational")
};
- let lsp_tool = cx.entity().clone();
+ let lsp_tool = cx.entity();
div().child(
PopoverMenu::new("lsp-tool")
@@ -456,7 +456,7 @@ impl SyntaxTreeToolbarItemView {
let active_layer = buffer_state.active_layer.clone()?;
let active_buffer = buffer_state.buffer.read(cx).snapshot();
- let view = cx.entity().clone();
+ let view = cx.entity();
Some(
PopoverMenu::new("Syntax Tree")
.trigger(Self::render_header(&active_layer))
@@ -37,7 +37,7 @@ const CONTENT: (Section<4>, Section<3>) = (
},
SectionEntry {
icon: IconName::CloudDownload,
- title: "Clone a Repo",
+ title: "Clone Repository",
action: &git::Clone,
},
SectionEntry {
@@ -4815,51 +4815,45 @@ impl OutlinePanel {
.when(show_indent_guides, |list| {
list.with_decoration(
ui::indent_guides(px(indent_size), IndentGuideColors::panel(cx))
- .with_compute_indents_fn(
- cx.entity().clone(),
- |outline_panel, range, _, _| {
- let entries = outline_panel.cached_entries.get(range);
- if let Some(entries) = entries {
- entries.into_iter().map(|item| item.depth).collect()
- } else {
- smallvec::SmallVec::new()
- }
- },
- )
- .with_render_fn(
- cx.entity().clone(),
- move |outline_panel, params, _, _| {
- const LEFT_OFFSET: Pixels = px(14.);
-
- let indent_size = params.indent_size;
- let item_height = params.item_height;
- let active_indent_guide_ix = find_active_indent_guide_ix(
- outline_panel,
- ¶ms.indent_guides,
- );
+ .with_compute_indents_fn(cx.entity(), |outline_panel, range, _, _| {
+ let entries = outline_panel.cached_entries.get(range);
+ if let Some(entries) = entries {
+ entries.into_iter().map(|item| item.depth).collect()
+ } else {
+ smallvec::SmallVec::new()
+ }
+ })
+ .with_render_fn(cx.entity(), move |outline_panel, params, _, _| {
+ const LEFT_OFFSET: Pixels = px(14.);
+
+ let indent_size = params.indent_size;
+ let item_height = params.item_height;
+ let active_indent_guide_ix = find_active_indent_guide_ix(
+ outline_panel,
+ ¶ms.indent_guides,
+ );
- params
- .indent_guides
- .into_iter()
- .enumerate()
- .map(|(ix, layout)| {
- let bounds = Bounds::new(
- point(
- layout.offset.x * indent_size + LEFT_OFFSET,
- layout.offset.y * item_height,
- ),
- size(px(1.), layout.length * item_height),
- );
- ui::RenderedIndentGuide {
- bounds,
- layout,
- is_active: active_indent_guide_ix == Some(ix),
- hitbox: None,
- }
- })
- .collect()
- },
- ),
+ params
+ .indent_guides
+ .into_iter()
+ .enumerate()
+ .map(|(ix, layout)| {
+ let bounds = Bounds::new(
+ point(
+ layout.offset.x * indent_size + LEFT_OFFSET,
+ layout.offset.y * item_height,
+ ),
+ size(px(1.), layout.length * item_height),
+ );
+ ui::RenderedIndentGuide {
+ bounds,
+ layout,
+ is_active: active_indent_guide_ix == Some(ix),
+ hitbox: None,
+ }
+ })
+ .collect()
+ }),
)
})
};
@@ -57,9 +57,9 @@ use std::{
};
use theme::ThemeSettings;
use ui::{
- Color, ContextMenu, DecoratedIcon, Icon, IconDecoration, IconDecorationKind, IndentGuideColors,
- IndentGuideLayout, KeyBinding, Label, LabelSize, ListItem, ListItemSpacing, ScrollableHandle,
- Scrollbar, ScrollbarState, StickyCandidate, Tooltip, prelude::*, v_flex,
+ Color, ContextMenu, DecoratedIcon, Divider, Icon, IconDecoration, IconDecorationKind,
+ IndentGuideColors, IndentGuideLayout, KeyBinding, Label, LabelSize, ListItem, ListItemSpacing,
+ ScrollableHandle, Scrollbar, ScrollbarState, StickyCandidate, Tooltip, prelude::*, v_flex,
};
use util::{ResultExt, TakeUntilExt, TryFutureExt, maybe, paths::compare_paths};
use workspace::{
@@ -69,7 +69,6 @@ use workspace::{
notifications::{DetachAndPromptErr, NotifyTaskExt},
};
use worktree::CreatedEntry;
-use zed_actions::OpenRecent;
const PROJECT_PANEL_KEY: &str = "ProjectPanel";
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
@@ -5351,26 +5350,22 @@ impl Render for ProjectPanel {
.when(show_indent_guides, |list| {
list.with_decoration(
ui::indent_guides(px(indent_size), IndentGuideColors::panel(cx))
- .with_compute_indents_fn(
- cx.entity().clone(),
- |this, range, window, cx| {
- let mut items =
- SmallVec::with_capacity(range.end - range.start);
- this.iter_visible_entries(
- range,
- window,
- cx,
- |entry, _, entries, _, _| {
- let (depth, _) =
- Self::calculate_depth_and_difference(
- entry, entries,
- );
- items.push(depth);
- },
- );
- items
- },
- )
+ .with_compute_indents_fn(cx.entity(), |this, range, window, cx| {
+ let mut items =
+ SmallVec::with_capacity(range.end - range.start);
+ this.iter_visible_entries(
+ range,
+ window,
+ cx,
+ |entry, _, entries, _, _| {
+ let (depth, _) = Self::calculate_depth_and_difference(
+ entry, entries,
+ );
+ items.push(depth);
+ },
+ );
+ items
+ })
.on_click(cx.listener(
|this, active_indent_guide: &IndentGuideLayout, window, cx| {
if window.modifiers().secondary() {
@@ -5394,7 +5389,7 @@ impl Render for ProjectPanel {
}
},
))
- .with_render_fn(cx.entity().clone(), move |this, params, _, cx| {
+ .with_render_fn(cx.entity(), move |this, params, _, cx| {
const LEFT_OFFSET: Pixels = px(14.);
const PADDING_Y: Pixels = px(4.);
const HITBOX_OVERDRAW: Pixels = px(3.);
@@ -5447,7 +5442,7 @@ impl Render for ProjectPanel {
})
.when(show_sticky_entries, |list| {
let sticky_items = ui::sticky_items(
- cx.entity().clone(),
+ cx.entity(),
|this, range, window, cx| {
let mut items = SmallVec::with_capacity(range.end - range.start);
this.iter_visible_entries(
@@ -5474,7 +5469,7 @@ impl Render for ProjectPanel {
list.with_decoration(if show_indent_guides {
sticky_items.with_decoration(
ui::indent_guides(px(indent_size), IndentGuideColors::panel(cx))
- .with_render_fn(cx.entity().clone(), move |_, params, _, _| {
+ .with_render_fn(cx.entity(), move |_, params, _, _| {
const LEFT_OFFSET: Pixels = px(14.);
let indent_size = params.indent_size;
@@ -5525,24 +5520,48 @@ impl Render for ProjectPanel {
.with_priority(3)
}))
} else {
+ let focus_handle = self.focus_handle(cx).clone();
+
v_flex()
.id("empty-project_panel")
- .size_full()
.p_4()
+ .size_full()
+ .items_center()
+ .justify_center()
+ .gap_1()
.track_focus(&self.focus_handle(cx))
.child(
- Button::new("open_project", "Open a project")
+ Button::new("open_project", "Open Project")
.full_width()
.key_binding(KeyBinding::for_action_in(
- &OpenRecent::default(),
- &self.focus_handle,
+ &workspace::Open,
+ &focus_handle,
window,
cx,
))
.on_click(cx.listener(|this, _, window, cx| {
this.workspace
.update(cx, |_, cx| {
- window.dispatch_action(OpenRecent::default().boxed_clone(), cx);
+ window.dispatch_action(workspace::Open.boxed_clone(), cx);
+ })
+ .log_err();
+ })),
+ )
+ .child(
+ h_flex()
+ .w_1_2()
+ .gap_2()
+ .child(Divider::horizontal())
+ .child(Label::new("or").size(LabelSize::XSmall).color(Color::Muted))
+ .child(Divider::horizontal()),
+ )
+ .child(
+ Button::new("clone_repo", "Clone Repository")
+ .full_width()
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.workspace
+ .update(cx, |_, cx| {
+ window.dispatch_action(git::Clone.boxed_clone(), cx);
})
.log_err();
})),
@@ -158,14 +158,6 @@ message SynchronizeContextsResponse {
repeated ContextVersion contexts = 1;
}
-message GetLlmToken {}
-
-message GetLlmTokenResponse {
- string token = 1;
-}
-
-message RefreshLlmToken {}
-
enum LanguageModelRole {
LanguageModelUser = 0;
LanguageModelAssistant = 1;
@@ -6,62 +6,6 @@ message UpdateInviteInfo {
uint32 count = 2;
}
-message GetPrivateUserInfo {}
-
-message GetPrivateUserInfoResponse {
- string metrics_id = 1;
- bool staff = 2;
- repeated string flags = 3;
- optional uint64 accepted_tos_at = 4;
-}
-
-enum Plan {
- Free = 0;
- ZedPro = 1;
- ZedProTrial = 2;
-}
-
-message UpdateUserPlan {
- Plan plan = 1;
- optional uint64 trial_started_at = 2;
- optional bool is_usage_based_billing_enabled = 3;
- optional SubscriptionUsage usage = 4;
- optional SubscriptionPeriod subscription_period = 5;
- optional bool account_too_young = 6;
- optional bool has_overdue_invoices = 7;
-}
-
-message SubscriptionPeriod {
- uint64 started_at = 1;
- uint64 ended_at = 2;
-}
-
-message SubscriptionUsage {
- uint32 model_requests_usage_amount = 1;
- UsageLimit model_requests_usage_limit = 2;
- uint32 edit_predictions_usage_amount = 3;
- UsageLimit edit_predictions_usage_limit = 4;
-}
-
-message UsageLimit {
- oneof variant {
- Limited limited = 1;
- Unlimited unlimited = 2;
- }
-
- message Limited {
- uint32 limit = 1;
- }
-
- message Unlimited {}
-}
-
-message AcceptTermsOfService {}
-
-message AcceptTermsOfServiceResponse {
- uint64 accepted_tos_at = 1;
-}
-
message ShutdownRemoteServer {}
message Toast {
@@ -84,11 +28,13 @@ message GetCrashFiles {
message GetCrashFilesResponse {
repeated CrashReport crashes = 1;
+ repeated string legacy_panics = 2;
}
message CrashReport {
- optional string panic_contents = 1;
- optional bytes minidump_contents = 2;
+ reserved 1, 2;
+ string metadata = 3;
+ bytes minidump_contents = 4;
}
message Extension {
@@ -135,12 +135,7 @@ message Envelope {
FollowResponse follow_response = 99;
UpdateFollowers update_followers = 100;
Unfollow unfollow = 101;
- GetPrivateUserInfo get_private_user_info = 102;
- GetPrivateUserInfoResponse get_private_user_info_response = 103;
- UpdateUserPlan update_user_plan = 234;
UpdateDiffBases update_diff_bases = 104;
- AcceptTermsOfService accept_terms_of_service = 239;
- AcceptTermsOfServiceResponse accept_terms_of_service_response = 240;
OnTypeFormatting on_type_formatting = 105;
OnTypeFormattingResponse on_type_formatting_response = 106;
@@ -250,10 +245,6 @@ message Envelope {
AddWorktree add_worktree = 222;
AddWorktreeResponse add_worktree_response = 223;
- GetLlmToken get_llm_token = 235;
- GetLlmTokenResponse get_llm_token_response = 236;
- RefreshLlmToken refresh_llm_token = 259;
-
LspExtSwitchSourceHeader lsp_ext_switch_source_header = 241;
LspExtSwitchSourceHeaderResponse lsp_ext_switch_source_header_response = 242;
@@ -406,6 +397,7 @@ message Envelope {
}
reserved 87 to 88;
+ reserved 102 to 103;
reserved 158 to 161;
reserved 164;
reserved 166 to 169;
@@ -419,10 +411,13 @@ message Envelope {
reserved 221;
reserved 224 to 229;
reserved 230 to 231;
+ reserved 234 to 236;
+ reserved 239 to 240;
reserved 246;
- reserved 270;
reserved 247 to 254;
reserved 255 to 256;
+ reserved 259;
+ reserved 270;
reserved 280 to 281;
reserved 332 to 333;
}
@@ -20,8 +20,6 @@ pub const SSH_PEER_ID: PeerId = PeerId { owner_id: 0, id: 0 };
pub const SSH_PROJECT_ID: u64 = 0;
messages!(
- (AcceptTermsOfService, Foreground),
- (AcceptTermsOfServiceResponse, Foreground),
(Ack, Foreground),
(AckBufferOperation, Background),
(AckChannelMessage, Background),
@@ -105,8 +103,6 @@ messages!(
(GetPathMetadataResponse, Background),
(GetPermalinkToLine, Foreground),
(GetPermalinkToLineResponse, Foreground),
- (GetPrivateUserInfo, Foreground),
- (GetPrivateUserInfoResponse, Foreground),
(GetProjectSymbols, Background),
(GetProjectSymbolsResponse, Background),
(GetReferences, Background),
@@ -119,8 +115,6 @@ messages!(
(GetTypeDefinitionResponse, Background),
(GetImplementation, Background),
(GetImplementationResponse, Background),
- (GetLlmToken, Background),
- (GetLlmTokenResponse, Background),
(OpenUnstagedDiff, Foreground),
(OpenUnstagedDiffResponse, Foreground),
(OpenUncommittedDiff, Foreground),
@@ -196,7 +190,6 @@ messages!(
(PrepareRenameResponse, Background),
(ProjectEntryResponse, Foreground),
(RefreshInlayHints, Foreground),
- (RefreshLlmToken, Background),
(RegisterBufferWithLanguageServers, Background),
(RejoinChannelBuffers, Foreground),
(RejoinChannelBuffersResponse, Foreground),
@@ -280,7 +273,6 @@ messages!(
(UpdateProject, Foreground),
(UpdateProjectCollaborator, Foreground),
(UpdateUserChannels, Foreground),
- (UpdateUserPlan, Foreground),
(UpdateWorktree, Foreground),
(UpdateWorktreeSettings, Foreground),
(UpdateRepository, Foreground),
@@ -321,7 +313,6 @@ messages!(
);
request_messages!(
- (AcceptTermsOfService, AcceptTermsOfServiceResponse),
(ApplyCodeAction, ApplyCodeActionResponse),
(
ApplyCompletionAdditionalEdits,
@@ -354,9 +345,7 @@ request_messages!(
(GetDocumentHighlights, GetDocumentHighlightsResponse),
(GetDocumentSymbols, GetDocumentSymbolsResponse),
(GetHover, GetHoverResponse),
- (GetLlmToken, GetLlmTokenResponse),
(GetNotifications, GetNotificationsResponse),
- (GetPrivateUserInfo, GetPrivateUserInfoResponse),
(GetProjectSymbols, GetProjectSymbolsResponse),
(GetReferences, GetReferencesResponse),
(GetSignatureHelp, GetSignatureHelpResponse),
@@ -1094,11 +1094,10 @@ impl RemoteServerProjects {
.size(LabelSize::Small),
)
.child(
- Button::new("learn-more", "Learn moreβ¦")
+ Button::new("learn-more", "Learn More")
.label_size(LabelSize::Small)
- .size(ButtonSize::None)
- .color(Color::Accent)
- .style(ButtonStyle::Transparent)
+ .icon(IconName::ArrowUpRight)
+ .icon_size(IconSize::XSmall)
.on_click(|_, _, cx| {
cx.open_url(
"https://zed.dev/docs/remote-development",
@@ -1292,7 +1291,7 @@ impl RemoteServerProjects {
let connection_string = connection_string.clone();
move |_, _: &menu::Confirm, window, cx| {
remove_ssh_server(
- cx.entity().clone(),
+ cx.entity(),
server_index,
connection_string.clone(),
window,
@@ -1312,7 +1311,7 @@ impl RemoteServerProjects {
.child(Label::new("Remove Server").color(Color::Error))
.on_click(cx.listener(move |_, _, window, cx| {
remove_ssh_server(
- cx.entity().clone(),
+ cx.entity(),
server_index,
connection_string.clone(),
window,
@@ -400,6 +400,7 @@ impl SshSocket {
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
+ .args(self.connection_options.additional_args())
.args(["-o", "ControlMaster=no", "-o"])
.arg(format!("ControlPath={}", self.socket_path.display()))
}
@@ -410,6 +411,7 @@ impl SshSocket {
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
+ .args(self.connection_options.additional_args())
.envs(self.envs.clone())
}
@@ -417,22 +419,26 @@ impl SshSocket {
// On Linux, we use the `ControlPath` option to create a socket file that ssh can use to
#[cfg(not(target_os = "windows"))]
fn ssh_args(&self) -> SshArgs {
+ let mut arguments = self.connection_options.additional_args();
+ arguments.extend(vec![
+ "-o".to_string(),
+ "ControlMaster=no".to_string(),
+ "-o".to_string(),
+ format!("ControlPath={}", self.socket_path.display()),
+ self.connection_options.ssh_url(),
+ ]);
SshArgs {
- arguments: vec![
- "-o".to_string(),
- "ControlMaster=no".to_string(),
- "-o".to_string(),
- format!("ControlPath={}", self.socket_path.display()),
- self.connection_options.ssh_url(),
- ],
+ arguments,
envs: None,
}
}
#[cfg(target_os = "windows")]
fn ssh_args(&self) -> SshArgs {
+ let mut arguments = self.connection_options.additional_args();
+ arguments.push(self.connection_options.ssh_url());
SshArgs {
- arguments: vec![self.connection_options.ssh_url()],
+ arguments,
envs: Some(self.envs.clone()),
}
}
@@ -1484,20 +1490,17 @@ impl RemoteConnection for SshRemoteConnection {
identifier = &unique_identifier,
);
- if let Some(rust_log) = std::env::var("RUST_LOG").ok() {
- start_proxy_command = format!(
- "RUST_LOG={} {}",
- shlex::try_quote(&rust_log).unwrap(),
- start_proxy_command
- )
- }
- if let Some(rust_backtrace) = std::env::var("RUST_BACKTRACE").ok() {
- start_proxy_command = format!(
- "RUST_BACKTRACE={} {}",
- shlex::try_quote(&rust_backtrace).unwrap(),
- start_proxy_command
- )
+ for env_var in ["RUST_LOG", "RUST_BACKTRACE", "ZED_GENERATE_MINIDUMPS"] {
+ if let Some(value) = std::env::var(env_var).ok() {
+ start_proxy_command = format!(
+ "{}={} {} ",
+ env_var,
+ shlex::try_quote(&value).unwrap(),
+ start_proxy_command,
+ );
+ }
}
+
if reconnect {
start_proxy_command.push_str(" --reconnect");
}
@@ -2235,8 +2238,7 @@ impl SshRemoteConnection {
#[cfg(not(target_os = "windows"))]
{
- run_cmd(Command::new("gzip").args(["-9", "-f", &bin_path.to_string_lossy()]))
- .await?;
+ run_cmd(Command::new("gzip").args(["-f", &bin_path.to_string_lossy()])).await?;
}
#[cfg(target_os = "windows")]
{
@@ -2468,7 +2470,7 @@ impl ChannelClient {
},
async {
smol::Timer::after(timeout).await;
- anyhow::bail!("Timeout detected")
+ anyhow::bail!("Timed out resyncing remote client")
},
)
.await
@@ -2482,7 +2484,7 @@ impl ChannelClient {
},
async {
smol::Timer::after(timeout).await;
- anyhow::bail!("Timeout detected")
+ anyhow::bail!("Timed out pinging remote client")
},
)
.await
@@ -34,10 +34,10 @@ use smol::io::AsyncReadExt;
use smol::Async;
use smol::{net::unix::UnixListener, stream::StreamExt as _};
-use std::collections::HashMap;
use std::ffi::OsStr;
use std::ops::ControlFlow;
use std::str::FromStr;
+use std::sync::LazyLock;
use std::{env, thread};
use std::{
io::Write,
@@ -48,6 +48,13 @@ use std::{
use telemetry_events::LocationData;
use util::ResultExt;
+pub static VERSION: LazyLock<&str> = LazyLock::new(|| match *RELEASE_CHANNEL {
+ ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION"),
+ ReleaseChannel::Nightly | ReleaseChannel::Dev => {
+ option_env!("ZED_COMMIT_SHA").unwrap_or("missing-zed-commit-sha")
+ }
+});
+
fn init_logging_proxy() {
env_logger::builder()
.format(|buf, record| {
@@ -113,7 +120,6 @@ fn init_logging_server(log_file_path: PathBuf) -> Result<Receiver<Vec<u8>>> {
fn init_panic_hook(session_id: String) {
std::panic::set_hook(Box::new(move |info| {
- crashes::handle_panic();
let payload = info
.payload()
.downcast_ref::<&str>()
@@ -121,6 +127,8 @@ fn init_panic_hook(session_id: String) {
.or_else(|| info.payload().downcast_ref::<String>().cloned())
.unwrap_or_else(|| "Box<Any>".to_string());
+ crashes::handle_panic(payload.clone(), info.location());
+
let backtrace = backtrace::Backtrace::new();
let mut backtrace = backtrace
.frames()
@@ -150,14 +158,6 @@ fn init_panic_hook(session_id: String) {
(&backtrace).join("\n")
);
- let release_channel = *RELEASE_CHANNEL;
- let version = match release_channel {
- ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION"),
- ReleaseChannel::Nightly | ReleaseChannel::Dev => {
- option_env!("ZED_COMMIT_SHA").unwrap_or("missing-zed-commit-sha")
- }
- };
-
let panic_data = telemetry_events::Panic {
thread: thread_name.into(),
payload: payload.clone(),
@@ -165,9 +165,9 @@ fn init_panic_hook(session_id: String) {
file: location.file().into(),
line: location.line(),
}),
- app_version: format!("remote-server-{version}"),
+ app_version: format!("remote-server-{}", *VERSION),
app_commit_sha: option_env!("ZED_COMMIT_SHA").map(|sha| sha.into()),
- release_channel: release_channel.dev_name().into(),
+ release_channel: RELEASE_CHANNEL.dev_name().into(),
target: env!("TARGET").to_owned().into(),
os_name: telemetry::os_name(),
os_version: Some(telemetry::os_version()),
@@ -204,8 +204,8 @@ fn handle_crash_files_requests(project: &Entity<HeadlessProject>, client: &Arc<C
client.add_request_handler(
project.downgrade(),
|_, _: TypedEnvelope<proto::GetCrashFiles>, _cx| async move {
+ let mut legacy_panics = Vec::new();
let mut crashes = Vec::new();
- let mut minidumps_by_session_id = HashMap::new();
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
while let Some(child) = children.next().await {
let child = child?;
@@ -227,41 +227,31 @@ fn handle_crash_files_requests(project: &Entity<HeadlessProject>, client: &Arc<C
.await
.context("error reading panic file")?;
- crashes.push(proto::CrashReport {
- panic_contents: Some(file_contents),
- minidump_contents: None,
- });
+ legacy_panics.push(file_contents);
+ smol::fs::remove_file(&child_path)
+ .await
+ .context("error removing panic")
+ .log_err();
} else if extension == Some(OsStr::new("dmp")) {
- let session_id = child_path.file_stem().unwrap().to_string_lossy();
- minidumps_by_session_id
- .insert(session_id.to_string(), smol::fs::read(&child_path).await?);
- }
-
- // We've done what we can, delete the file
- smol::fs::remove_file(&child_path)
- .await
- .context("error removing panic")
- .log_err();
- }
-
- for crash in &mut crashes {
- let panic: telemetry_events::Panic =
- serde_json::from_str(crash.panic_contents.as_ref().unwrap())?;
- if let dump @ Some(_) = minidumps_by_session_id.remove(&panic.session_id) {
- crash.minidump_contents = dump;
+ let mut json_path = child_path.clone();
+ json_path.set_extension("json");
+ if let Ok(json_content) = smol::fs::read_to_string(&json_path).await {
+ crashes.push(CrashReport {
+ metadata: json_content,
+ minidump_contents: smol::fs::read(&child_path).await?,
+ });
+ smol::fs::remove_file(&child_path).await.log_err();
+ smol::fs::remove_file(&json_path).await.log_err();
+ } else {
+ log::error!("Couldn't find json metadata for crash: {child_path:?}");
+ }
}
}
- crashes.extend(
- minidumps_by_session_id
- .into_values()
- .map(|dmp| CrashReport {
- panic_contents: None,
- minidump_contents: Some(dmp),
- }),
- );
-
- anyhow::Ok(proto::GetCrashFilesResponse { crashes })
+ anyhow::Ok(proto::GetCrashFilesResponse {
+ crashes,
+ legacy_panics,
+ })
},
);
}
@@ -442,7 +432,12 @@ pub fn execute_run(
let app = gpui::Application::headless();
let id = std::process::id().to_string();
app.background_executor()
- .spawn(crashes::init(id.clone()))
+ .spawn(crashes::init(crashes::InitCrashHandler {
+ session_id: id.clone(),
+ zed_version: VERSION.to_owned(),
+ release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
+ commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(),
+ }))
.detach();
init_panic_hook(id);
let log_rx = init_logging_server(log_file)?;
@@ -569,7 +564,13 @@ pub fn execute_proxy(identifier: String, is_reconnecting: bool) -> Result<()> {
let server_paths = ServerPaths::new(&identifier)?;
let id = std::process::id().to_string();
- smol::spawn(crashes::init(id.clone())).detach();
+ smol::spawn(crashes::init(crashes::InitCrashHandler {
+ session_id: id.clone(),
+ zed_version: VERSION.to_owned(),
+ release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
+ commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(),
+ }))
+ .detach();
init_panic_hook(id);
log::info!("starting proxy process. PID: {}", std::process::id());
@@ -244,7 +244,7 @@ impl Session {
repl_session_id = cx.entity_id().to_string(),
);
- let session_view = cx.entity().clone();
+ let session_view = cx.entity();
let kernel = match self.kernel_specification.clone() {
KernelSpecification::Jupyter(kernel_specification)
@@ -2,9 +2,9 @@ mod registrar;
use crate::{
FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOption,
- SearchOptions, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive,
- ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord,
- search_bar::{input_base_styles, render_action_button, render_text_input},
+ SearchOptions, SearchSource, SelectAllMatches, SelectNextMatch, SelectPreviousMatch,
+ ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord,
+ search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},
};
use any_vec::AnyVec;
use anyhow::Context as _;
@@ -213,22 +213,25 @@ impl Render for BufferSearchBar {
h_flex()
.gap_1()
.when(case, |div| {
- div.child(
- SearchOption::CaseSensitive
- .as_button(self.search_options, focus_handle.clone()),
- )
+ div.child(SearchOption::CaseSensitive.as_button(
+ self.search_options,
+ SearchSource::Buffer,
+ focus_handle.clone(),
+ ))
})
.when(word, |div| {
- div.child(
- SearchOption::WholeWord
- .as_button(self.search_options, focus_handle.clone()),
- )
+ div.child(SearchOption::WholeWord.as_button(
+ self.search_options,
+ SearchSource::Buffer,
+ focus_handle.clone(),
+ ))
})
.when(regex, |div| {
- div.child(
- SearchOption::Regex
- .as_button(self.search_options, focus_handle.clone()),
- )
+ div.child(SearchOption::Regex.as_button(
+ self.search_options,
+ SearchSource::Buffer,
+ focus_handle.clone(),
+ ))
}),
)
});
@@ -240,7 +243,7 @@ impl Render for BufferSearchBar {
this.child(render_action_button(
"buffer-search-bar-toggle",
IconName::Replace,
- self.replace_enabled,
+ self.replace_enabled.then_some(ActionButtonState::Toggled),
"Toggle Replace",
&ToggleReplace,
focus_handle.clone(),
@@ -285,7 +288,9 @@ impl Render for BufferSearchBar {
.child(render_action_button(
"buffer-search-nav-button",
ui::IconName::ChevronLeft,
- self.active_match_index.is_some(),
+ self.active_match_index
+ .is_none()
+ .then_some(ActionButtonState::Disabled),
"Select Previous Match",
&SelectPreviousMatch,
query_focus.clone(),
@@ -293,7 +298,9 @@ impl Render for BufferSearchBar {
.child(render_action_button(
"buffer-search-nav-button",
ui::IconName::ChevronRight,
- self.active_match_index.is_some(),
+ self.active_match_index
+ .is_none()
+ .then_some(ActionButtonState::Disabled),
"Select Next Match",
&SelectNextMatch,
query_focus.clone(),
@@ -313,7 +320,7 @@ impl Render for BufferSearchBar {
el.child(render_action_button(
"buffer-search-nav-button",
IconName::SelectAll,
- true,
+ Default::default(),
"Select All Matches",
&SelectAllMatches,
query_focus,
@@ -324,7 +331,7 @@ impl Render for BufferSearchBar {
el.child(render_action_button(
"buffer-search",
IconName::Close,
- true,
+ Default::default(),
"Close Search Bar",
&Dismiss,
focus_handle.clone(),
@@ -352,7 +359,7 @@ impl Render for BufferSearchBar {
.child(render_action_button(
"buffer-search-replace-button",
IconName::ReplaceNext,
- true,
+ Default::default(),
"Replace Next Match",
&ReplaceNext,
focus_handle.clone(),
@@ -360,7 +367,7 @@ impl Render for BufferSearchBar {
.child(render_action_button(
"buffer-search-replace-button",
IconName::ReplaceAll,
- true,
+ Default::default(),
"Replace All Matches",
&ReplaceAll,
focus_handle,
@@ -394,7 +401,7 @@ impl Render for BufferSearchBar {
div.child(h_flex().absolute().right_0().child(render_action_button(
"buffer-search",
IconName::Close,
- true,
+ Default::default(),
"Close Search Bar",
&Dismiss,
focus_handle.clone(),
@@ -1,9 +1,9 @@
use crate::{
BufferSearchBar, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
- SearchOption, SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive,
- ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord,
+ SearchOption, SearchOptions, SearchSource, SelectNextMatch, SelectPreviousMatch,
+ ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord,
buffer_search::Deploy,
- search_bar::{input_base_styles, render_action_button, render_text_input},
+ search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},
};
use anyhow::Context as _;
use collections::HashMap;
@@ -1665,7 +1665,7 @@ impl ProjectSearchBar {
});
}
- fn toggle_search_option(
+ pub(crate) fn toggle_search_option(
&mut self,
option: SearchOptions,
window: &mut Window,
@@ -1962,17 +1962,21 @@ impl Render for ProjectSearchBar {
.child(
h_flex()
.gap_1()
- .child(
- SearchOption::CaseSensitive
- .as_button(search.search_options, focus_handle.clone()),
- )
- .child(
- SearchOption::WholeWord
- .as_button(search.search_options, focus_handle.clone()),
- )
- .child(
- SearchOption::Regex.as_button(search.search_options, focus_handle.clone()),
- ),
+ .child(SearchOption::CaseSensitive.as_button(
+ search.search_options,
+ SearchSource::Project(cx),
+ focus_handle.clone(),
+ ))
+ .child(SearchOption::WholeWord.as_button(
+ search.search_options,
+ SearchSource::Project(cx),
+ focus_handle.clone(),
+ ))
+ .child(SearchOption::Regex.as_button(
+ search.search_options,
+ SearchSource::Project(cx),
+ focus_handle.clone(),
+ )),
);
let query_focus = search.query_editor.focus_handle(cx);
@@ -1985,7 +1989,10 @@ impl Render for ProjectSearchBar {
.child(render_action_button(
"project-search-nav-button",
IconName::ChevronLeft,
- search.active_match_index.is_some(),
+ search
+ .active_match_index
+ .is_none()
+ .then_some(ActionButtonState::Disabled),
"Select Previous Match",
&SelectPreviousMatch,
query_focus.clone(),
@@ -1993,7 +2000,10 @@ impl Render for ProjectSearchBar {
.child(render_action_button(
"project-search-nav-button",
IconName::ChevronRight,
- search.active_match_index.is_some(),
+ search
+ .active_match_index
+ .is_none()
+ .then_some(ActionButtonState::Disabled),
"Select Next Match",
&SelectNextMatch,
query_focus,
@@ -2054,7 +2064,7 @@ impl Render for ProjectSearchBar {
self.active_project_search
.as_ref()
.map(|search| search.read(cx).replace_enabled)
- .unwrap_or_default(),
+ .and_then(|enabled| enabled.then_some(ActionButtonState::Toggled)),
"Toggle Replace",
&ToggleReplace,
focus_handle.clone(),
@@ -2079,7 +2089,7 @@ impl Render for ProjectSearchBar {
.child(render_action_button(
"project-search-replace-button",
IconName::ReplaceNext,
- true,
+ Default::default(),
"Replace Next Match",
&ReplaceNext,
focus_handle.clone(),
@@ -2087,7 +2097,7 @@ impl Render for ProjectSearchBar {
.child(render_action_button(
"project-search-replace-button",
IconName::ReplaceAll,
- true,
+ Default::default(),
"Replace All Matches",
&ReplaceAll,
focus_handle,
@@ -2129,10 +2139,11 @@ impl Render for ProjectSearchBar {
this.toggle_opened_only(window, cx);
})),
)
- .child(
- SearchOption::IncludeIgnored
- .as_button(search.search_options, focus_handle.clone()),
- );
+ .child(SearchOption::IncludeIgnored.as_button(
+ search.search_options,
+ SearchSource::Project(cx),
+ focus_handle.clone(),
+ ));
h_flex()
.w_full()
.gap_2()
@@ -1,7 +1,7 @@
use bitflags::bitflags;
pub use buffer_search::BufferSearchBar;
use editor::SearchSettings;
-use gpui::{Action, App, FocusHandle, IntoElement, actions};
+use gpui::{Action, App, ClickEvent, FocusHandle, IntoElement, actions};
use project::search::SearchQuery;
pub use project_search::ProjectSearchView;
use ui::{ButtonStyle, IconButton, IconButtonShape};
@@ -11,6 +11,8 @@ use workspace::{Toast, Workspace};
pub use search_status_button::SEARCH_ICON;
+use crate::project_search::ProjectSearchBar;
+
pub mod buffer_search;
pub mod project_search;
pub(crate) mod search_bar;
@@ -83,9 +85,14 @@ pub enum SearchOption {
Backwards,
}
+pub(crate) enum SearchSource<'a, 'b> {
+ Buffer,
+ Project(&'a Context<'b, ProjectSearchBar>),
+}
+
impl SearchOption {
- pub fn as_options(self) -> SearchOptions {
- SearchOptions::from_bits(1 << self as u8).unwrap()
+ pub fn as_options(&self) -> SearchOptions {
+ SearchOptions::from_bits(1 << *self as u8).unwrap()
}
pub fn label(&self) -> &'static str {
@@ -119,25 +126,41 @@ impl SearchOption {
}
}
- pub fn as_button(&self, active: SearchOptions, focus_handle: FocusHandle) -> impl IntoElement {
+ pub(crate) fn as_button(
+ &self,
+ active: SearchOptions,
+ search_source: SearchSource,
+ focus_handle: FocusHandle,
+ ) -> impl IntoElement {
let action = self.to_toggle_action();
let label = self.label();
- IconButton::new(label, self.icon())
- .on_click({
+ IconButton::new(
+ (label, matches!(search_source, SearchSource::Buffer) as u32),
+ self.icon(),
+ )
+ .map(|button| match search_source {
+ SearchSource::Buffer => {
let focus_handle = focus_handle.clone();
- move |_, window, cx| {
+ button.on_click(move |_: &ClickEvent, window, cx| {
if !focus_handle.is_focused(&window) {
window.focus(&focus_handle);
}
- window.dispatch_action(action.boxed_clone(), cx)
- }
- })
- .style(ButtonStyle::Subtle)
- .shape(IconButtonShape::Square)
- .toggle_state(active.contains(self.as_options()))
- .tooltip({
- move |window, cx| Tooltip::for_action_in(label, action, &focus_handle, window, cx)
- })
+ window.dispatch_action(action.boxed_clone(), cx);
+ })
+ }
+ SearchSource::Project(cx) => {
+ let options = self.as_options();
+ button.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
+ this.toggle_search_option(options, window, cx);
+ }))
+ }
+ })
+ .style(ButtonStyle::Subtle)
+ .shape(IconButtonShape::Square)
+ .toggle_state(active.contains(self.as_options()))
+ .tooltip({
+ move |window, cx| Tooltip::for_action_in(label, action, &focus_handle, window, cx)
+ })
}
}
@@ -5,10 +5,15 @@ use theme::ThemeSettings;
use ui::{IconButton, IconButtonShape};
use ui::{Tooltip, prelude::*};
+pub(super) enum ActionButtonState {
+ Disabled,
+ Toggled,
+}
+
pub(super) fn render_action_button(
id_prefix: &'static str,
icon: ui::IconName,
- active: bool,
+ button_state: Option<ActionButtonState>,
tooltip: &'static str,
action: &'static dyn Action,
focus_handle: FocusHandle,
@@ -28,7 +33,10 @@ pub(super) fn render_action_button(
}
})
.tooltip(move |window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, window, cx))
- .disabled(!active)
+ .when_some(button_state, |this, state| match state {
+ ActionButtonState::Toggled => this.toggle_state(true),
+ ActionButtonState::Disabled => this.disabled(true),
+ })
}
pub(crate) fn input_base_styles(border_color: Hsla, map: impl FnOnce(Div) -> Div) -> Div {
@@ -2181,6 +2181,7 @@ impl KeybindingEditorModal {
let value = action_arguments
.as_ref()
+ .filter(|args| !args.is_empty())
.map(|args| {
serde_json::from_str(args).context("Failed to parse action arguments as JSON")
})
@@ -65,7 +65,7 @@ impl Render for IndentGuidesStory {
},
)
.with_compute_indents_fn(
- cx.entity().clone(),
+ cx.entity(),
|this, range, _cx, _context| {
this.depths
.iter()
@@ -2,7 +2,7 @@
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
-use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration};
+use std::{collections::HashMap, fmt::Display, time::Duration};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EventRequestBody {
@@ -93,19 +93,6 @@ impl Display for AssistantPhase {
#[serde(tag = "type")]
pub enum Event {
Flexible(FlexibleEvent),
- Editor(EditorEvent),
- EditPrediction(EditPredictionEvent),
- EditPredictionRating(EditPredictionRatingEvent),
- Call(CallEvent),
- Assistant(AssistantEventData),
- Cpu(CpuEvent),
- Memory(MemoryEvent),
- App(AppEvent),
- Setting(SettingEvent),
- Extension(ExtensionEvent),
- Edit(EditEvent),
- Action(ActionEvent),
- Repl(ReplEvent),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -114,54 +101,12 @@ pub struct FlexibleEvent {
pub event_properties: HashMap<String, serde_json::Value>,
}
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct EditorEvent {
- /// The editor operation performed (open, save)
- pub operation: String,
- /// The extension of the file that was opened or saved
- pub file_extension: Option<String>,
- /// Whether the user is in vim mode or not
- pub vim_mode: bool,
- /// Whether the user has copilot enabled or not
- pub copilot_enabled: bool,
- /// Whether the user has copilot enabled for the language of the file opened or saved
- pub copilot_enabled_for_language: bool,
- /// Whether the client is opening/saving a local file or a remote file via SSH
- #[serde(default)]
- pub is_via_ssh: bool,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct EditPredictionEvent {
- /// Provider of the completion suggestion (e.g. copilot, supermaven)
- pub provider: String,
- pub suggestion_accepted: bool,
- pub file_extension: Option<String>,
-}
-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum EditPredictionRating {
Positive,
Negative,
}
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct EditPredictionRatingEvent {
- pub rating: EditPredictionRating,
- pub input_events: Arc<str>,
- pub input_excerpt: Arc<str>,
- pub output_excerpt: Arc<str>,
- pub feedback: String,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct CallEvent {
- /// Operation performed: invite/join call; begin/end screenshare; share/unshare project; etc
- pub operation: String,
- pub room_id: Option<u64>,
- pub channel_id: Option<u64>,
-}
-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AssistantEventData {
/// Unique random identifier for each assistant tab (None for inline assist)
@@ -180,57 +125,6 @@ pub struct AssistantEventData {
pub language_name: Option<String>,
}
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct CpuEvent {
- pub usage_as_percentage: f32,
- pub core_count: u32,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct MemoryEvent {
- pub memory_in_bytes: u64,
- pub virtual_memory_in_bytes: u64,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct ActionEvent {
- pub source: String,
- pub action: String,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct EditEvent {
- pub duration: i64,
- pub environment: String,
- /// Whether the edits occurred locally or remotely via SSH
- #[serde(default)]
- pub is_via_ssh: bool,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct SettingEvent {
- pub setting: String,
- pub value: String,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct ExtensionEvent {
- pub extension_id: Arc<str>,
- pub version: Arc<str>,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct AppEvent {
- pub operation: String,
-}
-
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct ReplEvent {
- pub kernel_language: String,
- pub kernel_status: String,
- pub repl_session_id: String,
-}
-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BacktraceFrame {
pub ip: usize,
@@ -947,7 +947,7 @@ pub fn new_terminal_pane(
cx: &mut Context<TerminalPanel>,
) -> Entity<Pane> {
let is_local = project.read(cx).is_local();
- let terminal_panel = cx.entity().clone();
+ let terminal_panel = cx.entity();
let pane = cx.new(|cx| {
let mut pane = Pane::new(
workspace.clone(),
@@ -1009,7 +1009,7 @@ pub fn new_terminal_pane(
return ControlFlow::Break(());
};
if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>() {
- let this_pane = cx.entity().clone();
+ let this_pane = cx.entity();
let item = if tab.pane == this_pane {
pane.item_for_index(tab.ix)
} else {
@@ -1491,7 +1491,7 @@ impl TerminalView {
impl Render for TerminalView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let terminal_handle = self.terminal.clone();
- let terminal_view_handle = cx.entity().clone();
+ let terminal_view_handle = cx.entity();
let focused = self.focus_handle.is_focused(window);
@@ -19,6 +19,7 @@ use util::ResultExt as _;
use util::schemars::replace_subschema;
const MIN_FONT_SIZE: Pixels = px(6.0);
+const MAX_FONT_SIZE: Pixels = px(100.0);
const MIN_LINE_HEIGHT: f32 = 1.0;
#[derive(
@@ -103,8 +104,8 @@ pub struct ThemeSettings {
///
/// The terminal font family can be overridden using it's own setting.
pub buffer_font: Font,
- /// The agent font size. Determines the size of text in the agent panel.
- agent_font_size: Pixels,
+ /// The agent font size. Determines the size of text in the agent panel. Falls back to the UI font size if unset.
+ agent_font_size: Option<Pixels>,
/// The line height for buffers, and the terminal.
///
/// Changing this may affect the spacing of some UI elements.
@@ -404,9 +405,9 @@ pub struct ThemeSettingsContent {
#[serde(default)]
#[schemars(default = "default_font_features")]
pub buffer_font_features: Option<FontFeatures>,
- /// The font size for the agent panel.
+ /// The font size for the agent panel. Falls back to the UI font size if unset.
#[serde(default)]
- pub agent_font_size: Option<f32>,
+ pub agent_font_size: Option<Option<f32>>,
/// The name of the Zed theme to use.
#[serde(default)]
pub theme: Option<ThemeSelection>,
@@ -599,13 +600,13 @@ impl ThemeSettings {
clamp_font_size(font_size)
}
- /// Returns the UI font size.
+ /// Returns the agent panel font size. Falls back to the UI font size if unset.
pub fn agent_font_size(&self, cx: &App) -> Pixels {
- let font_size = cx
- .try_global::<AgentFontSize>()
+ cx.try_global::<AgentFontSize>()
.map(|size| size.0)
- .unwrap_or(self.agent_font_size);
- clamp_font_size(font_size)
+ .or(self.agent_font_size)
+ .map(clamp_font_size)
+ .unwrap_or_else(|| self.ui_font_size(cx))
}
/// Returns the buffer font size, read from the settings.
@@ -624,6 +625,14 @@ impl ThemeSettings {
self.ui_font_size
}
+ /// Returns the agent font size, read from the settings.
+ ///
+ /// The real agent font size is stored in-memory, to support temporary font size changes.
+ /// Use [`Self::agent_font_size`] to get the real font size.
+ pub fn agent_font_size_settings(&self) -> Option<Pixels> {
+ self.agent_font_size
+ }
+
// TODO: Rename: `line_height` -> `buffer_line_height`
/// Returns the buffer's line height.
pub fn line_height(&self) -> f32 {
@@ -732,14 +741,12 @@ pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
}
/// Adjusts the buffer font size.
-pub fn adjust_buffer_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
+pub fn adjust_buffer_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
- let mut adjusted_size = cx
+ let adjusted_size = cx
.try_global::<BufferFontSize>()
.map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
-
- f(&mut adjusted_size);
- cx.set_global(BufferFontSize(clamp_font_size(adjusted_size)));
+ cx.set_global(BufferFontSize(clamp_font_size(f(adjusted_size))));
cx.refresh_windows();
}
@@ -765,14 +772,12 @@ pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
}
/// Sets the adjusted UI font size.
-pub fn adjust_ui_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
+pub fn adjust_ui_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
- let mut adjusted_size = cx
+ let adjusted_size = cx
.try_global::<UiFontSize>()
.map_or(ui_font_size, |adjusted_size| adjusted_size.0);
-
- f(&mut adjusted_size);
- cx.set_global(UiFontSize(clamp_font_size(adjusted_size)));
+ cx.set_global(UiFontSize(clamp_font_size(f(adjusted_size))));
cx.refresh_windows();
}
@@ -784,19 +789,17 @@ pub fn reset_ui_font_size(cx: &mut App) {
}
}
-/// Sets the adjusted UI font size.
-pub fn adjust_agent_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
+/// Sets the adjusted agent panel font size.
+pub fn adjust_agent_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
let agent_font_size = ThemeSettings::get_global(cx).agent_font_size(cx);
- let mut adjusted_size = cx
+ let adjusted_size = cx
.try_global::<AgentFontSize>()
.map_or(agent_font_size, |adjusted_size| adjusted_size.0);
-
- f(&mut adjusted_size);
- cx.set_global(AgentFontSize(clamp_font_size(adjusted_size)));
+ cx.set_global(AgentFontSize(clamp_font_size(f(adjusted_size))));
cx.refresh_windows();
}
-/// Resets the UI font size to the default value.
+/// Resets the agent panel font size to the default value.
pub fn reset_agent_font_size(cx: &mut App) {
if cx.has_global::<AgentFontSize>() {
cx.remove_global::<AgentFontSize>();
@@ -806,7 +809,7 @@ pub fn reset_agent_font_size(cx: &mut App) {
/// Ensures font size is within the valid range.
pub fn clamp_font_size(size: Pixels) -> Pixels {
- size.max(MIN_FONT_SIZE)
+ size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE)
}
fn clamp_font_weight(weight: f32) -> FontWeight {
@@ -860,7 +863,7 @@ impl settings::Settings for ThemeSettings {
},
buffer_font_size: defaults.buffer_font_size.unwrap().into(),
buffer_line_height: defaults.buffer_line_height.unwrap(),
- agent_font_size: defaults.agent_font_size.unwrap().into(),
+ agent_font_size: defaults.agent_font_size.flatten().map(Into::into),
theme_selection: defaults.theme.clone(),
active_theme: themes
.get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
@@ -959,20 +962,20 @@ impl settings::Settings for ThemeSettings {
}
}
- merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
- this.ui_font_size = this.ui_font_size.clamp(px(6.), px(100.));
-
+ merge(
+ &mut this.ui_font_size,
+ value.ui_font_size.map(Into::into).map(clamp_font_size),
+ );
merge(
&mut this.buffer_font_size,
- value.buffer_font_size.map(Into::into),
+ value.buffer_font_size.map(Into::into).map(clamp_font_size),
);
- this.buffer_font_size = this.buffer_font_size.clamp(px(6.), px(100.));
-
merge(
&mut this.agent_font_size,
- value.agent_font_size.map(Into::into),
+ value
+ .agent_font_size
+ .map(|value| value.map(Into::into).map(clamp_font_size)),
);
- this.agent_font_size = this.agent_font_size.clamp(px(6.), px(100.));
merge(&mut this.buffer_line_height, value.buffer_line_height);
@@ -107,6 +107,8 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
let mut prev_buffer_font_size_settings =
ThemeSettings::get_global(cx).buffer_font_size_settings();
let mut prev_ui_font_size_settings = ThemeSettings::get_global(cx).ui_font_size_settings();
+ let mut prev_agent_font_size_settings =
+ ThemeSettings::get_global(cx).agent_font_size_settings();
cx.observe_global::<SettingsStore>(move |cx| {
let buffer_font_size_settings = ThemeSettings::get_global(cx).buffer_font_size_settings();
if buffer_font_size_settings != prev_buffer_font_size_settings {
@@ -119,6 +121,12 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
prev_ui_font_size_settings = ui_font_size_settings;
reset_ui_font_size(cx);
}
+
+ let agent_font_size_settings = ThemeSettings::get_global(cx).agent_font_size_settings();
+ if agent_font_size_settings != prev_agent_font_size_settings {
+ prev_agent_font_size_settings = agent_font_size_settings;
+ reset_agent_font_size(cx);
+ }
})
.detach();
}
@@ -299,7 +299,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, |vim, action: &VimSave, window, cx| {
vim.update_editor(cx, |_, editor, cx| {
- let Some(project) = editor.project.clone() else {
+ let Some(project) = editor.project().cloned() else {
return;
};
let Some(worktree) = project.read(cx).visible_worktrees(cx).next() else {
@@ -436,7 +436,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
let Some(workspace) = vim.workspace(window) else {
return;
};
- let Some(project) = editor.project.clone() else {
+ let Some(project) = editor.project().cloned() else {
return;
};
let Some(worktree) = project.read(cx).visible_worktrees(cx).next() else {
@@ -20,7 +20,7 @@ impl ModeIndicator {
})
.detach();
- let handle = cx.entity().clone();
+ let handle = cx.entity();
let window_handle = window.window_handle();
cx.observe_new::<Vim>(move |_, window, cx| {
let Some(window) = window else {
@@ -29,7 +29,7 @@ impl ModeIndicator {
if window.window_handle() != window_handle {
return;
}
- let vim = cx.entity().clone();
+ let vim = cx.entity();
handle.update(cx, |_, cx| {
cx.subscribe(&vim, |mode_indicator, vim, event, cx| match event {
VimEvent::Focused => {
@@ -332,7 +332,7 @@ impl Vim {
Vim::take_forced_motion(cx);
let prior_selections = self.editor_selections(window, cx);
let cursor_word = self.editor_cursor_word(window, cx);
- let vim = cx.entity().clone();
+ let vim = cx.entity();
let searched = pane.update(cx, |pane, cx| {
self.search.direction = direction;
@@ -402,7 +402,7 @@ impl Vim {
const NAMESPACE: &'static str = "vim";
pub fn new(window: &mut Window, cx: &mut Context<Editor>) -> Entity<Self> {
- let editor = cx.entity().clone();
+ let editor = cx.entity();
let mut initial_mode = VimSettings::get_global(cx).default_mode;
if initial_mode == Mode::Normal && HelixModeSetting::get_global(cx).0 {
@@ -253,7 +253,7 @@ impl Dock {
cx: &mut Context<Workspace>,
) -> Entity<Self> {
let focus_handle = cx.focus_handle();
- let workspace = cx.entity().clone();
+ let workspace = cx.entity();
let dock = cx.new(|cx| {
let focus_subscription =
cx.on_focus(&focus_handle, window, |dock: &mut Dock, window, cx| {
@@ -270,6 +270,12 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
/// Returns the textual contents of the tab.
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString;
+ /// Returns the suggested filename for saving this item.
+ /// By default, returns the tab content text.
+ fn suggested_filename(&self, cx: &App) -> SharedString {
+ self.tab_content_text(0, cx)
+ }
+
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
None
}
@@ -497,6 +503,7 @@ pub trait ItemHandle: 'static + Send {
) -> gpui::Subscription;
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement;
fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString;
+ fn suggested_filename(&self, cx: &App) -> SharedString;
fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon>;
fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString>;
fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent>;
@@ -631,6 +638,10 @@ impl<T: Item> ItemHandle for Entity<T> {
self.read(cx).tab_content_text(detail, cx)
}
+ fn suggested_filename(&self, cx: &App) -> SharedString {
+ self.read(cx).suggested_filename(cx)
+ }
+
fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon> {
self.read(cx).tab_icon(window, cx)
}
@@ -346,7 +346,7 @@ impl Render for LanguageServerPrompt {
)
.child(Label::new(request.message.to_string()).size(LabelSize::Small))
.children(request.actions.iter().enumerate().map(|(ix, action)| {
- let this_handle = cx.entity().clone();
+ let this_handle = cx.entity();
Button::new(ix, action.title.clone())
.size(ButtonSize::Large)
.on_click(move |_, window, cx| {
@@ -2062,6 +2062,8 @@ impl Pane {
})?
.await?;
} else if can_save_as && is_singleton {
+ let suggested_name =
+ cx.update(|_window, cx| item.suggested_filename(cx).to_string())?;
let new_path = pane.update_in(cx, |pane, window, cx| {
pane.activate_item(item_ix, true, true, window, cx);
pane.workspace.update(cx, |workspace, cx| {
@@ -2073,7 +2075,7 @@ impl Pane {
} else {
DirectoryLister::Project(workspace.project().clone())
};
- workspace.prompt_for_new_path(lister, window, cx)
+ workspace.prompt_for_new_path(lister, Some(suggested_name), window, cx)
})
})??;
let Some(new_path) = new_path.await.ok().flatten().into_iter().flatten().next()
@@ -2196,7 +2198,7 @@ impl Pane {
fn update_status_bar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let workspace = self.workspace.clone();
- let pane = cx.entity().clone();
+ let pane = cx.entity();
window.defer(cx, move |window, cx| {
let Ok(status_bar) =
@@ -2277,7 +2279,7 @@ impl Pane {
cx: &mut Context<Self>,
) {
maybe!({
- let pane = cx.entity().clone();
+ let pane = cx.entity();
let destination_index = match operation {
PinOperation::Pin => self.pinned_tab_count.min(ix),
@@ -2471,7 +2473,7 @@ impl Pane {
.on_drag(
DraggedTab {
item: item.boxed_clone(),
- pane: cx.entity().clone(),
+ pane: cx.entity(),
detail,
is_active,
ix,
@@ -2830,7 +2832,7 @@ impl Pane {
let navigate_backward = IconButton::new("navigate_backward", IconName::ArrowLeft)
.icon_size(IconSize::Small)
.on_click({
- let entity = cx.entity().clone();
+ let entity = cx.entity();
move |_, window, cx| {
entity.update(cx, |pane, cx| pane.navigate_backward(window, cx))
}
@@ -2846,7 +2848,7 @@ impl Pane {
let navigate_forward = IconButton::new("navigate_forward", IconName::ArrowRight)
.icon_size(IconSize::Small)
.on_click({
- let entity = cx.entity().clone();
+ let entity = cx.entity();
move |_, window, cx| entity.update(cx, |pane, cx| pane.navigate_forward(window, cx))
})
.disabled(!self.can_navigate_forward())
@@ -3052,7 +3054,7 @@ impl Pane {
return;
}
}
- let mut to_pane = cx.entity().clone();
+ let mut to_pane = cx.entity();
let split_direction = self.drag_split_direction;
let item_id = dragged_tab.item.item_id();
if let Some(preview_item_id) = self.preview_item_id {
@@ -3161,7 +3163,7 @@ impl Pane {
return;
}
}
- let mut to_pane = cx.entity().clone();
+ let mut to_pane = cx.entity();
let split_direction = self.drag_split_direction;
let project_entry_id = *project_entry_id;
self.workspace
@@ -3237,7 +3239,7 @@ impl Pane {
return;
}
}
- let mut to_pane = cx.entity().clone();
+ let mut to_pane = cx.entity();
let mut split_direction = self.drag_split_direction;
let paths = paths.paths().to_vec();
let is_remote = self
@@ -2067,6 +2067,7 @@ impl Workspace {
pub fn prompt_for_new_path(
&mut self,
lister: DirectoryLister,
+ suggested_name: Option<String>,
window: &mut Window,
cx: &mut Context<Self>,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
@@ -2094,7 +2095,7 @@ impl Workspace {
})
.or_else(std::env::home_dir)
.unwrap_or_else(|| PathBuf::from(""));
- cx.prompt_for_new_path(&relative_to)
+ cx.prompt_for_new_path(&relative_to, suggested_name.as_deref())
})?;
let abs_path = match abs_path.await? {
Ok(path) => path,
@@ -6337,7 +6338,7 @@ impl Render for Workspace {
.border_b_1()
.border_color(colors.border)
.child({
- let this = cx.entity().clone();
+ let this = cx.entity();
canvas(
move |bounds, window, cx| {
this.update(cx, |this, cx| {
@@ -8,6 +8,7 @@ use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::{Client, ProxySettings, UserStore, parse_zed_link};
use collab_ui::channel_view::ChannelView;
use collections::HashMap;
+use crashes::InitCrashHandler;
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
use editor::Editor;
use extension::ExtensionHostProxy;
@@ -269,7 +270,15 @@ pub fn main() {
let session = app.background_executor().block(Session::new());
app.background_executor()
- .spawn(crashes::init(session_id.clone()))
+ .spawn(crashes::init(InitCrashHandler {
+ session_id: session_id.clone(),
+ zed_version: app_version.to_string(),
+ release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(),
+ commit_sha: app_commit_sha
+ .as_ref()
+ .map(|sha| sha.full())
+ .unwrap_or_else(|| "no sha".to_owned()),
+ }))
.detach();
reliability::init_panic_hook(
app_version,
@@ -12,6 +12,7 @@ use gpui::{App, AppContext as _, SemanticVersion};
use http_client::{self, HttpClient, HttpClientWithUrl, HttpRequestExt, Method};
use paths::{crashes_dir, crashes_retired_dir};
use project::Project;
+use proto::{CrashReport, GetCrashFilesResponse};
use release_channel::{AppCommitSha, RELEASE_CHANNEL, ReleaseChannel};
use reqwest::multipart::{Form, Part};
use settings::Settings;
@@ -51,10 +52,6 @@ pub fn init_panic_hook(
thread::yield_now();
}
}
- crashes::handle_panic();
-
- let thread = thread::current();
- let thread_name = thread.name().unwrap_or("<unnamed>");
let payload = info
.payload()
@@ -63,6 +60,11 @@ pub fn init_panic_hook(
.or_else(|| info.payload().downcast_ref::<String>().cloned())
.unwrap_or_else(|| "Box<Any>".to_string());
+ crashes::handle_panic(payload.clone(), info.location());
+
+ let thread = thread::current();
+ let thread_name = thread.name().unwrap_or("<unnamed>");
+
if *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
let location = info.location().unwrap();
let backtrace = Backtrace::new();
@@ -214,45 +216,53 @@ pub fn init(
let installation_id = installation_id.clone();
let system_id = system_id.clone();
- if let Some(ssh_client) = project.ssh_client() {
- ssh_client.update(cx, |client, cx| {
- if TelemetrySettings::get_global(cx).diagnostics {
- let request = client.proto_client().request(proto::GetCrashFiles {});
- cx.background_spawn(async move {
- let crash_files = request.await?;
- for crash in crash_files.crashes {
- let mut panic: Option<Panic> = crash
- .panic_contents
- .and_then(|s| serde_json::from_str(&s).log_err());
-
- if let Some(panic) = panic.as_mut() {
- panic.session_id = session_id.clone();
- panic.system_id = system_id.clone();
- panic.installation_id = installation_id.clone();
- }
-
- if let Some(minidump) = crash.minidump_contents {
- upload_minidump(
- http_client.clone(),
- minidump.clone(),
- panic.as_ref(),
- )
- .await
- .log_err();
- }
-
- if let Some(panic) = panic {
- upload_panic(&http_client, &panic_report_url, panic, &mut None)
- .await?;
- }
- }
+ let Some(ssh_client) = project.ssh_client() else {
+ return;
+ };
+ ssh_client.update(cx, |client, cx| {
+ if !TelemetrySettings::get_global(cx).diagnostics {
+ return;
+ }
+ let request = client.proto_client().request(proto::GetCrashFiles {});
+ cx.background_spawn(async move {
+ let GetCrashFilesResponse {
+ legacy_panics,
+ crashes,
+ } = request.await?;
+
+ for panic in legacy_panics {
+ if let Some(mut panic) = serde_json::from_str::<Panic>(&panic).log_err() {
+ panic.session_id = session_id.clone();
+ panic.system_id = system_id.clone();
+ panic.installation_id = installation_id.clone();
+ upload_panic(&http_client, &panic_report_url, panic, &mut None).await?;
+ }
+ }
- anyhow::Ok(())
- })
- .detach_and_log_err(cx);
+ let Some(endpoint) = MINIDUMP_ENDPOINT.as_ref() else {
+ return Ok(());
+ };
+ for CrashReport {
+ metadata,
+ minidump_contents,
+ } in crashes
+ {
+ if let Some(metadata) = serde_json::from_str(&metadata).log_err() {
+ upload_minidump(
+ http_client.clone(),
+ endpoint,
+ minidump_contents,
+ &metadata,
+ )
+ .await
+ .log_err();
+ }
}
+
+ anyhow::Ok(())
})
- }
+ .detach_and_log_err(cx);
+ })
})
.detach();
}
@@ -466,16 +476,18 @@ fn upload_panics_and_crashes(
installation_id: Option<String>,
cx: &App,
) {
- let telemetry_settings = *client::TelemetrySettings::get_global(cx);
+ if !client::TelemetrySettings::get_global(cx).diagnostics {
+ return;
+ }
cx.background_spawn(async move {
- let most_recent_panic =
- upload_previous_panics(http.clone(), &panic_report_url, telemetry_settings)
- .await
- .log_err()
- .flatten();
- upload_previous_crashes(http, most_recent_panic, installation_id, telemetry_settings)
+ upload_previous_minidumps(http.clone()).await.warn_on_err();
+ let most_recent_panic = upload_previous_panics(http.clone(), &panic_report_url)
.await
.log_err()
+ .flatten();
+ upload_previous_crashes(http, most_recent_panic, installation_id)
+ .await
+ .log_err();
})
.detach()
}
@@ -484,7 +496,6 @@ fn upload_panics_and_crashes(
async fn upload_previous_panics(
http: Arc<HttpClientWithUrl>,
panic_report_url: &Url,
- telemetry_settings: client::TelemetrySettings,
) -> anyhow::Result<Option<(i64, String)>> {
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
@@ -507,58 +518,41 @@ async fn upload_previous_panics(
continue;
}
- if telemetry_settings.diagnostics {
- let panic_file_content = smol::fs::read_to_string(&child_path)
- .await
- .context("error reading panic file")?;
-
- let panic: Option<Panic> = serde_json::from_str(&panic_file_content)
- .log_err()
- .or_else(|| {
- panic_file_content
- .lines()
- .next()
- .and_then(|line| serde_json::from_str(line).ok())
- })
- .unwrap_or_else(|| {
- log::error!("failed to deserialize panic file {:?}", panic_file_content);
- None
- });
-
- if let Some(panic) = panic {
- let minidump_path = paths::logs_dir()
- .join(&panic.session_id)
- .with_extension("dmp");
- if minidump_path.exists() {
- let minidump = smol::fs::read(&minidump_path)
- .await
- .context("Failed to read minidump")?;
- if upload_minidump(http.clone(), minidump, Some(&panic))
- .await
- .log_err()
- .is_some()
- {
- fs::remove_file(minidump_path).ok();
- }
- }
+ let panic_file_content = smol::fs::read_to_string(&child_path)
+ .await
+ .context("error reading panic file")?;
- if !upload_panic(&http, &panic_report_url, panic, &mut most_recent_panic).await? {
- continue;
- }
- }
- }
+ let panic: Option<Panic> = serde_json::from_str(&panic_file_content)
+ .log_err()
+ .or_else(|| {
+ panic_file_content
+ .lines()
+ .next()
+ .and_then(|line| serde_json::from_str(line).ok())
+ })
+ .unwrap_or_else(|| {
+ log::error!("failed to deserialize panic file {:?}", panic_file_content);
+ None
+ });
- // We've done what we can, delete the file
- fs::remove_file(child_path)
- .context("error removing panic")
- .log_err();
+ if let Some(panic) = panic
+ && upload_panic(&http, &panic_report_url, panic, &mut most_recent_panic).await?
+ {
+ // We've done what we can, delete the file
+ fs::remove_file(child_path)
+ .context("error removing panic")
+ .log_err();
+ }
}
- if MINIDUMP_ENDPOINT.is_none() {
- return Ok(most_recent_panic);
- }
+ Ok(most_recent_panic)
+}
+
+pub async fn upload_previous_minidumps(http: Arc<HttpClientWithUrl>) -> anyhow::Result<()> {
+ let Some(minidump_endpoint) = MINIDUMP_ENDPOINT.as_ref() else {
+ return Err(anyhow::anyhow!("Minidump endpoint not set"));
+ };
- // loop back over the directory again to upload any minidumps that are missing panics
let mut children = smol::fs::read_dir(paths::logs_dir()).await?;
while let Some(child) = children.next().await {
let child = child?;
@@ -566,33 +560,35 @@ async fn upload_previous_panics(
if child_path.extension() != Some(OsStr::new("dmp")) {
continue;
}
- if upload_minidump(
- http.clone(),
- smol::fs::read(&child_path)
- .await
- .context("Failed to read minidump")?,
- None,
- )
- .await
- .log_err()
- .is_some()
- {
- fs::remove_file(child_path).ok();
+ let mut json_path = child_path.clone();
+ json_path.set_extension("json");
+ if let Ok(metadata) = serde_json::from_slice(&smol::fs::read(&json_path).await?) {
+ if upload_minidump(
+ http.clone(),
+ &minidump_endpoint,
+ smol::fs::read(&child_path)
+ .await
+ .context("Failed to read minidump")?,
+ &metadata,
+ )
+ .await
+ .log_err()
+ .is_some()
+ {
+ fs::remove_file(child_path).ok();
+ fs::remove_file(json_path).ok();
+ }
}
}
-
- Ok(most_recent_panic)
+ Ok(())
}
async fn upload_minidump(
http: Arc<HttpClientWithUrl>,
+ endpoint: &str,
minidump: Vec<u8>,
- panic: Option<&Panic>,
+ metadata: &crashes::CrashInfo,
) -> Result<()> {
- let minidump_endpoint = MINIDUMP_ENDPOINT
- .to_owned()
- .ok_or_else(|| anyhow::anyhow!("Minidump endpoint not set"))?;
-
let mut form = Form::new()
.part(
"upload_file_minidump",
@@ -600,38 +596,22 @@ async fn upload_minidump(
.file_name("minidump.dmp")
.mime_str("application/octet-stream")?,
)
+ .text(
+ "sentry[tags][channel]",
+ metadata.init.release_channel.clone(),
+ )
+ .text("sentry[tags][version]", metadata.init.zed_version.clone())
+ .text("sentry[release]", metadata.init.commit_sha.clone())
.text("platform", "rust");
- if let Some(panic) = panic {
- form = form
- .text("sentry[tags][channel]", panic.release_channel.clone())
- .text("sentry[tags][version]", panic.app_version.clone())
- .text("sentry[context][os][name]", panic.os_name.clone())
- .text(
- "sentry[context][device][architecture]",
- panic.architecture.clone(),
- )
- .text("sentry[logentry][formatted]", panic.payload.clone());
-
- if let Some(sha) = panic.app_commit_sha.clone() {
- form = form.text("sentry[release]", sha)
- } else {
- form = form.text(
- "sentry[release]",
- format!("{}-{}", panic.release_channel, panic.app_version),
- )
- }
- if let Some(v) = panic.os_version.clone() {
- form = form.text("sentry[context][os][release]", v);
- }
- if let Some(location) = panic.location_data.as_ref() {
- form = form.text("span", format!("{}:{}", location.file, location.line))
- }
+ if let Some(panic_info) = metadata.panic.as_ref() {
+ form = form.text("sentry[logentry][formatted]", panic_info.message.clone());
+ form = form.text("span", panic_info.span.clone());
// TODO: add gpu-context, feature-flag-context, and more of device-context like gpu
// name, screen resolution, available ram, device model, etc
}
let mut response_text = String::new();
- let mut response = http.send_multipart_form(&minidump_endpoint, form).await?;
+ let mut response = http.send_multipart_form(endpoint, form).await?;
response
.body_mut()
.read_to_string(&mut response_text)
@@ -681,11 +661,7 @@ async fn upload_previous_crashes(
http: Arc<HttpClientWithUrl>,
most_recent_panic: Option<(i64, String)>,
installation_id: Option<String>,
- telemetry_settings: client::TelemetrySettings,
) -> Result<()> {
- if !telemetry_settings.diagnostics {
- return Ok(());
- }
let last_uploaded = KEY_VALUE_STORE
.read_kvp(LAST_CRASH_UPLOADED)?
.unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
@@ -31,6 +31,7 @@ use gpui::{
px, retain_all,
};
use image_viewer::ImageInfo;
+use language::Capability;
use language_tools::lsp_tool::{self, LspTool};
use migrate::{MigrationBanner, MigrationEvent, MigrationNotification, MigrationType};
use migrator::{migrate_keymap, migrate_settings};
@@ -319,7 +320,7 @@ pub fn initialize_workspace(
return;
};
- let workspace_handle = cx.entity().clone();
+ let workspace_handle = cx.entity();
let center_pane = workspace.active_pane().clone();
initialize_pane(workspace, ¢er_pane, window, cx);
@@ -715,9 +716,7 @@ fn register_actions(
.insert(theme::clamp_font_size(ui_font_size).0);
});
} else {
- theme::adjust_ui_font_size(cx, |size| {
- *size += px(1.0);
- });
+ theme::adjust_ui_font_size(cx, |size| size + px(1.0));
}
}
})
@@ -732,9 +731,7 @@ fn register_actions(
.insert(theme::clamp_font_size(ui_font_size).0);
});
} else {
- theme::adjust_ui_font_size(cx, |size| {
- *size -= px(1.0);
- });
+ theme::adjust_ui_font_size(cx, |size| size - px(1.0));
}
}
})
@@ -762,9 +759,7 @@ fn register_actions(
.insert(theme::clamp_font_size(buffer_font_size).0);
});
} else {
- theme::adjust_buffer_font_size(cx, |size| {
- *size += px(1.0);
- });
+ theme::adjust_buffer_font_size(cx, |size| size + px(1.0));
}
}
})
@@ -780,9 +775,7 @@ fn register_actions(
.insert(theme::clamp_font_size(buffer_font_size).0);
});
} else {
- theme::adjust_buffer_font_size(cx, |size| {
- *size -= px(1.0);
- });
+ theme::adjust_buffer_font_size(cx, |size| size - px(1.0));
}
}
})
@@ -1764,7 +1757,11 @@ fn open_bundled_file(
workspace.with_local_workspace(window, cx, |workspace, window, cx| {
let project = workspace.project();
let buffer = project.update(cx, move |project, cx| {
- project.create_local_buffer(text.as_ref(), language, cx)
+ let buffer = project.create_local_buffer(text.as_ref(), language, cx);
+ buffer.update(cx, |buffer, cx| {
+ buffer.set_capability(Capability::ReadOnly, cx);
+ });
+ buffer
});
let buffer =
cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(title.into()));
@@ -4543,6 +4540,43 @@ mod tests {
assert!(has_default_theme);
}
+ #[gpui::test]
+ async fn test_bundled_files_editor(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+ cx.update(init);
+
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let _window = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
+
+ cx.update(|cx| {
+ cx.dispatch_action(&OpenDefaultSettings);
+ });
+ cx.run_until_parked();
+
+ assert_eq!(cx.read(|cx| cx.windows().len()), 1);
+
+ let workspace = cx.windows()[0].downcast::<Workspace>().unwrap();
+ let active_editor = workspace
+ .update(cx, |workspace, _, cx| {
+ workspace.active_item_as::<Editor>(cx)
+ })
+ .unwrap();
+ assert!(
+ active_editor.is_some(),
+ "Settings action should have opened an editor with the default file contents"
+ );
+
+ let active_editor = active_editor.unwrap();
+ assert!(
+ active_editor.read_with(cx, |editor, cx| editor.read_only(cx)),
+ "Default settings should be readonly"
+ );
+ assert!(
+ active_editor.read_with(cx, |editor, cx| editor.buffer().read(cx).read_only()),
+ "The underlying buffer should also be readonly for the shipped default settings"
+ );
+ }
+
#[gpui::test]
async fn test_bundled_languages(cx: &mut TestAppContext) {
env_logger::builder().is_test(true).try_init().ok();
@@ -229,8 +229,7 @@ fn assign_edit_prediction_provider(
if let Some(file) = buffer.read(cx).file() {
let id = file.worktree_id(cx);
if let Some(inner_worktree) = editor
- .project
- .as_ref()
+ .project()
.and_then(|project| project.read(cx).worktree_for_id(id, cx))
{
worktree = Some(inner_worktree);
@@ -1284,6 +1284,7 @@ Each option controls displaying of a particular toolbar element. If all elements
```json
"status_bar": {
"active_language_button": true,
+ "cursor_position_button": true
},
```
@@ -1,5 +1,7 @@
# Emmet
+Emmet support is available through the [Emmet extension](https://github.com/zed-extensions/emmet).
+
[Emmet](https://emmet.io/) is a web-developerβs toolkit that can greatly improve your HTML & CSS workflow.
- Language Server: [olrtg/emmet-language-server](https://github.com/olrtg/emmet-language-server)
@@ -48,7 +48,6 @@ There are several third-party Zed packages for various Linux distributions and p
- Manjaro: [`zed`](https://packages.manjaro.org/?query=zed)
- ALT Linux (Sisyphus): [`zed`](https://packages.altlinux.org/en/sisyphus/srpms/zed/)
- AOSC OS: [`zed`](https://packages.aosc.io/packages/zed)
-- openSUSE Tumbleweed: [`zed`](https://en.opensuse.org/Zed)
See [Repology](https://repology.org/project/zed-editor/versions) for a list of Zed packages in various repositories.
@@ -316,6 +316,10 @@ TBD: Centered layout related settings
// Clicking the button brings up the language selector.
// Defaults to true.
"active_language_button": true,
+ // Show/hide a button that displays the cursor's position.
+ // Clicking the button brings up an input for jumping to a line and column.
+ // Defaults to true.
+ "cursor_position_button": true,
},
```
@@ -1,3 +0,0 @@
-*.wasm
-grammars
-target
@@ -1,16 +0,0 @@
-[package]
-name = "zed_emmet"
-version = "0.0.6"
-edition.workspace = true
-publish.workspace = true
-license = "Apache-2.0"
-
-[lints]
-workspace = true
-
-[lib]
-path = "src/emmet.rs"
-crate-type = ["cdylib"]
-
-[dependencies]
-zed_extension_api = "0.1.0"
@@ -1 +0,0 @@
-../../LICENSE-APACHE
@@ -1,24 +0,0 @@
-id = "emmet"
-name = "Emmet"
-description = "Emmet support"
-version = "0.0.6"
-schema_version = 1
-authors = ["Piotr Osiewicz <piotr@zed.dev>"]
-repository = "https://github.com/zed-industries/zed"
-
-[language_servers.emmet-language-server]
-name = "Emmet Language Server"
-language = "HTML"
-languages = ["HTML", "PHP", "ERB", "HTML/ERB", "JavaScript", "TSX", "CSS", "HEEX", "Elixir", "Vue.js"]
-
-[language_servers.emmet-language-server.language_ids]
-"HTML" = "html"
-"PHP" = "php"
-"ERB" = "eruby"
-"HTML/ERB" = "eruby"
-"JavaScript" = "javascriptreact"
-"TSX" = "typescriptreact"
-"CSS" = "css"
-"HEEX" = "heex"
-"Elixir" = "heex"
-"Vue.js" = "vue"
@@ -1,106 +0,0 @@
-use std::{env, fs};
-use zed_extension_api::{self as zed, Result};
-
-struct EmmetExtension {
- did_find_server: bool,
-}
-
-const SERVER_PATH: &str = "node_modules/@olrtg/emmet-language-server/dist/index.js";
-const PACKAGE_NAME: &str = "@olrtg/emmet-language-server";
-
-impl EmmetExtension {
- fn server_exists(&self) -> bool {
- fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file())
- }
-
- fn server_script_path(&mut self, language_server_id: &zed::LanguageServerId) -> Result<String> {
- let server_exists = self.server_exists();
- if self.did_find_server && server_exists {
- return Ok(SERVER_PATH.to_string());
- }
-
- zed::set_language_server_installation_status(
- language_server_id,
- &zed::LanguageServerInstallationStatus::CheckingForUpdate,
- );
- let version = zed::npm_package_latest_version(PACKAGE_NAME)?;
-
- if !server_exists
- || zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version)
- {
- zed::set_language_server_installation_status(
- language_server_id,
- &zed::LanguageServerInstallationStatus::Downloading,
- );
- let result = zed::npm_install_package(PACKAGE_NAME, &version);
- match result {
- Ok(()) => {
- if !self.server_exists() {
- Err(format!(
- "installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'",
- ))?;
- }
- }
- Err(error) => {
- if !self.server_exists() {
- Err(error)?;
- }
- }
- }
- }
-
- self.did_find_server = true;
- Ok(SERVER_PATH.to_string())
- }
-}
-
-impl zed::Extension for EmmetExtension {
- fn new() -> Self {
- Self {
- did_find_server: false,
- }
- }
-
- fn language_server_command(
- &mut self,
- language_server_id: &zed::LanguageServerId,
- _worktree: &zed::Worktree,
- ) -> Result<zed::Command> {
- let server_path = self.server_script_path(language_server_id)?;
- Ok(zed::Command {
- command: zed::node_binary_path()?,
- args: vec![
- zed_ext::sanitize_windows_path(env::current_dir().unwrap())
- .join(&server_path)
- .to_string_lossy()
- .to_string(),
- "--stdio".to_string(),
- ],
- env: Default::default(),
- })
- }
-}
-
-zed::register_extension!(EmmetExtension);
-
-/// Extensions to the Zed extension API that have not yet stabilized.
-mod zed_ext {
- /// Sanitizes the given path to remove the leading `/` on Windows.
- ///
- /// On macOS and Linux this is a no-op.
- ///
- /// This is a workaround for https://github.com/bytecodealliance/wasmtime/issues/10415.
- pub fn sanitize_windows_path(path: std::path::PathBuf) -> std::path::PathBuf {
- use zed_extension_api::{Os, current_platform};
-
- let (os, _arch) = current_platform();
- match os {
- Os::Mac | Os::Linux => path,
- Os::Windows => path
- .to_string_lossy()
- .to_string()
- .trim_start_matches('/')
- .into(),
- }
- }
-}
@@ -16,9 +16,6 @@ extend-exclude = [
"crates/google_ai/src/supported_countries.rs",
"crates/open_ai/src/supported_countries.rs",
- # Some crate names are flagged as typos.
- "crates/indexed_docs/src/providers/rustdoc/popular_crates.txt",
-
# Some mock data is flagged as typos.
"crates/assistant_tools/src/web_search_tool.rs",