Compare commits

..

4 Commits

Author SHA1 Message Date
Mikayla
04558cb563 Add guard for mode refresh 2024-06-25 11:05:24 -07:00
Mikayla
da456c33a0 ? 2024-06-25 11:04:53 -07:00
Mikayla
04e243f01e clippy 2024-06-25 09:24:47 -07:00
Mikayla
e7827d5069 Fix a bug introduced by #13479 2024-06-25 08:59:45 -07:00
243 changed files with 3675 additions and 10652 deletions

View File

@@ -254,7 +254,7 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
body_path: target/release-notes.md
body_file: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -402,7 +402,7 @@ jobs:
- name: Upload app bundle to release
uses: softprops/action-gh-release@v1
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
if: ${{ env.RELEASE_CHANNEL == 'preview' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}

View File

@@ -7,12 +7,10 @@
# Reference: https://git-scm.com/docs/gitmailmap
# Keep these entries sorted alphabetically.
# In Zed: `editor: sort lines case insensitive`
# In Zed: `editor: sort lines case sensitive`
Alex Viscreanu <alexviscreanu@gmail.com>
Alex Viscreanu <alexviscreanu@gmail.com> <alexandru.viscreanu@kiwi.com>
amtoaer <amtoaer@gmail.com>
amtoaer <amtoaer@gmail.com> <amtoaer@outlook.com>
Antonio Scandurra <me@as-cii.com>
Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
Bennet Bo Fenner <bennet@zed.dev>
@@ -22,8 +20,6 @@ Christian Bergschneider <christian.bergschneider@gmx.de>
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
Conrad Irwin <conrad@zed.dev>
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
Danilo Leal <danilo@zed.dev>
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
Evren Sen <146845123+evrsen@users.noreply.github.com>
Fernando Tagawa <tagawafernando@gmail.com>
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
@@ -58,16 +54,12 @@ Nate Butler <iamnbutler@gmail.com> <nate@zed.dev>
Nathan Sobo <nathan@zed.dev>
Nathan Sobo <nathan@zed.dev> <nathan@warp.dev>
Nathan Sobo <nathan@zed.dev> <nathansobo@gmail.com>
Nigel Jose <nigelmjose@gmail.com>
Nigel Jose <nigelmjose@gmail.com> <nigel.jose@student.manchester.ac.uk>
Peter Tripp <peter@zed.dev>
Peter Tripp <peter@zed.dev> <petertripp@gmail.com>
Petros Amoiridis <petros@hey.com>
Petros Amoiridis <petros@hey.com> <petros@zed.dev>
Piotr Osiewicz <piotr@zed.dev>
Piotr Osiewicz <piotr@zed.dev> <24362066+osiewicz@users.noreply.github.com>
Pocæus <github@pocaeus.com>
Pocæus <github@pocaeus.com> <pseudomata@proton.me>
Rashid Almheiri <r.muhairi@pm.me>
Rashid Almheiri <r.muhairi@pm.me> <69181766+huwaireb@users.noreply.github.com>
Richard Feldman <oss@rtfeldman.com>

View File

@@ -19,13 +19,6 @@
"JavaScript": {
"tab_size": 2,
"formatter": "prettier"
},
"Rust": {
"tasks": {
"variables": {
"RUST_DEFAULT_PACKAGE_RUN": "zed"
}
}
}
},
"formatter": "auto",

158
Cargo.lock generated
View File

@@ -374,7 +374,6 @@ dependencies = [
"anyhow",
"assistant_slash_command",
"async-watch",
"breadcrumbs",
"cargo_toml",
"chrono",
"client",
@@ -383,15 +382,14 @@ dependencies = [
"ctor",
"editor",
"env_logger",
"feature_flags",
"file_icons",
"fs",
"futures 0.3.28",
"fuzzy",
"gpui",
"heed",
"html_to_markdown 0.1.0",
"html_to_markdown",
"http 0.1.0",
"indexed_docs",
"indoc",
"language",
"log",
@@ -407,6 +405,7 @@ dependencies = [
"rand 0.8.5",
"regex",
"rope",
"rustdoc",
"schemars",
"search",
"semantic_index",
@@ -418,7 +417,6 @@ dependencies = [
"strsim 0.11.1",
"strum",
"telemetry_events",
"terminal",
"terminal_view",
"theme",
"tiktoken-rs",
@@ -2553,13 +2551,18 @@ name = "collab_ui"
version = "0.1.0"
dependencies = [
"anyhow",
"auto_update",
"call",
"channel",
"client",
"collections",
"command_palette",
"db",
"dev_server_projects",
"editor",
"emojis",
"extensions_ui",
"feedback",
"futures 0.3.28",
"fuzzy",
"gpui",
@@ -2572,6 +2575,7 @@ dependencies = [
"picker",
"pretty_assertions",
"project",
"recent_projects",
"release_channel",
"rich_text",
"rpc",
@@ -2583,14 +2587,15 @@ dependencies = [
"smallvec",
"story",
"theme",
"theme_selector",
"time",
"time_format",
"title_bar",
"tree-sitter-markdown",
"ui",
"util",
"vcs_menu",
"workspace",
"zed_actions",
]
[[package]]
@@ -3917,7 +3922,6 @@ dependencies = [
"futures 0.3.28",
"gpui",
"http 0.1.0",
"indexed_docs",
"isahc",
"language",
"log",
@@ -4889,7 +4893,6 @@ dependencies = [
"log",
"media",
"metal",
"mio 1.0.0",
"num_cpus",
"objc",
"oo7",
@@ -4916,7 +4919,6 @@ dependencies = [
"taffy",
"thiserror",
"time",
"unicode-segmentation",
"usvg",
"util",
"uuid",
@@ -5144,9 +5146,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.3.9"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hex"
@@ -5240,18 +5242,6 @@ dependencies = [
"regex",
]
[[package]]
name = "html_to_markdown"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e608e8dd0939bfb6b516d96a5919751b835297a02230aecb88d2fc84ebebaa8a"
dependencies = [
"anyhow",
"html5ever",
"markup5ever_rcdom",
"regex",
]
[[package]]
name = "http"
version = "0.1.0"
@@ -5495,31 +5485,6 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
[[package]]
name = "indexed_docs"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"collections",
"derive_more",
"fs",
"futures 0.3.28",
"fuzzy",
"gpui",
"heed",
"html_to_markdown 0.1.0",
"http 0.1.0",
"indexmap 1.9.3",
"indoc",
"parking_lot",
"paths",
"pretty_assertions",
"serde",
"strum",
"util",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -5660,7 +5625,7 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.9",
"hermit-abi 0.3.3",
"libc",
"windows-sys 0.48.0",
]
@@ -5691,7 +5656,7 @@ dependencies = [
"fnv",
"lazy_static",
"libc",
"mio 0.8.11",
"mio",
"rand 0.8.5",
"serde",
"tempfile",
@@ -6640,19 +6605,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4929e1f84c5e54c3ec6141cd5d8b5a5c055f031f80cf78f2072920173cb4d880"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
[[package]]
name = "miow"
version = "0.6.0"
@@ -6891,7 +6843,7 @@ dependencies = [
"kqueue",
"libc",
"log",
"mio 0.8.11",
"mio",
"walkdir",
"windows-sys 0.48.0",
]
@@ -7071,7 +7023,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi 0.3.9",
"hermit-abi 0.3.3",
"libc",
]
@@ -9059,6 +9011,31 @@ dependencies = [
"semver",
]
[[package]]
name = "rustdoc"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"collections",
"derive_more",
"fs",
"futures 0.3.28",
"fuzzy",
"gpui",
"heed",
"html_to_markdown",
"http 0.1.0",
"indexmap 1.9.3",
"indoc",
"parking_lot",
"paths",
"pretty_assertions",
"serde",
"strum",
"util",
]
[[package]]
name = "rustix"
version = "0.37.23"
@@ -11068,41 +11045,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "title_bar"
version = "0.1.0"
dependencies = [
"auto_update",
"call",
"client",
"collections",
"command_palette",
"dev_server_projects",
"editor",
"extensions_ui",
"feedback",
"gpui",
"http 0.1.0",
"notifications",
"pretty_assertions",
"project",
"recent_projects",
"rpc",
"serde",
"settings",
"smallvec",
"story",
"theme",
"theme_selector",
"tree-sitter-markdown",
"ui",
"util",
"vcs_menu",
"windows 0.57.0",
"workspace",
"zed_actions",
]
[[package]]
name = "tokio"
version = "1.37.0"
@@ -11112,7 +11054,7 @@ dependencies = [
"backtrace",
"bytes 1.5.0",
"libc",
"mio 0.8.11",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
@@ -13607,7 +13549,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.144.0"
version = "0.142.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -13807,7 +13749,6 @@ dependencies = [
name = "zed_gleam"
version = "0.1.3"
dependencies = [
"html_to_markdown 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"zed_extension_api 0.0.7",
]
@@ -13855,7 +13796,7 @@ dependencies = [
[[package]]
name = "zed_prisma"
version = "0.0.3"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.4",
]
@@ -13896,13 +13837,6 @@ dependencies = [
"zed_extension_api 0.0.6",
]
[[package]]
name = "zed_test_extension"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.0.6",
]
[[package]]
name = "zed_toml"
version = "0.1.1"
@@ -13926,7 +13860,7 @@ dependencies = [
[[package]]
name = "zed_zig"
version = "0.1.3"
version = "0.1.2"
dependencies = [
"zed_extension_api 0.0.7",
]

View File

@@ -21,7 +21,6 @@ members = [
"crates/command_palette_hooks",
"crates/copilot",
"crates/db",
"crates/dev_server_projects",
"crates/diagnostics",
"crates/editor",
"crates/extension",
@@ -45,7 +44,6 @@ members = [
"crates/html_to_markdown",
"crates/http",
"crates/image_viewer",
"crates/indexed_docs",
"crates/inline_completion_button",
"crates/install_cli",
"crates/journal",
@@ -79,10 +77,14 @@ members = [
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/dev_server_projects",
"crates/repl",
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/rustdoc",
"crates/task",
"crates/tasks_ui",
"crates/search",
"crates/semantic_index",
"crates/semantic_version",
@@ -93,20 +95,17 @@ members = [
"crates/story",
"crates/storybook",
"crates/sum_tree",
"crates/tab_switcher",
"crates/supermaven",
"crates/supermaven_api",
"crates/tab_switcher",
"crates/task",
"crates/tasks_ui",
"crates/telemetry_events",
"crates/terminal",
"crates/terminal_view",
"crates/text",
"crates/theme",
"crates/theme_importer",
"crates/theme_selector",
"crates/telemetry_events",
"crates/time_format",
"crates/title_bar",
"crates/ui",
"crates/ui_text_field",
"crates/util",
@@ -140,7 +139,6 @@ members = [
"extensions/snippets",
"extensions/svelte",
"extensions/terraform",
"extensions/test-extension",
"extensions/toml",
"extensions/uiua",
"extensions/vue",
@@ -159,8 +157,10 @@ assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_tooling = { path = "crates/assistant_tooling" }
async-watch = "0.3.1"
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
base64 = "0.13"
breadcrumbs = { path = "crates/breadcrumbs" }
call = { path = "crates/call" }
channel = { path = "crates/channel" }
@@ -173,8 +173,8 @@ collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
copilot = { path = "crates/copilot" }
dashmap = "5.5.3"
db = { path = "crates/db" }
dev_server_projects = { path = "crates/dev_server_projects" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
@@ -195,10 +195,9 @@ gpui_macros = { path = "crates/gpui_macros" }
headless = { path = "crates/headless" }
html_to_markdown = { path = "crates/html_to_markdown" }
http = { path = "crates/http" }
image_viewer = { path = "crates/image_viewer" }
indexed_docs = { path = "crates/indexed_docs" }
inline_completion_button = { path = "crates/inline_completion_button" }
install_cli = { path = "crates/install_cli" }
image_viewer = { path = "crates/image_viewer" }
inline_completion_button = { path = "crates/inline_completion_button" }
journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_selector = { path = "crates/language_selector" }
@@ -224,16 +223,21 @@ plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
prettier = { path = "crates/prettier" }
project = { path = "crates/project" }
proto = { path = "crates/proto" }
worktree = { path = "crates/worktree" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
proto = { path = "crates/proto" }
quick_action_bar = { path = "crates/quick_action_bar" }
recent_projects = { path = "crates/recent_projects" }
release_channel = { path = "crates/release_channel" }
dev_server_projects = { path = "crates/dev_server_projects" }
repl = { path = "crates/repl" }
rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
rustdoc = { path = "crates/rustdoc" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" }
@@ -241,23 +245,20 @@ settings = { path = "crates/settings" }
snippet = { path = "crates/snippet" }
sqlez = { path = "crates/sqlez" }
sqlez_macros = { path = "crates/sqlez_macros" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
story = { path = "crates/story" }
storybook = { path = "crates/storybook" }
sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
telemetry_events = { path = "crates/telemetry_events" }
terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
theme = { path = "crates/theme" }
theme_importer = { path = "crates/theme_importer" }
theme_selector = { path = "crates/theme_selector" }
telemetry_events = { path = "crates/telemetry_events" }
time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
ui = { path = "crates/ui" }
ui_text_field = { path = "crates/ui_text_field" }
util = { path = "crates/util" }
@@ -265,13 +266,12 @@ vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
welcome = { path = "crates/welcome" }
workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
alacritty_terminal = "0.23"
any_vec = "0.13"
anyhow = "1.0.57"
any_vec = "0.13"
ashpd = "0.8.0"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = { version = "0.1"}
@@ -279,23 +279,20 @@ async-fs = "1.6"
async-recursion = "1.0.0"
async-tar = "0.4.2"
async-trait = "0.1"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.13"
bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
cap-std = "3.0"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
clickhouse = { version = "0.11.6" }
cocoa = "0.25"
ctor = "0.2.6"
core-foundation = { version = "0.9.3" }
core-foundation-sys = "0.8.6"
ctor = "0.2.6"
dashmap = "5.5.3"
derive_more = "0.99.17"
dirs = "4.0"
emojis = "0.6.1"

View File

@@ -1,92 +0,0 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -1,92 +0,0 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>

Before

Width:  |  Height:  |  Size: 276 B

View File

@@ -94,7 +94,6 @@
"lua": "lua",
"m4a": "audio",
"m4v": "video",
"markdown": "document",
"md": "document",
"mdb": "storage",
"mdf": "storage",

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-type"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" x2="15" y1="20" y2="20"/><line x1="12" x2="12" y1="4" y2="20"/></svg>

Before

Width:  |  Height:  |  Size: 329 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-a-large-small"><path d="M21 14h-5"/><path d="M16 16v-3.5a2.5 2.5 0 0 1 5 0V16"/><path d="M4.5 13h6"/><path d="m3 16 4.5-9 4.5 9"/></svg>

Before

Width:  |  Height:  |  Size: 339 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bold"><path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"/></svg>

Before

Width:  |  Height:  |  Size: 296 B

View File

@@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5 4.5L4.5 11.5" stroke="black" stroke-linecap="square" stroke-linejoin="round"/>
<path d="M4.5 4.5L11.5 11.5" stroke="black" stroke-linecap="square" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 291 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5 4.5H4.5V11.5H11.5V4.5Z" stroke="#FBF1C7"/>
</svg>

Before

Width:  |  Height:  |  Size: 161 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 8H12" stroke="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 138 B

View File

@@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 6.5H3.5V12.5H9.5V6.5Z" stroke="#FBF1C7"/>
<path d="M10 8.5L12.5 8.5L12.5 3.5L7.5 3.5L7.5 6" stroke="#FBF1C7"/>
</svg>

Before

Width:  |  Height:  |  Size: 228 B

View File

@@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 13.6667H12" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 2.33333H12" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 11L8 5L11 11" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 9H10" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 539 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>

Before

Width:  |  Height:  |  Size: 301 B

View File

@@ -70,11 +70,6 @@
{
"context": "ProjectPanel",
"bindings": {
"f2": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
"ctrl-[": "project_panel::CollapseSelectedEntry",
"ctrl-b": "project_panel::CollapseSelectedEntry",
"alt-b": "project_panel::CollapseSelectedEntry",
@@ -82,13 +77,5 @@
"ctrl-f": "project_panel::ExpandSelectedEntry",
"ctrl-shift-c": "project_panel::CopyPath"
}
},
{
"context": "ProjectPanel && not_editing",
"bindings": {
"a": "project_panel::NewFile",
"shift-a": "project_panel::NewDirectory",
"shift-d": "project_panel::Duplicate"
}
}
]

View File

@@ -55,13 +55,11 @@
"up": "editor::MoveUp",
"ctrl-up": "editor::LineUp",
"ctrl-down": "editor::LineDown",
"pageup": "editor::MovePageUp",
"alt-pageup": "editor::PageUp",
"pageup": "editor::PageUp",
"shift-pageup": "editor::SelectPageUp",
"home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown",
"pagedown": "editor::MovePageDown",
"alt-pagedown": "editor::PageDown",
"pagedown": "editor::PageDown",
"shift-pagedown": "editor::SelectPageDown",
"end": "editor::MoveToEndOfLine",
"left": "editor::MoveLeft",
@@ -155,7 +153,6 @@
// }
// ],
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
}
},
@@ -549,7 +546,6 @@
"ctrl-enter": "assistant::Assist",
"ctrl-s": "workspace::Save",
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"shift-enter": "assistant::Split",
"ctrl-r": "assistant::CycleMessageRole",
"enter": "assistant::ConfirmCommand",
@@ -591,9 +587,8 @@
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": "project_panel::Trash",
"delete": "project_panel::Trash",
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFinder",
@@ -655,20 +650,13 @@
"ctrl-insert": "terminal::Copy",
"shift-ctrl-v": "terminal::Paste",
"shift-insert": "terminal::Paste",
"ctrl-enter": "assistant::InlineAssist",
"up": ["terminal::SendKeystroke", "up"],
"pageup": ["terminal::SendKeystroke", "pageup"],
"down": ["terminal::SendKeystroke", "down"],
"pagedown": ["terminal::SendKeystroke", "pagedown"],
"escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown",
"shift-up": "terminal::ScrollLineUp",
"shift-down": "terminal::ScrollLineDown",
"shift-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom"
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
}
}
]

View File

@@ -61,17 +61,13 @@
"cmd-shift-z": "editor::Redo",
"up": "editor::MoveUp",
"ctrl-up": "editor::MoveToStartOfParagraph",
"pageup": "editor::MovePageUp",
"shift-pageup": "editor::SelectPageUp",
"cmd-pageup": "editor::PageUp",
"ctrl-pageup": "editor::LineUp",
"pageup": "editor::PageUp",
"shift-pageup": "editor::MovePageUp",
"home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown",
"ctrl-down": "editor::MoveToEndOfParagraph",
"pagedown": "editor::MovePageDown",
"shift-pagedown": "editor::SelectPageDown",
"cmd-pagedown": "editor::PageDown",
"ctrl-pagedown": "editor::LineDown",
"pagedown": "editor::PageDown",
"shift-pagedown": "editor::MovePageDown",
"end": "editor::MoveToEndOfLine",
"left": "editor::MoveLeft",
"right": "editor::MoveRight",
@@ -193,7 +189,6 @@
}
],
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol"
}
},
@@ -239,7 +234,6 @@
"cmd-enter": "assistant::Assist",
"cmd-s": "workspace::Save",
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"shift-enter": "assistant::Split",
"ctrl-r": "assistant::CycleMessageRole",
"enter": "assistant::ConfirmCommand",
@@ -611,7 +605,6 @@
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"cmd-n": "project_panel::NewFile",
"cmd-d": "project_panel::Duplicate",
"alt-cmd-n": "project_panel::NewDirectory",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
@@ -621,9 +614,8 @@
"enter": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
@@ -690,7 +682,6 @@
"cmd-c": "terminal::Copy",
"cmd-v": "terminal::Paste",
"cmd-k": "terminal::Clear",
"ctrl-enter": "assistant::InlineAssist",
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"],
"cmd-right": ["terminal::SendText", "\u0005"],
@@ -706,13 +697,7 @@
"pagedown": ["terminal::SendKeystroke", "pagedown"],
"escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown",
"shift-up": "terminal::ScrollLineUp",
"shift-down": "terminal::ScrollLineDown",
"shift-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom"
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
}
}
]

View File

@@ -78,7 +78,6 @@
"bindings": {
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle",
"cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy"
@@ -95,10 +94,6 @@
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
}

View File

@@ -19,7 +19,6 @@
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
"cmd-shift-l": "editor::SplitSelectionIntoLines",
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
"cmd-shift-d": "editor::DuplicateLineDown",
"shift-f12": "editor::FindAllReferences",
"alt-cmd-down": "editor::GoToDefinition",
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",

View File

@@ -87,15 +87,7 @@
},
{
"context": "ProjectPanel",
"bindings": {
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-d": "project_panel::Duplicate",
"cmd-n": "project_panel::NewFolder",
"return": "project_panel::Rename",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
"cmd-alt-c": "project_panel::CopyPath"
}
"bindings": {}
},
{
"context": "Dock",

View File

@@ -47,16 +47,19 @@
"{": "vim::StartOfParagraph",
"}": "vim::EndOfParagraph",
"|": "vim::GoToColumn",
// Word motions
"w": "vim::NextWordStart",
"e": "vim::NextWordEnd",
"b": "vim::PreviousWordStart",
"g e": "vim::PreviousWordEnd",
// Subword motions
// "w": "vim::NextSubwordStart",
// "b": "vim::PreviousSubwordStart",
// "e": "vim::NextSubwordEnd",
// "g e": "vim::PreviousSubwordEnd",
"shift-w": [
"vim::NextWordStart",
{
@@ -75,12 +78,8 @@
"ignorePunctuation": true
}
],
"g shift-e": [
"vim::PreviousWordEnd",
{
"ignorePunctuation": true
}
],
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
"/": "vim::Search",
"g /": "pane::DeploySearch",
"?": [
@@ -127,22 +126,8 @@
}
],
"m": ["vim::PushOperator", "Mark"],
"'": [
"vim::PushOperator",
{
"Jump": {
"line": true
}
}
],
"`": [
"vim::PushOperator",
{
"Jump": {
"line": false
}
}
],
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
";": "vim::RepeatFind",
",": "vim::RepeatFindReversed",
"ctrl-o": "pane::GoBack",
@@ -346,6 +331,7 @@
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w d": "editor::GoToDefinitionSplit",
"ctrl-w g d": "editor::GoToDefinitionSplit",
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
@@ -403,9 +389,6 @@
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
"\"": ["vim::PushOperator", "Register"],
"q": "vim::ToggleRecord",
"shift-q": "vim::ReplayLastRecording",
"@": ["vim::PushOperator", "ReplayRegister"],
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
// tree-sitter related commands
@@ -693,8 +676,7 @@
{
"context": "EmptyPane || SharedScreen",
"bindings": {
":": "command_palette::Toggle",
"g /": "pane::DeploySearch"
":": "command_palette::Toggle"
}
},
{
@@ -722,14 +704,5 @@
"g g": "menu::SelectFirst",
"-": "project_panel::SelectParent"
}
},
{
"context": "OutlinePanel",
"bindings": {
"j": "menu::SelectNext",
"k": "menu::SelectPrev",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst"
}
}
]

View File

@@ -1,15 +1,19 @@
{
// The name of the Zed theme to use for the UI.
//
// `mode` is one of:
// The theme can also be set to follow system preferences:
//
// "theme": {
// "mode": "system",
// "light": "One Light",
// "dark": "One Dark"
// }
//
// Where `mode` is one of:
// - "system": Use the theme that corresponds to the system's appearance
// - "light": Use the theme indicated by the "light" field
// - "dark": Use the theme indicated by the "dark" field
"theme": {
"mode": "system",
"light": "One Light",
"dark": "One Dark"
},
"theme": "One Dark",
// The name of a base set of key bindings to use.
// This setting can take four values, each named after another
// text editor:
@@ -25,7 +29,7 @@
"inline_completion_provider": "copilot"
},
// The name of a font to use for rendering text in the editor
"buffer_font_family": "Zed Plex Mono",
"buffer_font_family": "Zed Mono",
// The OpenType features to enable for text in the editor.
"buffer_font_features": {
// Disable ligatures:
@@ -38,17 +42,16 @@
// Set the buffer's line height.
// May take 3 values:
// 1. Use a line height that's comfortable for reading (1.618)
// "buffer_line_height": "comfortable"
// "line_height": "comfortable"
// 2. Use a standard line height, (1.3)
// "buffer_line_height": "standard",
// "line_height": "standard",
// 3. Use a custom line height
// "buffer_line_height": {
// "line_height": {
// "custom": 2
// },
"buffer_line_height": "comfortable",
// The name of a font to use for rendering text in the UI
// (On macOS) You can set this to ".SysmtemUIFont" to use the system font
"ui_font_family": "Zed Plex Sans",
"ui_font_family": ".SystemUIFont",
// The OpenType features to enable for text in the UI
"ui_font_features": {
// Disable ligatures:
@@ -162,11 +165,11 @@
// 1. Draw tabs and spaces only for the selected text (default):
// "selection"
// 2. Do not draw any tabs or spaces:
// "none"
// "none"
// 3. Draw all invisible symbols:
// "all"
// "all"
// 4. Draw whitespaces at boundaries only:
// "boundary"
// "boundaries"
// For a whitespace to be on a boundary, any of the following conditions need to be met:
// - It is a tab
// - It is adjacent to an edge (start or end)
@@ -307,8 +310,8 @@
// when a corresponding project entry becomes active.
// Gitignored entries are never auto revealed.
"auto_reveal_entries": true,
// Whether to fold directories automatically and show compact folders
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
/// Whether to fold directories automatically
/// when a directory has only one directory inside.
"auto_fold_dirs": false,
/// Scrollbar-related settings
"scrollbar": {
@@ -666,17 +669,13 @@
// "font_size": 15,
// Set the terminal's font family. If this option is not included,
// the terminal will default to matching the buffer's font family.
// "font_family": "Zed Plex Mono",
// "font_family": "Zed Mono",
// Sets the maximum number of lines in the terminal's scrollback buffer.
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
// Existing terminals will not pick up this change until they are recreated.
// "max_scroll_history_lines": 10000,
},
"code_actions_on_format": {},
/// Settings related to running tasks.
"tasks": {
"variables": {}
},
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
// use those languages.
@@ -689,9 +688,7 @@
// "TOML": ["Embargo.lock"]
// }
//
"file_types": {
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json"]
},
"file_types": {},
// The extensions that Zed should automatically install on startup.
//
// If you don't want any of these extensions, add this field to your settings
@@ -761,11 +758,6 @@
"allowed": true
}
},
"JSONC": {
"prettier": {
"allowed": true
}
},
"Markdown": {
"format_on_save": "off",
"prettier": {

View File

@@ -8,10 +8,5 @@
// from the command palette or from `Zed` application menu.
{
"ui_font_size": 16,
"buffer_font_size": 16,
"theme": {
"mode": "system",
"light": "One Light",
"dark": "One Dark"
}
"buffer_font_size": 16
}

View File

@@ -52,13 +52,4 @@ impl Assets {
cx.text_system().add_fonts(embedded_fonts)
}
pub fn load_test_fonts(&self, cx: &AppContext) {
cx.text_system()
.add_fonts(vec![self
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
.unwrap()
.unwrap()])
.unwrap()
}
}

View File

@@ -13,18 +13,17 @@ path = "src/assistant.rs"
doctest = false
[dependencies]
anthropic = { workspace = true, features = ["schemars"] }
anyhow.workspace = true
anthropic = { workspace = true, features = ["schemars"] }
assistant_slash_command.workspace = true
async-watch.workspace = true
breadcrumbs.workspace = true
cargo_toml.workspace = true
chrono.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
editor.workspace = true
feature_flags.workspace = true
file_icons.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
@@ -32,7 +31,6 @@ gpui.workspace = true
heed.workspace = true
html_to_markdown.workspace = true
http.workspace = true
indexed_docs.workspace = true
indoc.workspace = true
language.workspace = true
log.workspace = true
@@ -46,6 +44,7 @@ paths.workspace = true
project.workspace = true
regex.workspace = true
rope.workspace = true
rustdoc.workspace = true
schemars.workspace = true
search.workspace = true
semantic_index.workspace = true
@@ -57,7 +56,6 @@ smol.workspace = true
strsim = "0.11"
strum.workspace = true
telemetry_events.workspace = true
terminal.workspace = true
terminal_view.workspace = true
theme.workspace = true
tiktoken-rs.workspace = true

View File

@@ -9,7 +9,6 @@ mod prompts;
mod search;
mod slash_command;
mod streaming_diff;
mod terminal_inline_assistant;
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
use assistant_settings::{AnthropicModel, AssistantSettings, CloudModel, OllamaModel, OpenAiModel};
@@ -20,16 +19,15 @@ pub(crate) use completion_provider::*;
pub(crate) use context_store::*;
use fs::Fs;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
use indexed_docs::IndexedDocsRegistry;
pub(crate) use inline_assistant::*;
pub(crate) use model_selector::*;
use rustdoc::RustdocStore;
use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use slash_command::{
active_command, default_command, diagnostics_command, docs_command, fetch_command,
file_command, now_command, project_command, prompt_command, search_command, tabs_command,
term_command,
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
project_command, prompt_command, rustdoc_command, search_command, tabs_command, term_command,
};
use std::{
fmt::{self, Display},
@@ -44,13 +42,11 @@ actions!(
Split,
CycleMessageRole,
QuoteSelection,
InsertIntoEditor,
ToggleFocus,
ResetKey,
InlineAssist,
InsertActivePrompt,
DeployHistory,
DeployPromptLibrary,
ToggleHistory,
ApplyEdit,
ConfirmCommand,
ToggleModelSelector
@@ -293,8 +289,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
register_slash_commands(cx);
assistant_panel::init(cx);
inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
terminal_inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
IndexedDocsRegistry::init_global(cx);
RustdocStore::init_global(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(Assistant::NAMESPACE);
@@ -324,8 +319,8 @@ fn register_slash_commands(cx: &mut AppContext) {
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
slash_command_registry.register_command(term_command::TermSlashCommand, true);
slash_command_registry.register_command(now_command::NowSlashCommand, true);
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
slash_command_registry.register_command(docs_command::DocsSlashCommand, true);
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
}

File diff suppressed because it is too large Load Diff

View File

@@ -169,7 +169,6 @@ pub enum AssistantProvider {
model: OpenAiModel,
api_url: String,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Vec<OpenAiModel>,
},
Anthropic {
model: AnthropicModel,
@@ -189,7 +188,6 @@ impl Default for AssistantProvider {
model: OpenAiModel::default(),
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
}
}
@@ -204,7 +202,6 @@ pub enum AssistantProviderContent {
default_model: Option<OpenAiModel>,
api_url: Option<String>,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Option<Vec<OpenAiModel>>,
},
#[serde(rename = "anthropic")]
Anthropic {
@@ -275,7 +272,6 @@ impl AssistantSettingsContent {
default_model: settings.default_open_ai_model.clone(),
api_url: Some(open_ai_api_url.clone()),
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
})
} else {
settings.default_open_ai_model.clone().map(|open_ai_model| {
@@ -283,7 +279,6 @@ impl AssistantSettingsContent {
default_model: Some(open_ai_model),
api_url: None,
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
}
})
},
@@ -331,14 +326,6 @@ impl AssistantSettingsContent {
*model = Some(new_model);
}
}
Some(AssistantProviderContent::Ollama {
default_model: model,
..
}) => {
if let LanguageModel::Ollama(new_model) = new_model {
*model = Some(new_model);
}
}
provider => match new_model {
LanguageModel::Cloud(model) => {
*provider = Some(AssistantProviderContent::ZedDotDev {
@@ -350,7 +337,6 @@ impl AssistantSettingsContent {
default_model: Some(model),
api_url: None,
low_speed_timeout_in_seconds: None,
available_models: Some(Default::default()),
})
}
LanguageModel::Anthropic(model) => {
@@ -495,18 +481,15 @@ impl Settings for AssistantSettings {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
AssistantProviderContent::OpenAi {
default_model: model_override,
api_url: api_url_override,
low_speed_timeout_in_seconds: low_speed_timeout_in_seconds_override,
available_models: available_models_override,
},
) => {
merge(model, model_override);
merge(api_url, api_url_override);
merge(available_models, available_models_override);
if let Some(low_speed_timeout_in_seconds_override) =
low_speed_timeout_in_seconds_override
{
@@ -567,12 +550,10 @@ impl Settings for AssistantSettings {
default_model: model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => AssistantProvider::OpenAi {
model: model.unwrap_or_default(),
api_url: api_url.unwrap_or_else(|| open_ai::OPEN_AI_API_URL.into()),
low_speed_timeout_in_seconds,
available_models: available_models.unwrap_or_default(),
},
AssistantProviderContent::Anthropic {
default_model: model,
@@ -629,7 +610,6 @@ mod tests {
model: OpenAiModel::FourOmni,
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);
@@ -652,7 +632,6 @@ mod tests {
model: OpenAiModel::FourOmni,
api_url: "test-url".into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);
SettingsStore::update_global(cx, |store, cx| {
@@ -673,7 +652,6 @@ mod tests {
model: OpenAiModel::Four,
api_url: open_ai::OPEN_AI_API_URL.into(),
low_speed_timeout_in_seconds: None,
available_models: Default::default(),
}
);

View File

@@ -24,20 +24,6 @@ use settings::{Settings, SettingsStore};
use std::sync::Arc;
use std::time::Duration;
/// Choose which model to use for openai provider.
/// If the model is not available, try to use the first available model, or fallback to the original model.
fn choose_openai_model(
model: &::open_ai::Model,
available_models: &[::open_ai::Model],
) -> ::open_ai::Model {
available_models
.iter()
.find(|&m| m == model)
.or_else(|| available_models.first())
.unwrap_or_else(|| model)
.clone()
}
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
let mut settings_version = 0;
let provider = match &AssistantSettings::get_global(cx).provider {
@@ -48,9 +34,8 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
@@ -92,11 +77,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
provider.update(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
@@ -152,11 +136,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
*provider = CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
choose_openai_model(model, available_models),
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
@@ -218,10 +201,10 @@ impl CompletionProvider {
cx.global::<Self>()
}
pub fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
pub fn available_models(&self) -> Vec<LanguageModel> {
match self {
CompletionProvider::OpenAi(provider) => provider
.available_models(cx)
.available_models()
.map(LanguageModel::OpenAi)
.collect(),
CompletionProvider::Anthropic(provider) => provider

View File

@@ -236,7 +236,7 @@ pub fn preprocess_anthropic_request(request: &mut LanguageModelRequest) {
}
if !system_message.is_empty() {
new_messages.insert(
request.messages.insert(
0,
LanguageModelRequestMessage {
role: Role::System,

View File

@@ -1,5 +1,4 @@
use crate::assistant_settings::CloudModel;
use crate::assistant_settings::{AssistantProvider, AssistantSettings};
use crate::{
assistant_settings::OpenAiModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
};
@@ -57,26 +56,8 @@ impl OpenAiCompletionProvider {
self.settings_version = settings_version;
}
pub fn available_models(&self, cx: &AppContext) -> impl Iterator<Item = OpenAiModel> {
if let AssistantProvider::OpenAi {
available_models, ..
} = &AssistantSettings::get_global(cx).provider
{
if !available_models.is_empty() {
// available_models is set, just return it
return available_models.clone().into_iter();
}
}
let available_models = if matches!(self.model, OpenAiModel::Custom { .. }) {
// available_models is not set but the default model is set to custom, only show custom
vec![self.model.clone()]
} else {
// default case, use all models except custom
OpenAiModel::iter()
.filter(|model| !matches!(model, OpenAiModel::Custom { .. }))
.collect()
};
available_models.into_iter()
pub fn available_models(&self) -> impl Iterator<Item = OpenAiModel> {
OpenAiModel::iter()
}
pub fn settings_version(&self) -> usize {
@@ -232,8 +213,7 @@ pub fn count_open_ai_tokens(
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Opus)
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
| LanguageModel::Cloud(CloudModel::Claude3Haiku)
| LanguageModel::OpenAi(OpenAiModel::Custom { .. }) => {
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
// Tiktoken doesn't yet support these models, so we manually use the
// same tokenizer as GPT-4.
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)

View File

@@ -18,9 +18,9 @@ use editor::{
use fs::Fs;
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
use gpui::{
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, Global, HighlightStyle,
Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView,
WhiteSpace, WindowContext,
point, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global,
HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View,
ViewContext, WeakView, WhiteSpace, WindowContext,
};
use language::{Buffer, Point, Selection, TransactionId};
use multi_buffer::MultiBufferRow;
@@ -1298,8 +1298,7 @@ impl Render for PromptEditor {
PopoverMenu::new("model-switcher")
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::global(cx).available_models(cx)
{
for model in CompletionProvider::global(cx).available_models() {
menu = menu.custom_entry(
{
let model = model.clone();
@@ -1730,7 +1729,7 @@ impl PromptEditor {
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3),
background_color: None,

View File

@@ -23,7 +23,7 @@ impl RenderOnce for ModelSelector {
.with_handle(self.handle)
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::global(cx).available_models(cx) {
for model in CompletionProvider::global(cx).available_models() {
menu = menu.custom_entry(
{
let model = model.clone();
@@ -79,6 +79,6 @@ impl RenderOnce for ModelSelector {
Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
}),
)
.attach(gpui::AnchorCorner::BottomLeft)
.anchor(gpui::AnchorCorner::BottomRight)
}
}

View File

@@ -5,7 +5,7 @@ use crate::{
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandRegistry;
use chrono::{DateTime, Utc};
use collections::{HashMap, HashSet};
use collections::HashMap;
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
use futures::{
future::{self, BoxFuture, Shared},
@@ -34,7 +34,7 @@ use std::{
use theme::ThemeSettings;
use ui::{
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
SharedString, Styled, Tooltip, ViewContext, VisualContext,
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
};
use util::{ResultExt, TryFutureExt};
use uuid::Uuid;
@@ -42,12 +42,7 @@ use workspace::Workspace;
actions!(
prompt_library,
[
NewPrompt,
DeletePrompt,
DuplicatePrompt,
ToggleDefaultPrompt
]
[NewPrompt, DeletePrompt, ToggleDefaultPrompt]
);
/// Init starts loading the PromptStore in the background and assigns
@@ -416,12 +411,6 @@ impl PromptLibrary {
}
}
pub fn duplicate_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.duplicate_prompt(active_prompt_id, cx);
}
}
pub fn toggle_default_for_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_prompt_id) = self.active_prompt_id {
self.toggle_default_for_prompt(active_prompt_id, cx);
@@ -575,47 +564,6 @@ impl PromptLibrary {
}
}
pub fn duplicate_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
if let Some(prompt) = self.prompt_editors.get(&prompt_id) {
const DUPLICATE_SUFFIX: &str = " copy";
let title_to_duplicate = prompt.title_editor.read(cx).text(cx);
let existing_titles = self
.prompt_editors
.iter()
.filter(|&(&id, _)| id != prompt_id)
.map(|(_, prompt_editor)| prompt_editor.title_editor.read(cx).text(cx))
.filter(|title| title.starts_with(&title_to_duplicate))
.collect::<HashSet<_>>();
let title = if existing_titles.is_empty() {
title_to_duplicate + DUPLICATE_SUFFIX
} else {
let mut i = 1;
loop {
let new_title = format!("{title_to_duplicate}{DUPLICATE_SUFFIX} {i}");
if !existing_titles.contains(&new_title) {
break new_title;
}
i += 1;
}
};
let new_id = PromptId::new();
let body = prompt.body_editor.read(cx).text(cx);
let save = self
.store
.save(new_id, Some(title.into()), false, body.into());
self.picker.update(cx, |picker, cx| picker.refresh(cx));
cx.spawn(|this, mut cx| async move {
save.await?;
this.update(&mut cx, |prompt_library, cx| {
prompt_library.load_prompt(new_id, true, cx)
})
})
.detach_and_log_err(cx);
}
}
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
if let Some(active_prompt) = self.active_prompt_id {
self.prompt_editors[&active_prompt]
@@ -773,7 +721,7 @@ impl PromptLibrary {
.child(
h_flex()
.p(Spacing::Small.rems(cx))
.h_9()
.h(TitleBar::height(cx))
.w_full()
.flex_none()
.justify_end()
@@ -893,42 +841,6 @@ impl PromptLibrary {
h_flex()
.h_full()
.gap(Spacing::XXLarge.rems(cx))
.children(prompt_editor.token_count.map(
|token_count| {
let token_count: SharedString =
token_count.to_string().into();
let label_token_count: SharedString =
token_count.to_string().into();
h_flex()
.id("token_count")
.tooltip(move |cx| {
let token_count =
token_count.clone();
Tooltip::with_meta(
format!(
"{} tokens",
token_count.clone()
),
None,
format!(
"Model: {}",
current_model
.display_name()
),
cx,
)
})
.child(
Label::new(format!(
"{} tokens",
label_token_count.clone()
))
.color(Color::Muted),
)
},
))
.child(
IconButton::new(
"delete-prompt",
@@ -949,28 +861,24 @@ impl PromptLibrary {
cx.dispatch_action(Box::new(DeletePrompt));
}),
)
.child(
IconButton::new(
"duplicate-prompt",
IconName::BookCopy,
)
.size(ButtonSize::Large)
.style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.size(ButtonSize::Large)
.tooltip(move |cx| {
Tooltip::for_action(
"Duplicate Prompt",
&DuplicatePrompt,
cx,
)
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(
DuplicatePrompt,
));
}),
)
// .child(
// IconButton::new(
// "duplicate-prompt",
// IconName::BookCopy,
// )
// .size(ButtonSize::Large)
// .style(ButtonStyle::Transparent)
// .shape(IconButtonShape::Square)
// .size(ButtonSize::Large)
// .tooltip(move |cx| {
// Tooltip::for_action(
// "Duplicate Prompt",
// &gpui::NoAction,
// cx,
// )
// })
// .disabled(true),
// )
.child(
IconButton::new(
"toggle-default-prompt",
@@ -1012,7 +920,38 @@ impl PromptLibrary {
.on_action(cx.listener(Self::move_up_from_body))
.flex_grow()
.h_full()
.child(prompt_editor.body_editor.clone()),
.child(prompt_editor.body_editor.clone())
.children(prompt_editor.token_count.map(|token_count| {
let token_count: SharedString = token_count.to_string().into();
let label_token_count: SharedString =
token_count.to_string().into();
h_flex()
.id("token_count")
.absolute()
.bottom_1()
.right_4()
.flex_initial()
.px_2()
.py_1()
.tooltip(move |cx| {
let token_count = token_count.clone();
Tooltip::with_meta(
format!("{} tokens", token_count.clone()),
None,
format!("Model: {}", current_model.display_name()),
cx,
)
})
.child(
Label::new(format!(
"{} tokens",
label_token_count.clone()
))
.color(Color::Muted),
)
})),
),
)
}))
@@ -1029,7 +968,6 @@ impl Render for PromptLibrary {
.key_context("PromptLibrary")
.on_action(cx.listener(|this, &NewPrompt, cx| this.new_prompt(cx)))
.on_action(cx.listener(|this, &DeletePrompt, cx| this.delete_active_prompt(cx)))
.on_action(cx.listener(|this, &DuplicatePrompt, cx| this.duplicate_active_prompt(cx)))
.on_action(cx.listener(|this, &ToggleDefaultPrompt, cx| {
this.toggle_default_for_active_prompt(cx)
}))

View File

@@ -6,130 +6,118 @@ pub fn generate_content_prompt(
language_name: Option<&str>,
buffer: BufferSnapshot,
range: Range<usize>,
_project_name: Option<String>,
project_name: Option<String>,
) -> anyhow::Result<String> {
let mut prompt = String::new();
let content_type = match language_name {
None | Some("Markdown" | "Plain Text") => {
writeln!(
prompt,
"Here's a file of text that I'm going to ask you to make an edit to."
)?;
"text"
writeln!(prompt, "You are an expert engineer.")?;
"Text"
}
Some(language_name) => {
writeln!(prompt, "You are an expert {language_name} engineer.")?;
writeln!(
prompt,
"Here's a file of {language_name} that I'm going to ask you to make an edit to."
"Your answer MUST always and only be valid {}.",
language_name
)?;
"code"
"Code"
}
};
const MAX_CTX: usize = 50000;
let mut is_truncated = false;
if let Some(project_name) = project_name {
writeln!(
prompt,
"You are currently working inside the '{project_name}' project in code editor Zed."
)?;
}
writeln!(
prompt,
"The user has the following file open in the editor:"
)?;
if range.is_empty() {
prompt.push_str("The point you'll need to insert at is marked with <insert_here></insert_here>.\n\n<document>");
write!(prompt, "```")?;
if let Some(language_name) = language_name {
write!(prompt, "{language_name}")?;
}
for chunk in buffer.as_rope().chunks_in_range(0..range.start) {
prompt.push_str(chunk);
}
prompt.push_str("<|CURSOR|>");
for chunk in buffer.as_rope().chunks_in_range(range.start..buffer.len()) {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"Assume the cursor is located where the `<|CURSOR|>` span is."
)
.unwrap();
writeln!(
prompt,
"{content_type} can't be replaced, so assume your answer will be inserted at the cursor.",
)
.unwrap();
writeln!(
prompt,
"Generate {content_type} based on the users prompt: {user_prompt}",
)
.unwrap();
} else {
prompt.push_str("The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.\n\n<document>");
}
// Include file content.
let before_range = 0..range.start;
let truncated_before = if before_range.len() > MAX_CTX {
is_truncated = true;
range.start - MAX_CTX..range.start
} else {
before_range
};
let mut non_rewrite_len = truncated_before.len();
for chunk in buffer.text_for_range(truncated_before) {
prompt.push_str(chunk);
}
if !range.is_empty() {
prompt.push_str("<rewrite_this>\n");
write!(prompt, "```")?;
for chunk in buffer.as_rope().chunks() {
prompt.push_str(chunk);
}
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"In particular, the following piece of text is selected:"
)?;
write!(prompt, "```")?;
if let Some(language_name) = language_name {
write!(prompt, "{language_name}")?;
}
prompt.push('\n');
for chunk in buffer.text_for_range(range.clone()) {
prompt.push_str(chunk);
}
prompt.push_str("\n<rewrite_this>");
} else {
prompt.push_str("<insert_here></insert_here>");
}
let after_range = range.end..buffer.len();
let truncated_after = if after_range.len() > MAX_CTX {
is_truncated = true;
range.end..range.end + MAX_CTX
} else {
after_range
};
non_rewrite_len += truncated_after.len();
for chunk in buffer.text_for_range(truncated_after) {
prompt.push_str(chunk);
}
write!(prompt, "</document>\n\n").unwrap();
if is_truncated {
writeln!(prompt, "The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.\n")?;
}
if range.is_empty() {
writeln!(
prompt,
"You can't replace {content_type}, your answer will be inserted in place of the `<insert_here></insert_here>` tags. Don't include the insert_here tags in your output.",
)
.unwrap();
writeln!(
prompt,
"Generate {content_type} based on the following prompt:\n\n<prompt>\n{user_prompt}\n</prompt>",
)
.unwrap();
writeln!(prompt, "Match the indentation in the original file in the inserted {content_type}, don't include any indentation on blank lines.\n").unwrap();
prompt.push_str("Immediately start with the following format with no remarks:\n\n```\n{{INSERTED_CODE}}\n```");
} else {
writeln!(prompt, "Edit the section of {content_type} in <rewrite_this></rewrite_this> tags based on the following prompt:'").unwrap();
writeln!(prompt, "\n<prompt>\n{user_prompt}\n</prompt>\n").unwrap();
let rewrite_len = range.end - range.start;
if rewrite_len < 20000 && rewrite_len * 2 < non_rewrite_len {
writeln!(prompt, "And here's the section to rewrite based on that prompt again for reference:\n\n<rewrite_this>\n").unwrap();
for chunk in buffer.text_for_range(range.clone()) {
prompt.push_str(chunk);
}
writeln!(prompt, "\n</rewrite_this>\n").unwrap();
if !prompt.ends_with('\n') {
prompt.push('\n');
}
writeln!(prompt, "Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {content_type} will be preserved.\n").unwrap();
write!(
writeln!(prompt, "```")?;
prompt.push('\n');
writeln!(
prompt,
"Start at the indentation level in the original file in the rewritten {content_type}. "
"Modify the user's selected {content_type} based upon the users prompt: {user_prompt}"
)
.unwrap();
writeln!(
prompt,
"You must reply with only the adjusted {content_type}, not the entire file."
)
.unwrap();
prompt.push_str("Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.");
prompt.push_str("\n\nImmediately start with the following format with no remarks:\n\n```\n{{REWRITTEN_CODE}}\n```");
}
writeln!(prompt, "Never make remarks about the output.").unwrap();
writeln!(
prompt,
"Do not return anything else, except the generated {content_type}."
)
.unwrap();
Ok(prompt)
}
pub fn generate_terminal_assistant_prompt(
user_prompt: &str,
shell: Option<&str>,
working_directory: Option<&str>,
) -> String {
let mut prompt = String::new();
writeln!(&mut prompt, "You are an expert terminal user.").unwrap();
writeln!(&mut prompt, "You will be given a description of a command and you need to respond with a command that matches the description.").unwrap();
writeln!(&mut prompt, "Do not include markdown blocks or any other text formatting in your response, always respond with a single command that can be executed in the given shell.").unwrap();
if let Some(shell) = shell {
writeln!(&mut prompt, "Current shell is '{shell}'.").unwrap();
}
if let Some(working_directory) = working_directory {
writeln!(
&mut prompt,
"Current working directory is '{working_directory}'."
)
.unwrap();
}
writeln!(&mut prompt, "Here is the description of the command:").unwrap();
prompt.push_str(user_prompt);
prompt
}

View File

@@ -20,12 +20,12 @@ use workspace::Workspace;
pub mod active_command;
pub mod default_command;
pub mod diagnostics_command;
pub mod docs_command;
pub mod fetch_command;
pub mod file_command;
pub mod now_command;
pub mod project_command;
pub mod prompt_command;
pub mod rustdoc_command;
pub mod search_command;
pub mod tabs_command;
pub mod term_command;

View File

@@ -20,9 +20,9 @@ use util::paths::PathMatcher;
use util::ResultExt;
use workspace::Workspace;
pub(crate) struct DiagnosticsSlashCommand;
pub(crate) struct DiagnosticsCommand;
impl DiagnosticsSlashCommand {
impl DiagnosticsCommand {
fn search_paths(
&self,
query: String,
@@ -81,7 +81,7 @@ impl DiagnosticsSlashCommand {
}
}
impl SlashCommand for DiagnosticsSlashCommand {
impl SlashCommand for DiagnosticsCommand {
fn name(&self) -> String {
"diagnostics".into()
}

View File

@@ -1,365 +0,0 @@
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::{anyhow, bail, Result};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use gpui::{AppContext, Model, Task, WeakView};
use indexed_docs::{
IndexedDocsRegistry, IndexedDocsStore, LocalProvider, PackageName, ProviderId, RustdocIndexer,
};
use language::LspAdapterDelegate;
use project::{Project, ProjectPath};
use ui::prelude::*;
use util::{maybe, ResultExt};
use workspace::Workspace;
pub(crate) struct DocsSlashCommand;
impl DocsSlashCommand {
pub const NAME: &'static str = "docs";
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
let worktree = project.read(cx).worktrees().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 rustdoc provider is 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_rustdoc_provider_is_registered(
&self,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) {
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
if indexed_docs_registry
.get_provider_store(ProviderId::rustdoc())
.is_none()
{
let index_provider_deps = maybe!({
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
let workspace = workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace was 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()))
.ok_or_else(|| anyhow!("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(RustdocIndexer::new(Box::new(
LocalProvider::new(fs, cargo_workspace_root),
))));
}
}
}
}
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>,
query: String,
_cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
self.ensure_rustdoc_provider_is_registered(workspace, cx);
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
let args = DocsSlashCommandArgs::parse(&query);
let store = args
.provider()
.ok_or_else(|| anyhow!("no docs provider specified"))
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
cx.background_executor().spawn(async move {
/// HACK: Prefixes the completions with the provider ID so that it doesn't get deleted
/// when a completion is accepted.
///
/// We will likely want to extend `complete_argument` with support for replacing just
/// a particular range of the argument when a completion is accepted.
fn prefix_with_provider(provider: ProviderId, items: Vec<String>) -> Vec<String> {
items
.into_iter()
.map(|item| format!("{provider} {item}"))
.collect()
}
match args {
DocsSlashCommandArgs::NoProvider => {
let providers = indexed_docs_registry.list_providers();
Ok(providers
.into_iter()
.map(|provider| provider.to_string())
.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.
let _ = store.clone().index(package.as_str().into());
}
let items = store.search(package).await;
Ok(prefix_with_provider(provider, items))
}
DocsSlashCommandArgs::SearchItemDocs {
provider,
item_path,
..
} => {
let store = store?;
let items = store.search(item_path).await;
Ok(prefix_with_provider(provider, items))
}
}
})
}
fn run(
self: Arc<Self>,
argument: Option<&str>,
_workspace: WeakView<Workspace>,
_delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let Some(argument) = argument else {
return Task::ready(Err(anyhow!("missing argument")));
};
let args = DocsSlashCommandArgs::parse(argument);
let text = cx.background_executor().spawn({
let store = args
.provider()
.ok_or_else(|| anyhow!("no docs provider specified"))
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
async move {
match args {
DocsSlashCommandArgs::NoProvider => bail!("no docs provider specified"),
DocsSlashCommandArgs::SearchPackageDocs {
provider, package, ..
} => {
let store = store?;
let item_docs = store.load(package.clone()).await?;
anyhow::Ok((provider, package, item_docs.to_string()))
}
DocsSlashCommandArgs::SearchItemDocs {
provider,
item_path,
..
} => {
let store = store?;
let item_docs = store.load(item_path.clone()).await?;
anyhow::Ok((provider, item_path, item_docs.to_string()))
}
}
}
});
cx.foreground_executor().spawn(async move {
let (provider, path, text) = text.await?;
let range = 0..text.len();
Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::FileRust,
label: format!("docs ({provider}): {path}",).into(),
}],
run_commands_in_text: false,
})
})
}
}
fn is_item_path_delimiter(char: char) -> bool {
!char.is_alphanumeric() && char != '-' && char != '_'
}
#[derive(Debug, PartialEq)]
pub(crate) enum DocsSlashCommandArgs {
NoProvider,
SearchPackageDocs {
provider: ProviderId,
package: String,
index: bool,
},
SearchItemDocs {
provider: ProviderId,
package: String,
item_path: String,
},
}
impl DocsSlashCommandArgs {
pub fn parse(argument: &str) -> Self {
let Some((provider, argument)) = argument.split_once(' ') else {
return Self::NoProvider;
};
let provider = ProviderId(provider.into());
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())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_docs_slash_command_args() {
assert_eq!(
DocsSlashCommandArgs::parse(""),
DocsSlashCommandArgs::NoProvider
);
assert_eq!(
DocsSlashCommandArgs::parse("rustdoc"),
DocsSlashCommandArgs::NoProvider
);
assert_eq!(
DocsSlashCommandArgs::parse("rustdoc "),
DocsSlashCommandArgs::SearchPackageDocs {
provider: ProviderId("rustdoc".into()),
package: "".into(),
index: false
}
);
assert_eq!(
DocsSlashCommandArgs::parse("gleam "),
DocsSlashCommandArgs::SearchPackageDocs {
provider: ProviderId("gleam".into()),
package: "".into(),
index: false
}
);
assert_eq!(
DocsSlashCommandArgs::parse("rustdoc gpui"),
DocsSlashCommandArgs::SearchPackageDocs {
provider: ProviderId("rustdoc".into()),
package: "gpui".into(),
index: false,
}
);
assert_eq!(
DocsSlashCommandArgs::parse("gleam gleam_stdlib"),
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 gpui:"),
DocsSlashCommandArgs::SearchPackageDocs {
provider: ProviderId("rustdoc".into()),
package: "gpui".into(),
index: true,
}
);
assert_eq!(
DocsSlashCommandArgs::parse("gleam gleam_stdlib/"),
DocsSlashCommandArgs::SearchPackageDocs {
provider: ProviderId("gleam".into()),
package: "gleam_stdlib".into(),
index: true
}
);
assert_eq!(
DocsSlashCommandArgs::parse("rustdoc gpui::foo::bar::Baz"),
DocsSlashCommandArgs::SearchItemDocs {
provider: ProviderId("rustdoc".into()),
package: "gpui".into(),
item_path: "gpui::foo::bar::Baz".into()
}
);
assert_eq!(
DocsSlashCommandArgs::parse("gleam gleam_stdlib/gleam/int"),
DocsSlashCommandArgs::SearchItemDocs {
provider: ProviderId("gleam".into()),
package: "gleam_stdlib".into(),
item_path: "gleam_stdlib/gleam/int".into()
}
);
}
}

View File

@@ -3,10 +3,10 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use chrono::Local;
use chrono::{DateTime, Local};
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use ui::prelude::*;
use ui::{prelude::*, ButtonLike, ElevationIndex};
use workspace::Workspace;
pub(crate) struct NowSlashCommand;
@@ -46,7 +46,7 @@ impl SlashCommand for NowSlashCommand {
_cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let now = Local::now();
let text = format!("Today is {now}.", now = now.to_rfc2822());
let text = format!("Today is {now}.", now = now.to_rfc3339());
let range = 0..text.len();
Task::ready(Ok(SlashCommandOutput {
@@ -54,9 +54,29 @@ impl SlashCommand for NowSlashCommand {
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::CountdownTimer,
label: now.to_rfc2822().into(),
label: now.to_rfc3339().into(),
}],
run_commands_in_text: false,
}))
}
}
#[derive(IntoElement)]
struct NowPlaceholder {
pub id: ElementId,
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
pub now: DateTime<Local>,
}
impl RenderOnce for NowPlaceholder {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let unfold = self.unfold;
ButtonLike::new(self.id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::CountdownTimer))
.child(Label::new(self.now.to_rfc3339()))
.on_click(move |_, cx| unfold(cx))
}
}

View File

@@ -0,0 +1,238 @@
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::{anyhow, bail, Context, Result};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use fs::Fs;
use futures::AsyncReadExt;
use gpui::{AppContext, Model, Task, WeakView};
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::LspAdapterDelegate;
use project::{Project, ProjectPath};
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
use ui::prelude::*;
use util::{maybe, ResultExt};
use workspace::Workspace;
pub(crate) struct RustdocSlashCommand;
impl RustdocSlashCommand {
async fn build_message(
fs: Arc<dyn Fs>,
http_client: Arc<HttpClientWithUrl>,
crate_name: CrateName,
module_path: Vec<String>,
path_to_cargo_toml: Option<&Path>,
) -> Result<(RustdocSource, String)> {
let cargo_workspace_root = path_to_cargo_toml.and_then(|path| path.parent());
if let Some(cargo_workspace_root) = cargo_workspace_root {
let mut local_cargo_doc_path = cargo_workspace_root.join("target/doc");
local_cargo_doc_path.push(crate_name.as_ref());
if !module_path.is_empty() {
local_cargo_doc_path.push(module_path.join("/"));
}
local_cargo_doc_path.push("index.html");
if let Ok(contents) = fs.load(&local_cargo_doc_path).await {
let (markdown, _items) = convert_rustdoc_to_markdown(contents.as_bytes())?;
return Ok((RustdocSource::Local, markdown));
}
}
let version = "latest";
let path = format!(
"{crate_name}/{version}/{crate_name}/{module_path}",
module_path = module_path.join("/")
);
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()
);
}
let (markdown, _items) = convert_rustdoc_to_markdown(&body[..])?;
Ok((RustdocSource::DocsDotRs, markdown))
}
fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
let worktree = project.read(cx).worktrees().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(),
))
}
}
impl SlashCommand for RustdocSlashCommand {
fn name(&self) -> String {
"rustdoc".into()
}
fn description(&self) -> String {
"insert Rust docs".into()
}
fn menu_text(&self) -> String {
"Insert Rust Documentation".into()
}
fn requires_argument(&self) -> bool {
true
}
fn complete_argument(
self: Arc<Self>,
query: String,
_cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
let index_provider_deps = maybe!({
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
let workspace = workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace was 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()))
.ok_or_else(|| anyhow!("no Cargo workspace root found"))?;
anyhow::Ok((fs, cargo_workspace_root))
});
let store = RustdocStore::global(cx);
cx.background_executor().spawn(async move {
if let Some((crate_name, rest)) = query.split_once(':') {
if rest.is_empty() {
if let Some((fs, cargo_workspace_root)) = index_provider_deps.log_err() {
let provider = Box::new(LocalProvider::new(fs, cargo_workspace_root));
// We don't need to hold onto this task, as the `RustdocStore` will hold it
// until it completes.
let _ = store.clone().index(crate_name.into(), provider);
}
}
}
let items = store.search(query).await;
Ok(items)
})
}
fn run(
self: Arc<Self>,
argument: Option<&str>,
workspace: WeakView<Workspace>,
_delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let Some(argument) = argument else {
return Task::ready(Err(anyhow!("missing crate name")));
};
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
};
let project = workspace.read(cx).project().clone();
let fs = project.read(cx).fs().clone();
let http_client = workspace.read(cx).client().http_client();
let path_to_cargo_toml = Self::path_to_cargo_toml(project, cx);
let mut path_components = argument.split("::");
let crate_name = match path_components
.next()
.ok_or_else(|| anyhow!("missing crate name"))
{
Ok(crate_name) => CrateName::from(crate_name),
Err(err) => return Task::ready(Err(err)),
};
let item_path = path_components.map(ToString::to_string).collect::<Vec<_>>();
let text = cx.background_executor().spawn({
let rustdoc_store = RustdocStore::global(cx);
let crate_name = crate_name.clone();
let item_path = item_path.clone();
async move {
let item_docs = rustdoc_store
.load(
crate_name.clone(),
if item_path.is_empty() {
None
} else {
Some(item_path.join("::"))
},
)
.await;
if let Ok(item_docs) = item_docs {
anyhow::Ok((RustdocSource::Index, item_docs.docs().to_owned()))
} else {
Self::build_message(
fs,
http_client,
crate_name,
item_path,
path_to_cargo_toml.as_deref(),
)
.await
}
}
});
let module_path = if item_path.is_empty() {
None
} else {
Some(SharedString::from(item_path.join("::")))
};
cx.foreground_executor().spawn(async move {
let (source, text) = text.await?;
let range = 0..text.len();
let crate_path = module_path
.map(|module_path| format!("{}::{}", crate_name, module_path))
.unwrap_or_else(|| crate_name.to_string());
Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::FileRust,
label: format!(
"rustdoc ({source}): {crate_path}",
source = match source {
RustdocSource::Index => "index",
RustdocSource::Local => "local",
RustdocSource::DocsDotRs => "docs.rs",
}
)
.into(),
}],
run_commands_in_text: false,
})
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -196,24 +196,23 @@ mod linux {
impl Detect {
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
let path = if let Some(path) = path {
path.to_path_buf().canonicalize()?
path.to_path_buf().canonicalize()
} else {
let cli = env::current_exe()?;
let dir = cli
.parent()
.and_then(Path::parent)
.ok_or_else(|| anyhow!("no parent path for cli"))?;
// libexec is the standard, lib/zed is for Arch (and other non-libexec distros),
// ./zed is for the target directory in development builds.
let possible_locations =
["../libexec/zed-editor", "../lib/zed/zed-editor", "./zed"];
possible_locations
.iter()
.find_map(|p| dir.join(p).canonicalize().ok().filter(|path| path != &cli))
.ok_or_else(|| {
anyhow!("could not find any of: {}", possible_locations.join(", "))
})?
};
match dir.join("libexec").join("zed-editor").canonicalize() {
Ok(path) => Ok(path),
// In development cli and zed are in the ./target/ directory together
Err(e) => match cli.parent().unwrap().join("zed").canonicalize() {
Ok(path) if path != cli => Ok(path),
_ => Err(e),
},
}
}?;
Ok(App(path))
}

View File

@@ -202,10 +202,6 @@ impl Telemetry {
event_coalescer: EventCoalescer::new(clock.clone()),
max_queue_size: MAX_QUEUE_LEN,
worktree_id_map: WorktreeIdMap(HashMap::from_iter([
(
"pnpm-lock.yaml".to_string(),
ProjectCache::new("pnpm".to_string()),
),
(
"yarn.lock".to_string(),
ProjectCache::new("yarn".to_string()),
@@ -615,7 +611,6 @@ impl Telemetry {
let request_body = EventRequestBody {
installation_id: state.installation_id.as_deref().map(Into::into),
metrics_id: state.metrics_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
is_staff: state.is_staff,
app_version: state.app_version.clone(),

View File

@@ -87,27 +87,51 @@ impl Global {
}
pub fn observed_any(&self, other: &Self) -> bool {
self.0
.iter()
.zip(other.0.iter())
.any(|(left, right)| *right > 0 && left >= right)
let mut lhs = self.0.iter();
let mut rhs = other.0.iter();
loop {
if let Some(left) = lhs.next() {
if let Some(right) = rhs.next() {
if *right > 0 && left >= right {
return true;
}
} else {
return false;
}
} else {
return false;
}
}
}
pub fn observed_all(&self, other: &Self) -> bool {
let mut lhs = self.0.iter();
let mut rhs = other.0.iter();
self.0.iter().all(|left| match rhs.next() {
Some(right) => left >= right,
None => true,
}) && rhs.next().is_none()
loop {
if let Some(left) = lhs.next() {
if let Some(right) = rhs.next() {
if left < right {
return false;
}
} else {
return true;
}
} else {
return rhs.next().is_none();
}
}
}
pub fn changed_since(&self, other: &Self) -> bool {
self.0.len() > other.0.len()
|| self
.0
.iter()
.zip(other.0.iter())
.any(|(left, right)| left > right)
if self.0.len() > other.0.len() {
return true;
}
for (left, right) in self.0.iter().zip(other.0.iter()) {
if left > right {
return true;
}
}
false
}
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {

View File

@@ -664,7 +664,6 @@ where
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
installation_id: String,
metrics_id: String,
operation: String,
app_version: String,
file_extension: String,
@@ -714,7 +713,6 @@ impl EditorEventRow {
os_version: body.os_version.clone().unwrap_or_default(),
architecture: body.architecture.clone(),
installation_id: body.installation_id.clone().unwrap_or_default(),
metrics_id: body.metrics_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),

View File

@@ -36,6 +36,8 @@ pub fn router() -> Router {
struct GetExtensionsParams {
filter: Option<String>,
#[serde(default)]
ids: Option<String>,
#[serde(default)]
max_schema_version: i32,
}
@@ -43,15 +45,18 @@ async fn get_extensions(
Extension(app): Extension<Arc<AppState>>,
Query(params): Query<GetExtensionsParams>,
) -> Result<Json<GetExtensionsResponse>> {
let extensions = app
.db
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
.await?;
let extension_ids = params
.ids
.as_ref()
.map(|s| s.split(',').map(|s| s.trim()).collect::<Vec<_>>());
if let Some(query) = params.filter.as_deref() {
let count = extensions.len();
tracing::info!(query, count, "extension_search")
}
let extensions = if let Some(extension_ids) = extension_ids {
app.db.get_extensions_by_ids(&extension_ids, None).await?
} else {
app.db
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
.await?
};
Ok(Json(GetExtensionsResponse { data: extensions }))
}

View File

@@ -4462,7 +4462,6 @@ async fn complete_with_open_ai(
tool_calls: choice
.delta
.tool_calls
.unwrap_or_default()
.into_iter()
.map(|delta| proto::ToolCallDelta {
index: delta.index as u32,

View File

@@ -30,13 +30,17 @@ test-support = [
[dependencies]
anyhow.workspace = true
auto_update.workspace = true
call.workspace = true
channel.workspace = true
client.workspace = true
collections.workspace = true
command_palette.workspace = true
db.workspace = true
editor.workspace = true
emojis.workspace = true
extensions_ui.workspace = true
feedback.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
@@ -47,6 +51,8 @@ notifications.workspace = true
parking_lot.workspace = true
picker.workspace = true
project.workspace = true
recent_projects.workspace = true
dev_server_projects.workspace = true
release_channel.workspace = true
rich_text.workspace = true
rpc.workspace = true
@@ -58,13 +64,14 @@ settings.workspace = true
smallvec.workspace = true
story = { workspace = true, optional = true }
theme.workspace = true
theme_selector.workspace = true
time_format.workspace = true
time.workspace = true
title_bar.workspace = true
ui.workspace = true
util.workspace = true
vcs_menu.workspace = true
workspace.workspace = true
zed_actions.workspace = true
[dev-dependencies]
call = { workspace = true, features = ["test-support"] }

View File

@@ -2,7 +2,10 @@ mod channel_modal;
mod contact_finder;
use self::channel_modal::ChannelModal;
use crate::{channel_view::ChannelView, chat_panel::ChatPanel, CollaborationPanelSettings};
use crate::{
channel_view::ChannelView, chat_panel::ChatPanel, face_pile::FacePile,
CollaborationPanelSettings,
};
use call::ActiveCall;
use channel::{Channel, ChannelEvent, ChannelStore};
use client::{ChannelId, Client, Contact, ProjectId, User, UserStore};
@@ -31,8 +34,7 @@ use std::{mem, sync::Arc};
use theme::{ActiveTheme, ThemeSettings};
use ui::{
prelude::*, tooltip_container, Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu,
Facepile, Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem,
Tooltip,
Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem, Tooltip,
};
use util::{maybe, ResultExt, TryFutureExt};
use workspace::{
@@ -2540,7 +2542,7 @@ impl CollabPanel {
None
} else {
let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
let result = Facepile::new(
let result = FacePile::new(
participants
.iter()
.map(|user| Avatar::new(user.avatar_uri.clone()).into_any_element())

View File

@@ -1,28 +1,21 @@
mod call_controls;
mod collab;
mod platforms;
mod window_controls;
use crate::platforms::{platform_linux, platform_mac, platform_windows};
use crate::face_pile::FacePile;
use auto_update::AutoUpdateStatus;
use call::{ActiveCall, ParticipantLocation};
use client::{Client, UserStore};
use collab::render_color_ribbon;
use call::{ActiveCall, ParticipantLocation, Room};
use client::{proto::PeerId, Client, User, UserStore};
use gpui::{
actions, div, px, Action, AnyElement, AppContext, Decorations, Element, InteractiveElement,
Interactivity, IntoElement, Model, MouseButton, ParentElement, Render, Stateful,
actions, canvas, div, point, px, Action, AnyElement, AppContext, Element, Hsla,
InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView,
};
use project::{Project, RepositoryEntry};
use recent_projects::RecentProjects;
use rpc::proto::DevServerStatus;
use rpc::proto::{self, DevServerStatus};
use settings::Settings;
use smallvec::SmallVec;
use std::sync::Arc;
use theme::{ActiveTheme, ThemeSettings};
use ui::{
h_flex, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton,
IconName, Indicator, PopoverMenu, TintColor, Tooltip,
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
};
use util::ResultExt;
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
@@ -44,389 +37,333 @@ actions!(
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, cx| {
let item = cx.new_view(|cx| TitleBar::new("title-bar", workspace, cx));
workspace.set_titlebar_item(item.into(), cx)
let titlebar_item = cx.new_view(|cx| CollabTitlebarItem::new(workspace, cx));
workspace.set_titlebar_item(titlebar_item.into(), cx)
})
.detach();
}
pub struct TitleBar {
platform_style: PlatformStyle,
content: Stateful<Div>,
children: SmallVec<[AnyElement; 2]>,
pub struct CollabTitlebarItem {
project: Model<Project>,
user_store: Model<UserStore>,
client: Arc<Client>,
workspace: WeakView<Workspace>,
should_move: bool,
_subscriptions: Vec<Subscription>,
}
impl Render for TitleBar {
impl Render for CollabTitlebarItem {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let room = ActiveCall::global(cx).read(cx).room().cloned();
let current_user = self.user_store.read(cx).current_user();
let client = self.client.clone();
let project_id = self.project.read(cx).remote_id();
let workspace = self.workspace.upgrade();
let close_action = Box::new(workspace::CloseWindow);
let platform_supported = cfg!(target_os = "macos");
let height = Self::height(cx);
let supported_controls = cx.window_controls();
let decorations = cx.window_decorations();
h_flex()
.id("titlebar")
.w_full()
.pt(Self::top_padding(cx))
.h(height + Self::top_padding(cx))
.map(|this| {
if cx.is_fullscreen() {
this.pl_2()
} else if self.platform_style == PlatformStyle::Mac {
this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
} else {
this.pl_2()
}
TitleBar::new("collab-titlebar", Box::new(workspace::CloseWindow))
// note: on windows titlebar behaviour is handled by the platform implementation
.when(cfg!(not(windows)), |this| {
this.on_click(|event, cx| {
if event.up.click_count == 2 {
cx.zoom_window();
}
})
})
.map(|el| {
match decorations {
Decorations::Server => el,
Decorations::Client { tiling, .. } => el
.when(!(tiling.top || tiling.right), |el| {
el.rounded_tr(theme::CLIENT_SIDE_DECORATION_ROUNDING)
})
.when(!(tiling.top || tiling.left), |el| el.rounded_tl(theme::CLIENT_SIDE_DECORATION_ROUNDING))
}
})
.bg(cx.theme().colors().title_bar_background)
.content_stretch()
// left side
.child(
div()
.id("titlebar-content")
.flex()
.flex_row()
.justify_between()
h_flex()
.gap_1()
.children(self.render_application_menu(cx))
.children(self.render_project_host(cx))
.child(self.render_project_name(cx))
.children(self.render_project_branch(cx))
.on_mouse_move(|_, cx| cx.stop_propagation()),
)
.child(
h_flex()
.id("collaborator-list")
.w_full()
// note: on windows titlebar behaviour is handled by the platform implementation
.when(cfg!(not(windows)), |this| {
this.on_click(|event, cx| {
if event.up.click_count == 2 {
cx.zoom_window();
}
})
})
// left side
.child(
h_flex()
.gap_1()
.children(self.render_application_menu(cx))
.children(self.render_project_host(cx))
.child(self.render_project_name(cx))
.children(self.render_project_branch(cx))
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
)
.child(
h_flex()
.id("collaborator-list")
.w_full()
.gap_1()
.overflow_x_scroll()
.when_some(
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|this, ((current_user, peer_id), room)| {
let player_colors = cx.theme().players();
let room = room.read(cx);
let mut remote_participants =
room.remote_participants().values().collect::<Vec<_>>();
remote_participants.sort_by_key(|p| p.participant_index.0);
.gap_1()
.overflow_x_scroll()
.when_some(
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|this, ((current_user, peer_id), room)| {
let player_colors = cx.theme().players();
let room = room.read(cx);
let mut remote_participants =
room.remote_participants().values().collect::<Vec<_>>();
remote_participants.sort_by_key(|p| p.participant_index.0);
let current_user_face_pile = self.render_collaborator(
&current_user,
peer_id,
true,
room.is_speaking(),
room.is_muted(),
None,
&room,
project_id,
&current_user,
cx,
);
let current_user_face_pile = self.render_collaborator(
&current_user,
peer_id,
true,
room.is_speaking(),
room.is_muted(),
None,
&room,
project_id,
&current_user,
cx,
);
this.children(current_user_face_pile.map(|face_pile| {
v_flex()
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.child(face_pile)
.child(render_color_ribbon(player_colors.local().cursor))
}))
.children(
remote_participants.iter().filter_map(|collaborator| {
let player_color = player_colors
.color_for_participant(collaborator.participant_index.0);
let is_following = workspace
.as_ref()?
.read(cx)
.is_being_followed(collaborator.peer_id);
let is_present = project_id.map_or(false, |project_id| {
collaborator.location
== ParticipantLocation::SharedProject { project_id }
});
this.children(current_user_face_pile.map(|face_pile| {
v_flex()
.on_mouse_move(|_, cx| cx.stop_propagation())
.child(face_pile)
.child(render_color_ribbon(player_colors.local().cursor))
}))
.children(
remote_participants.iter().filter_map(|collaborator| {
let player_color = player_colors
.color_for_participant(collaborator.participant_index.0);
let is_following = workspace
.as_ref()?
.read(cx)
.is_being_followed(collaborator.peer_id);
let is_present = project_id.map_or(false, |project_id| {
collaborator.location
== ParticipantLocation::SharedProject { project_id }
});
let facepile = self.render_collaborator(
&collaborator.user,
collaborator.peer_id,
is_present,
collaborator.speaking,
collaborator.muted,
is_following.then_some(player_color.selection),
&room,
project_id,
&current_user,
cx,
)?;
let face_pile = self.render_collaborator(
&collaborator.user,
collaborator.peer_id,
is_present,
collaborator.speaking,
collaborator.muted,
is_following.then_some(player_color.selection),
&room,
project_id,
&current_user,
cx,
)?;
Some(
v_flex()
.id(("collaborator", collaborator.user.id))
.child(facepile)
.child(render_color_ribbon(player_color.cursor))
.cursor_pointer()
.on_click({
let peer_id = collaborator.peer_id;
cx.listener(move |this, _, cx| {
this.workspace
.update(cx, |workspace, cx| {
workspace.follow(peer_id, cx);
})
.ok();
})
Some(
v_flex()
.id(("collaborator", collaborator.user.id))
.child(face_pile)
.child(render_color_ribbon(player_color.cursor))
.cursor_pointer()
.on_click({
let peer_id = collaborator.peer_id;
cx.listener(move |this, _, cx| {
this.workspace
.update(cx, |workspace, cx| {
workspace.follow(peer_id, cx);
})
.tooltip({
let login = collaborator.user.github_login.clone();
move |cx| {
Tooltip::text(format!("Follow {login}"), cx)
}
}),
)
}),
)
},
),
)
// right side
.child(
h_flex()
.gap_1()
.pr_1()
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.when_some(room, |this, room| {
let room = room.read(cx);
let project = self.project.read(cx);
let is_local = project.is_local();
let is_dev_server_project = project.dev_server_project_id().is_some();
let is_shared = (is_local || is_dev_server_project) && project.is_shared();
let is_muted = room.is_muted();
let is_deafened = room.is_deafened().unwrap_or(false);
let is_screen_sharing = room.is_screen_sharing();
let can_use_microphone = room.can_use_microphone();
let can_share_projects = room.can_share_projects();
this.when(
(is_local || is_dev_server_project) && can_share_projects,
|this| {
this.child(
Button::new(
"toggle_sharing",
if is_shared { "Unshare" } else { "Share" },
)
.tooltip(move |cx| {
Tooltip::text(
if is_shared {
"Stop sharing project with call participants"
} else {
"Share project with call participants"
},
cx,
)
.ok();
})
.style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.selected(is_shared)
.label_size(LabelSize::Small)
.on_click(cx.listener(
move |this, _, cx| {
if is_shared {
this.unshare_project(&Default::default(), cx);
} else {
this.share_project(&Default::default(), cx);
}
},
)),
)
},
)
.child(
div()
.child(
IconButton::new("leave-call", ui::IconName::Exit)
.style(ButtonStyle::Subtle)
.tooltip(|cx| Tooltip::text("Leave call", cx))
.icon_size(IconSize::Small)
.on_click(move |_, cx| {
ActiveCall::global(cx)
.update(cx, |call, cx| call.hang_up(cx))
.detach_and_log_err(cx);
}),
)
.pr_2(),
)
.when(can_use_microphone, |this| {
this.child(
IconButton::new(
"mute-microphone",
if is_muted {
ui::IconName::MicMute
} else {
ui::IconName::Mic
},
)
.tooltip(move |cx| {
Tooltip::text(
if !platform_supported {
"Cannot share microphone"
} else if is_muted {
"Unmute microphone"
} else {
"Mute microphone"
},
cx,
)
})
.style(ButtonStyle::Subtle)
.icon_size(IconSize::Small)
.selected(platform_supported && is_muted)
.disabled(!platform_supported)
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
.on_click(move |_, cx| {
call_controls::toggle_mute(&Default::default(), cx);
.tooltip({
let login = collaborator.user.github_login.clone();
move |cx| {
Tooltip::text(format!("Follow {login}"), cx)
}
}),
)
})
.child(
IconButton::new(
"mute-sound",
if is_deafened {
ui::IconName::AudioOff
} else {
ui::IconName::AudioOn
},
)
.style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
.icon_size(IconSize::Small)
.selected(is_deafened)
.disabled(!platform_supported)
.tooltip(move |cx| {
if !platform_supported {
Tooltip::text("Cannot share microphone", cx)
} else if can_use_microphone {
Tooltip::with_meta(
"Deafen Audio",
None,
"Mic will be muted",
cx,
)
} else {
Tooltip::text("Deafen Audio", cx)
}
})
.on_click(move |_, cx| {
call_controls::toggle_deafen(&Default::default(), cx)
}),
)
.when(can_share_projects, |this| {
this.child(
IconButton::new("screen-share", ui::IconName::Screen)
.style(ButtonStyle::Subtle)
.icon_size(IconSize::Small)
.selected(is_screen_sharing)
.disabled(!platform_supported)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.tooltip(move |cx| {
Tooltip::text(
if !platform_supported {
"Cannot share screen"
} else if is_screen_sharing {
"Stop Sharing Screen"
} else {
"Share Screen"
},
cx,
)
})
.on_click(move |_, cx| {
call_controls::toggle_screen_sharing(&Default::default(), cx)
}),
}),
)
},
),
)
// right side
.child(
h_flex()
.gap_1()
.pr_1()
.on_mouse_move(|_, cx| cx.stop_propagation())
.when_some(room, |this, room| {
let room = room.read(cx);
let project = self.project.read(cx);
let is_local = project.is_local();
let is_dev_server_project = project.dev_server_project_id().is_some();
let is_shared = (is_local || is_dev_server_project) && project.is_shared();
let is_muted = room.is_muted();
let is_deafened = room.is_deafened().unwrap_or(false);
let is_screen_sharing = room.is_screen_sharing();
let can_use_microphone = room.can_use_microphone();
let can_share_projects = room.can_share_projects();
this.when(
(is_local || is_dev_server_project) && can_share_projects,
|this| {
this.child(
Button::new(
"toggle_sharing",
if is_shared { "Unshare" } else { "Share" },
)
.tooltip(move |cx| {
Tooltip::text(
if is_shared {
"Stop sharing project with call participants"
} else {
"Share project with call participants"
},
cx,
)
})
.child(div().pr_2())
})
.map(|el| {
let status = self.client.status();
let status = &*status.borrow();
if matches!(status, client::Status::Connected { .. }) {
el.child(self.render_user_menu_button(cx))
} else {
el.children(self.render_connection_status(status, cx))
.child(self.render_sign_in_button(cx))
.child(self.render_user_menu_button(cx))
}
}),
)
).when(
self.platform_style == PlatformStyle::Windows && !cx.is_fullscreen(),
|title_bar| title_bar.child(platform_windows::WindowsWindowControls::new(height)),
).when(
self.platform_style == PlatformStyle::Linux
&& !cx.is_fullscreen()
&& matches!(decorations, Decorations::Client { .. }),
|title_bar| {
title_bar
.child(platform_linux::LinuxWindowControls::new(close_action))
.when(supported_controls.window_menu, |titlebar| {
titlebar.on_mouse_down(gpui::MouseButton::Right, move |ev, cx| {
cx.show_window_menu(ev.position)
})
})
.on_mouse_move(cx.listener(move |this, _ev, cx| {
if this.should_move {
this.should_move = false;
cx.start_window_move();
.style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.selected(is_shared)
.label_size(LabelSize::Small)
.on_click(cx.listener(
move |this, _, cx| {
if is_shared {
this.unshare_project(&Default::default(), cx);
} else {
this.share_project(&Default::default(), cx);
}
}))
.on_mouse_down_out(cx.listener(move |this, _ev, _cx| {
this.should_move = false;
}))
.on_mouse_down(gpui::MouseButton::Left, cx.listener(move |this, _ev, _cx| {
this.should_move = true;
}))
},
)
},
)),
)
},
)
.child(
div()
.child(
IconButton::new("leave-call", ui::IconName::Exit)
.style(ButtonStyle::Subtle)
.tooltip(|cx| Tooltip::text("Leave call", cx))
.icon_size(IconSize::Small)
.on_click(move |_, cx| {
ActiveCall::global(cx)
.update(cx, |call, cx| call.hang_up(cx))
.detach_and_log_err(cx);
}),
)
.pr_2(),
)
.when(can_use_microphone, |this| {
this.child(
IconButton::new(
"mute-microphone",
if is_muted {
ui::IconName::MicMute
} else {
ui::IconName::Mic
},
)
.tooltip(move |cx| {
Tooltip::text(
if !platform_supported {
"Cannot share microphone"
} else if is_muted {
"Unmute microphone"
} else {
"Mute microphone"
},
cx,
)
})
.style(ButtonStyle::Subtle)
.icon_size(IconSize::Small)
.selected(platform_supported && is_muted)
.disabled(!platform_supported)
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
)
})
.child(
IconButton::new(
"mute-sound",
if is_deafened {
ui::IconName::AudioOff
} else {
ui::IconName::AudioOn
},
)
.style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
.icon_size(IconSize::Small)
.selected(is_deafened)
.disabled(!platform_supported)
.tooltip(move |cx| {
if !platform_supported {
Tooltip::text("Cannot share microphone", cx)
} else if can_use_microphone {
Tooltip::with_meta(
"Deafen Audio",
None,
"Mic will be muted",
cx,
)
} else {
Tooltip::text("Deafen Audio", cx)
}
})
.on_click(move |_, cx| crate::toggle_deafen(&Default::default(), cx)),
)
.when(can_share_projects, |this| {
this.child(
IconButton::new("screen-share", ui::IconName::Screen)
.style(ButtonStyle::Subtle)
.icon_size(IconSize::Small)
.selected(is_screen_sharing)
.disabled(!platform_supported)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.tooltip(move |cx| {
Tooltip::text(
if !platform_supported {
"Cannot share screen"
} else if is_screen_sharing {
"Stop Sharing Screen"
} else {
"Share Screen"
},
cx,
)
})
.on_click(move |_, cx| {
crate::toggle_screen_sharing(&Default::default(), cx)
}),
)
})
.child(div().pr_2())
})
.map(|el| {
let status = self.client.status();
let status = &*status.borrow();
if matches!(status, client::Status::Connected { .. }) {
el.child(self.render_user_menu_button(cx))
} else {
el.children(self.render_connection_status(status, cx))
.child(self.render_sign_in_button(cx))
.child(self.render_user_menu_button(cx))
}
}),
)
}
}
impl TitleBar {
pub fn new(
id: impl Into<ElementId>,
workspace: &Workspace,
cx: &mut ViewContext<Self>,
) -> Self {
fn render_color_ribbon(color: Hsla) -> impl Element {
canvas(
move |_, _| {},
move |bounds, _, cx| {
let height = bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(height.0 / 2.0);
let mut path = Path::new(bounds.lower_left());
path.curve_to(
bounds.origin + point(horizontal_offset, vertical_offset),
bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
bounds.lower_right(),
bounds.upper_right() + point(px(0.0), vertical_offset),
);
path.line_to(bounds.lower_left());
cx.paint_path(path, color);
},
)
.h_1()
.w_full()
}
impl CollabTitlebarItem {
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
let project = workspace.project().clone();
let user_store = workspace.app_state().user_store.clone();
let client = workspace.app_state().client.clone();
@@ -443,11 +380,7 @@ impl TitleBar {
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
Self {
platform_style: PlatformStyle::platform(),
content: div().id(id.into()),
children: SmallVec::new(),
workspace: workspace.weak_handle(),
should_move: false,
project,
user_store,
client,
@@ -455,45 +388,6 @@ impl TitleBar {
}
}
#[cfg(not(target_os = "windows"))]
pub fn height(cx: &mut WindowContext) -> Pixels {
(1.75 * cx.rem_size()).max(px(34.))
}
#[cfg(target_os = "windows")]
pub fn height(_cx: &mut WindowContext) -> Pixels {
// todo(windows) instead of hard coded size report the actual size to the Windows platform API
px(32.)
}
#[cfg(not(target_os = "windows"))]
fn top_padding(_cx: &WindowContext) -> Pixels {
px(0.)
}
#[cfg(target_os = "windows")]
fn top_padding(cx: &WindowContext) -> Pixels {
use windows::Win32::UI::{
HiDpi::GetSystemMetricsForDpi,
WindowsAndMessaging::{SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI},
};
// This top padding is not dependent on the title bar style and is instead a quirk of maximized windows on Windows:
// https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
if cx.is_maximized() {
px((padding * 2) as f32)
} else {
px(0.)
}
}
/// Sets the platform style.
pub fn platform_style(mut self, style: PlatformStyle) -> Self {
self.platform_style = style;
self
}
pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
cfg!(not(target_os = "macos")).then(|| {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
@@ -818,6 +712,97 @@ impl TitleBar {
)
}
#[allow(clippy::too_many_arguments)]
fn render_collaborator(
&self,
user: &Arc<User>,
peer_id: PeerId,
is_present: bool,
is_speaking: bool,
is_muted: bool,
leader_selection_color: Option<Hsla>,
room: &Room,
project_id: Option<u64>,
current_user: &Arc<User>,
cx: &ViewContext<Self>,
) -> Option<Div> {
if room.role_for_user(user.id) == Some(proto::ChannelRole::Guest) {
return None;
}
const FACEPILE_LIMIT: usize = 3;
let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
let extra_count = followers.len().saturating_sub(FACEPILE_LIMIT);
Some(
div()
.m_0p5()
.p_0p5()
// When the collaborator is not followed, still draw this wrapper div, but leave
// it transparent, so that it does not shift the layout when following.
.when_some(leader_selection_color, |div, color| {
div.rounded_md().bg(color)
})
.child(
FacePile::empty()
.child(
Avatar::new(user.avatar_uri.clone())
.grayscale(!is_present)
.border_color(if is_speaking {
cx.theme().status().info
} else {
// We draw the border in a transparent color rather to avoid
// the layout shift that would come with adding/removing the border.
gpui::transparent_black()
})
.when(is_muted, |avatar| {
avatar.indicator(
AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted)
.tooltip({
let github_login = user.github_login.clone();
move |cx| {
Tooltip::text(
format!("{} is muted", github_login),
cx,
)
}
}),
)
}),
)
.children(followers.iter().take(FACEPILE_LIMIT).filter_map(
|follower_peer_id| {
let follower = room
.remote_participants()
.values()
.find_map(|p| {
(p.peer_id == *follower_peer_id).then_some(&p.user)
})
.or_else(|| {
(self.client.peer_id() == Some(*follower_peer_id))
.then_some(current_user)
})?
.clone();
Some(div().mt(-px(4.)).child(
Avatar::new(follower.avatar_uri.clone()).size(rems(0.75)),
))
},
))
.children(if extra_count > 0 {
Some(
div()
.ml_1()
.child(Label::new(format!("+{extra_count}")))
.into_any_element(),
)
} else {
None
}),
),
)
}
fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
if cx.is_window_active() {
ActiveCall::global(cx)
@@ -975,17 +960,3 @@ impl TitleBar {
}
}
}
impl InteractiveElement for TitleBar {
fn interactivity(&mut self) -> &mut Interactivity {
self.content.interactivity()
}
}
impl StatefulInteractiveElement for TitleBar {}
impl ParentElement for TitleBar {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements)
}
}

View File

@@ -1,16 +1,20 @@
pub mod channel_view;
pub mod chat_panel;
pub mod collab_panel;
mod collab_titlebar_item;
mod face_pile;
pub mod notification_panel;
pub mod notifications;
mod panel_settings;
use std::{rc::Rc, sync::Arc};
use call::{report_call_event_for_room, ActiveCall};
pub use collab_panel::CollabPanel;
pub use collab_titlebar_item::CollabTitlebarItem;
use gpui::{
point, AppContext, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds,
WindowDecorations, WindowKind, WindowOptions,
actions, point, AppContext, Pixels, PlatformDisplay, Size, Task, WindowBackgroundAppearance,
WindowBounds, WindowContext, WindowKind, WindowOptions,
};
use panel_settings::MessageEditorSettings;
pub use panel_settings::{
@@ -19,7 +23,12 @@ pub use panel_settings::{
use release_channel::ReleaseChannel;
use settings::Settings;
use ui::px;
use workspace::AppState;
use workspace::{notifications::DetachAndPromptErr, AppState};
actions!(
collab,
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
);
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
CollaborationPanelSettings::register(cx);
@@ -27,13 +36,63 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
NotificationPanelSettings::register(cx);
MessageEditorSettings::register(cx);
vcs_menu::init(cx);
collab_titlebar_item::init(cx);
collab_panel::init(cx);
channel_view::init(cx);
chat_panel::init(cx);
collab_panel::init(cx);
notification_panel::init(cx);
notifications::init(&app_state, cx);
title_bar::init(cx);
vcs_menu::init(cx);
}
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut WindowContext) {
let call = ActiveCall::global(cx).read(cx);
if let Some(room) = call.room().cloned() {
let client = call.client();
let toggle_screen_sharing = room.update(cx, |room, cx| {
if room.is_screen_sharing() {
report_call_event_for_room(
"disable screen share",
room.id(),
room.channel_id(),
&client,
);
Task::ready(room.unshare_screen(cx))
} else {
report_call_event_for_room(
"enable screen share",
room.id(),
room.channel_id(),
&client,
);
room.share_screen(cx)
}
});
toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", cx, |e, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e)));
}
}
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
let call = ActiveCall::global(cx).read(cx);
if let Some(room) = call.room().cloned() {
let client = call.client();
room.update(cx, |room, cx| {
let operation = if room.is_muted() {
"enable microphone"
} else {
"disable microphone"
};
report_call_event_for_room(operation, room.id(), room.channel_id(), &client);
room.toggle_mute(cx)
});
}
}
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
room.update(cx, |room, cx| room.toggle_deafen(cx));
}
}
fn notification_window_options(
@@ -63,9 +122,8 @@ fn notification_window_options(
kind: WindowKind::PopUp,
is_movable: false,
display_id: Some(screen.id()),
window_background: WindowBackgroundAppearance::Transparent,
window_background: WindowBackgroundAppearance::default(),
app_id: Some(app_id.to_owned()),
window_min_size: None,
window_decorations: Some(WindowDecorations::Client),
window_min_size: Size::default(),
}
}

View File

@@ -1,19 +1,14 @@
use crate::prelude::*;
use gpui::AnyElement;
use smallvec::SmallVec;
use ui::prelude::*;
/// A facepile is a collection of faces stacked horizontally
/// always with the leftmost face on top and descending in z-index
///
/// Facepiles are used to display a group of people or things,
/// such as a list of participants in a collaboration session.
#[derive(IntoElement)]
pub struct Facepile {
pub struct FacePile {
base: Div,
faces: SmallVec<[AnyElement; 2]>,
}
impl Facepile {
impl FacePile {
pub fn empty() -> Self {
Self::new(SmallVec::new())
}
@@ -23,7 +18,7 @@ impl Facepile {
}
}
impl RenderOnce for Facepile {
impl RenderOnce for FacePile {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
// Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
self.base
@@ -41,13 +36,13 @@ impl RenderOnce for Facepile {
}
}
impl ParentElement for Facepile {
impl ParentElement for FacePile {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.faces.extend(elements);
}
}
impl Styled for Facepile {
impl Styled for FacePile {
fn style(&mut self) -> &mut gpui::StyleRefinement {
self.base.style()
}

View File

@@ -102,9 +102,6 @@ impl Render for ProjectDiagnosticsEditor {
div()
.track_focus(&self.focus_handle)
.when(self.path_states.is_empty(), |el| {
el.key_context("EmptyPane")
})
.size_full()
.on_action(cx.listener(Self::toggle_warnings))
.child(child)

View File

@@ -720,7 +720,8 @@ impl DisplaySnapshot {
if let Some(severity) = chunk.diagnostic_severity {
// Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
let diagnostic_color =
super::diagnostic_style(severity, true, &editor_style.status);
diagnostic_highlight.underline = Some(UnderlineStyle {
color: Some(diagnostic_color),
thickness: 1.0.into(),
@@ -956,18 +957,16 @@ impl DisplaySnapshot {
return false;
}
(buffer_row.0 + 1..=max_row.0)
.find_map(|next_row| {
let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
if next_line_indent.raw_len() > line_indent.raw_len() {
Some(true)
} else if !next_line_indent.is_line_blank() {
Some(false)
} else {
None
}
})
.unwrap_or(false)
for next_row in (buffer_row.0 + 1)..=max_row.0 {
let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
if next_line_indent.raw_len() > line_indent.raw_len() {
return true;
} else if !next_line_indent.is_line_blank() {
break;
}
}
false
}
pub fn foldable_range(

View File

@@ -1221,7 +1221,7 @@ mod tests {
use super::*;
use crate::display_map::inlay_map::InlayMap;
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
use gpui::{div, font, px, Element};
use gpui::{div, font, px, AssetSource, Element};
use multi_buffer::MultiBuffer;
use rand::prelude::*;
use settings::SettingsStore;
@@ -2014,7 +2014,12 @@ mod tests {
let settings = SettingsStore::test(cx);
cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx);
assets::Assets.load_test_fonts(cx);
cx.text_system()
.add_fonts(vec![assets::Assets
.load("fonts/zed-mono/zed-mono-extended.ttf")
.unwrap()
.unwrap()])
.unwrap();
}
impl TransformBlock {

View File

@@ -2131,7 +2131,7 @@ impl Editor {
self.refresh_inline_completion(false, cx);
}
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&str> {
self.placeholder_text.as_deref()
}
@@ -2914,9 +2914,6 @@ impl Editor {
let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
let end_offset = start_offset + end_difference;
let start_offset = start_offset + start_difference;
if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
continue;
}
let start = buffer_snapshot.anchor_after(start_offset);
let end = buffer_snapshot.anchor_after(end_offset);
linked_edits
@@ -3123,24 +3120,14 @@ impl Editor {
let anchor = snapshot.anchor_after(selection.end);
if !self.linked_edit_ranges.is_empty() {
let start_anchor = snapshot.anchor_before(selection.start);
let is_word_char = text.chars().next().map_or(true, |char| {
let scope = snapshot.language_scope_at(start_anchor.to_offset(&snapshot));
let kind = char_kind(&scope, char);
kind == CharKind::Word
});
if is_word_char {
if let Some(ranges) = self
.linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
{
for (buffer, edits) in ranges {
linked_edits
.entry(buffer.clone())
.or_default()
.extend(edits.into_iter().map(|range| (range, text.clone())));
}
if let Some(ranges) =
self.linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
{
for (buffer, edits) in ranges {
linked_edits
.entry(buffer.clone())
.or_default()
.extend(edits.into_iter().map(|range| (range, text.clone())));
}
}
}
@@ -6822,16 +6809,6 @@ impl Editor {
return;
}
if self
.context_menu
.write()
.as_mut()
.map(|menu| menu.select_first(self.project.as_ref(), cx))
.unwrap_or(false)
{
return;
}
if matches!(self.mode, EditorMode::SingleLine { .. }) {
cx.propagate();
return;
@@ -8479,14 +8456,13 @@ impl Editor {
runnable: &mut Runnable,
cx: &WindowContext<'_>,
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
let (worktree_id, file) = project
let (inventory, worktree_id) = project.read_with(cx, |project, cx| {
let worktree_id = project
.buffer_for_id(runnable.buffer)
.and_then(|buffer| buffer.read(cx).file())
.map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
.unzip();
.map(|file| WorktreeId::from_usize(file.worktree_id()));
(project.task_inventory().clone(), worktree_id, file)
(project.task_inventory().clone(), worktree_id)
});
let inventory = inventory.read(cx);
@@ -8496,12 +8472,7 @@ impl Editor {
.flat_map(|tag| {
let tag = tag.0.clone();
inventory
.list_tasks(
file.clone(),
Some(runnable.language.clone()),
worktree_id,
cx,
)
.list_tasks(Some(runnable.language.clone()), worktree_id)
.into_iter()
.filter(move |(_, template)| {
template.tags.iter().any(|source_tag| source_tag == &tag)
@@ -8842,7 +8813,13 @@ impl Editor {
let display_point = initial_point.to_display_point(snapshot);
let mut hunks = hunks
.map(|hunk| diff_hunk_to_display(&hunk, &snapshot))
.filter(|hunk| is_wrapped || !hunk.contains_display_row(display_point.row()))
.filter(|hunk| {
if is_wrapped {
true
} else {
!hunk.contains_display_row(display_point.row())
}
})
.dedup();
if let Some(hunk) = hunks.next() {
@@ -12413,7 +12390,6 @@ impl ViewInputHandler for Editor {
let font_id = cx.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let line_height = style.text.line_height_in_pixels(cx.rem_size());
let em_width = cx
.text_system()
.typographic_bounds(font_id, font_size, 'm')
@@ -12541,7 +12517,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
let group_id: SharedString = cx.block_id.to_string().into();
let mut text_style = cx.text_style().clone();
text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
text_style.color = diagnostic_style(diagnostic.severity, true, cx.theme().status());
let theme_settings = ThemeSettings::get_global(cx);
text_style.font_family = theme_settings.buffer_font.family.clone();
text_style.font_style = theme_settings.buffer_font.style;
@@ -12637,19 +12613,25 @@ pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, V
prev_offset = ix + 1;
if in_code_block {
code_ranges.push(prev_len..text_without_backticks.len());
in_code_block = false;
} else {
in_code_block = true;
}
in_code_block = !in_code_block;
}
(text_without_backticks.into(), code_ranges)
}
fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
match severity {
DiagnosticSeverity::ERROR => colors.error,
DiagnosticSeverity::WARNING => colors.warning,
DiagnosticSeverity::INFORMATION => colors.info,
DiagnosticSeverity::HINT => colors.info,
fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
match (severity, valid) {
(DiagnosticSeverity::ERROR, true) => colors.error,
(DiagnosticSeverity::ERROR, false) => colors.error,
(DiagnosticSeverity::WARNING, true) => colors.warning,
(DiagnosticSeverity::WARNING, false) => colors.warning,
(DiagnosticSeverity::INFORMATION, true) => colors.info,
(DiagnosticSeverity::INFORMATION, false) => colors.info,
(DiagnosticSeverity::HINT, true) => colors.info,
(DiagnosticSeverity::HINT, false) => colors.info,
_ => colors.ignored,
}
}

View File

@@ -10,8 +10,8 @@ use crate::{
};
use futures::StreamExt;
use gpui::{
div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
WindowOptions,
div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
WindowBounds, WindowOptions,
};
use indoc::indoc;
use language::{
@@ -7019,73 +7019,6 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
apply_additional_edits.await.unwrap();
}
#[gpui::test]
async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.lsp
.handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
label: "first".into(),
..Default::default()
},
lsp::CompletionItem {
label: "last".into(),
..Default::default()
},
])))
});
cx.set_state("variableˇ");
cx.simulate_keystroke(".");
cx.executor().run_until_parked();
cx.update_editor(|editor, _| {
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
assert_eq!(
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
&["first", "last"]
);
} else {
panic!("expected completion menu to be open");
}
});
cx.update_editor(|editor, cx| {
editor.move_page_down(&MovePageDown::default(), cx);
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
assert!(
menu.selected_item == 1,
"expected PageDown to select the last item from the context menu"
);
} else {
panic!("expected completion menu to stay open after PageDown");
}
});
cx.update_editor(|editor, cx| {
editor.move_page_up(&MovePageUp::default(), cx);
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
assert!(
menu.selected_item == 0,
"expected PageUp to select the first item from the context menu"
);
} else {
panic!("expected completion menu to stay open after PageUp");
}
});
}
#[gpui::test]
async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -12556,7 +12489,12 @@ pub(crate) fn update_test_project_settings(
pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
_ = cx.update(|cx| {
assets::Assets.load_test_fonts(cx);
cx.text_system()
.add_fonts(vec![assets::Assets
.load("fonts/zed-mono/zed-mono-extended.ttf")
.unwrap()
.unwrap()])
.unwrap();
let store = SettingsStore::test(cx);
cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx);

View File

@@ -1119,12 +1119,11 @@ impl EditorElement {
ScrollBeyondLastLine::Off => 1.0,
ScrollBeyondLastLine::VerticalScrollMargin => 1.0 + settings.vertical_scroll_margin,
};
let total_rows =
(snapshot.max_point().row().as_f32() + scroll_beyond_last_line).max(rows_per_page);
let total_rows = snapshot.max_point().row().as_f32() + scroll_beyond_last_line;
let height = bounds.size.height;
let px_per_row = height / total_rows;
let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.);
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.0);
Some(ScrollbarLayout {
hitbox: cx.insert_hitbox(track_bounds, false),
@@ -4677,17 +4676,17 @@ impl Element for EditorElement {
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
let height_in_lines = bounds.size.height / line_height;
let max_row = snapshot.max_point().row().as_f32();
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
(max_row - height_in_lines + 1.).max(0.)
(snapshot.max_point().row().as_f32() - height_in_lines + 1.).max(0.)
} else {
let settings = EditorSettings::get_global(cx);
let max_row = snapshot.max_point().row().as_f32();
match settings.scroll_beyond_last_line {
ScrollBeyondLastLine::OnePage => max_row,
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.).max(0.),
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.0).max(0.0),
ScrollBeyondLastLine::VerticalScrollMargin => {
(max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
.max(0.)
(max_row - height_in_lines + 1.0 + settings.vertical_scroll_margin)
.max(0.0)
}
}
};

View File

@@ -1102,35 +1102,6 @@ impl SearchableItem for Editor {
});
}
}
fn replace_all(
&mut self,
matches: &mut dyn Iterator<Item = &Self::Match>,
query: &SearchQuery,
cx: &mut ViewContext<Self>,
) {
let text = self.buffer.read(cx);
let text = text.snapshot(cx);
let mut edits = vec![];
for m in matches {
let text = text.text_for_range(m.clone()).collect::<Vec<_>>();
let text: Cow<_> = if text.len() == 1 {
text.first().cloned().unwrap().into()
} else {
let joined_chunks = text.join("");
joined_chunks.into()
};
if let Some(replacement) = query.replacement_for(&text) {
edits.push((m.clone(), Arc::from(&*replacement)));
}
}
if !edits.is_empty() {
self.transact(cx, |this, cx| {
this.edit(edits, cx);
});
}
}
fn match_index_for_direction(
&mut self,
matches: &[Range<Anchor>],

View File

@@ -203,24 +203,13 @@ impl ScrollManager {
let scroll_top = scroll_position.y;
let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
ScrollBeyondLastLine::OnePage => scroll_top,
ScrollBeyondLastLine::Off => {
if let Some(height_in_lines) = self.visible_line_count {
let max_row = map.max_point().row().0 as f32;
scroll_top.min(max_row - height_in_lines + 1.).max(0.)
} else {
scroll_top
}
}
ScrollBeyondLastLine::VerticalScrollMargin => {
if let Some(height_in_lines) = self.visible_line_count {
let max_row = map.max_point().row().0 as f32;
scroll_top
.min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
.max(0.)
} else {
scroll_top
}
}
ScrollBeyondLastLine::Off => scroll_top
.min((map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap() + 1.0),
ScrollBeyondLastLine::VerticalScrollMargin => scroll_top.min(
(map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap()
+ 1.0
+ self.vertical_scroll_margin,
),
};
let scroll_top_buffer_point =

View File

@@ -25,7 +25,7 @@ pub fn marked_display_snapshot(
let (unmarked_text, markers) = marked_text_offsets(text);
let font = Font {
family: "Zed Plex Mono".into(),
family: "Zed Mono".into(),
features: FontFeatures::default(),
weight: FontWeight::default(),
style: FontStyle::default(),

View File

@@ -10,7 +10,7 @@ use serde_json::json;
use crate::{Editor, ToPoint};
use collections::HashSet;
use futures::Future;
use gpui::{View, ViewContext, VisualTestContext};
use gpui::{AssetSource, View, ViewContext, VisualTestContext};
use indoc::indoc;
use language::{
point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,
@@ -39,7 +39,12 @@ impl EditorLspTestContext {
let app_state = cx.update(AppState::test);
cx.update(|cx| {
assets::Assets.load_test_fonts(cx);
cx.text_system()
.add_fonts(vec![assets::Assets
.load("fonts/zed-mono/zed-mono-extended.ttf")
.unwrap()
.unwrap()])
.unwrap();
language::init(cx);
crate::init(cx);
workspace::init(app_state.clone(), cx);

View File

@@ -28,7 +28,6 @@ fs.workspace = true
futures.workspace = true
gpui.workspace = true
http.workspace = true
indexed_docs.workspace = true
isahc.workspace = true
language.workspace = true
log.workspace = true

View File

@@ -1,57 +0,0 @@
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures::FutureExt;
use indexed_docs::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId};
use wasmtime_wasi::WasiView;
use crate::wasm_host::{WasmExtension, WasmHost};
pub struct ExtensionIndexedDocsProvider {
pub(crate) extension: WasmExtension,
pub(crate) host: Arc<WasmHost>,
pub(crate) id: ProviderId,
}
#[async_trait]
impl IndexedDocsProvider for ExtensionIndexedDocsProvider {
fn id(&self) -> ProviderId {
self.id.clone()
}
fn database_path(&self) -> PathBuf {
let mut database_path = self.host.work_dir.clone();
database_path.push(self.extension.manifest.id.as_ref());
database_path.push("docs");
database_path.push(format!("{}.0.mdb", self.id));
database_path
}
async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {
self.extension
.call({
let id = self.id.clone();
|extension, store| {
async move {
let database_resource = store.data_mut().table().push(database)?;
extension
.call_index_docs(
store,
id.as_ref(),
package.as_ref(),
database_resource,
)
.await?
.map_err(|err| anyhow!("{err:?}"))?;
anyhow::Ok(())
}
.boxed()
}
})
.await
}
}

View File

@@ -76,8 +76,6 @@ pub struct ExtensionManifest {
pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,
#[serde(default)]
pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
#[serde(default)]
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
}
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -139,9 +137,6 @@ pub struct SlashCommandManifestEntry {
pub requires_argument: bool,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct IndexedDocsProviderEntry {}
impl ExtensionManifest {
pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> {
let extension_name = extension_dir
@@ -205,6 +200,5 @@ fn manifest_from_old_manifest(
.collect(),
language_servers: Default::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
}
}

View File

@@ -1,5 +1,4 @@
pub mod extension_builder;
mod extension_indexed_docs_provider;
mod extension_lsp_adapter;
mod extension_manifest;
mod extension_settings;
@@ -9,7 +8,6 @@ mod wasm_host;
#[cfg(test)]
mod extension_store_test;
use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
use crate::extension_manifest::SchemaVersion;
use crate::extension_slash_command::ExtensionSlashCommand;
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
@@ -34,7 +32,6 @@ use gpui::{
WeakModel,
};
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
use indexed_docs::{IndexedDocsRegistry, ProviderId};
use language::{
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
};
@@ -114,7 +111,6 @@ pub struct ExtensionStore {
language_registry: Arc<LanguageRegistry>,
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
modified_extensions: HashSet<Arc<str>>,
wasm_host: Arc<WasmHost>,
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
@@ -192,7 +188,6 @@ pub fn init(
language_registry,
theme_registry,
SlashCommandRegistry::global(cx),
IndexedDocsRegistry::global(cx),
cx,
)
});
@@ -226,7 +221,6 @@ impl ExtensionStore {
language_registry: Arc<LanguageRegistry>,
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
cx: &mut ModelContext<Self>,
) -> Self {
let work_dir = extensions_dir.join("work");
@@ -258,7 +252,6 @@ impl ExtensionStore {
language_registry,
theme_registry,
slash_command_registry,
indexed_docs_registry,
reload_tx,
tasks: Vec::new(),
};
@@ -1199,18 +1192,7 @@ impl ExtensionStore {
false,
);
}
for (provider_id, _provider) in &manifest.indexed_docs_providers {
this.indexed_docs_registry.register_provider(Box::new(
ExtensionIndexedDocsProvider {
extension: wasm_extension.clone(),
host: this.wasm_host.clone(),
id: ProviderId(provider_id.clone()),
},
));
}
}
this.wasm_extensions.extend(wasm_extensions);
ThemeSettings::reload_current_theme(cx)
})

View File

@@ -12,7 +12,6 @@ use fs::{FakeFs, Fs, RealFs};
use futures::{io::BufReader, AsyncReadExt, StreamExt};
use gpui::{Context, SemanticVersion, TestAppContext};
use http::{FakeHttpClient, Response};
use indexed_docs::IndexedDocsRegistry;
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
use node_runtime::FakeNodeRuntime;
use parking_lot::Mutex;
@@ -159,7 +158,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
.collect(),
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
}),
dev: false,
},
@@ -184,7 +182,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
grammars: BTreeMap::default(),
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
}),
dev: false,
},
@@ -257,7 +254,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let slash_command_registry = SlashCommandRegistry::new();
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
let node_runtime = FakeNodeRuntime::new();
let store = cx.new_model(|cx| {
@@ -271,7 +267,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_registry.clone(),
theme_registry.clone(),
slash_command_registry.clone(),
indexed_docs_registry.clone(),
cx,
)
});
@@ -344,7 +339,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
grammars: BTreeMap::default(),
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
}),
dev: false,
},
@@ -395,7 +389,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_registry.clone(),
theme_registry.clone(),
slash_command_registry,
indexed_docs_registry,
cx,
)
});
@@ -446,7 +439,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
}
#[gpui::test]
async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
init_test(cx);
cx.executor().allow_parking();
@@ -456,8 +449,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
.parent()
.unwrap();
let cache_dir = root_dir.join("target");
let test_extension_id = "test-extension";
let test_extension_dir = root_dir.join("extensions").join(test_extension_id);
let gleam_extension_dir = root_dir.join("extensions").join("gleam");
let fs = Arc::new(RealFs::default());
let extensions_dir = temp_tree(json!({
@@ -476,7 +468,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let slash_command_registry = SlashCommandRegistry::new();
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
let node_runtime = FakeNodeRuntime::new();
let mut status_updates = language_registry.language_server_binary_statuses();
@@ -567,7 +558,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
language_registry.clone(),
theme_registry.clone(),
slash_command_registry,
indexed_docs_registry,
cx,
)
});
@@ -597,7 +587,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
extension_store
.update(cx, |store, cx| {
store.install_dev_extension(test_extension_dir.clone(), cx)
store.install_dev_extension(gleam_extension_dir.clone(), cx)
})
.await
.unwrap();
@@ -612,8 +602,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
.unwrap();
let fake_server = fake_servers.next().await.unwrap();
let expected_server_path =
extensions_dir.join(format!("work/{test_extension_id}/gleam-v1.2.3/gleam"));
let expected_server_path = extensions_dir.join("work/gleam/gleam-v1.2.3/gleam");
let expected_binary_contents = language_server_version.lock().binary_contents.clone();
assert_eq!(fake_server.binary.path, expected_server_path);
@@ -727,8 +716,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
// The extension re-fetches the latest version of the language server.
let fake_server = fake_servers.next().await.unwrap();
let new_expected_server_path =
extensions_dir.join(format!("work/{test_extension_id}/gleam-v2.0.0/gleam"));
let new_expected_server_path = extensions_dir.join("work/gleam/gleam-v2.0.0/gleam");
let expected_binary_contents = language_server_version.lock().binary_contents.clone();
assert_eq!(fake_server.binary.path, new_expected_server_path);
assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);

View File

@@ -2,7 +2,6 @@ mod since_v0_0_1;
mod since_v0_0_4;
mod since_v0_0_6;
mod since_v0_0_7;
use indexed_docs::IndexedDocsDatabase;
use release_channel::ReleaseChannel;
use since_v0_0_7 as latest;
@@ -290,24 +289,6 @@ impl Extension {
}
}
}
pub async fn call_index_docs(
&self,
store: &mut Store<WasmState>,
provider: &str,
package_name: &str,
database: Resource<Arc<IndexedDocsDatabase>>,
) -> Result<Result<(), String>> {
match self {
Extension::V007(ext) => {
ext.call_index_docs(store, provider, package_name, database)
.await
}
Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
Err(anyhow!("`index_docs` not available prior to v0.0.7"))
}
}
}
}
trait ToWasmtimeResult<T> {

View File

@@ -1,13 +1,10 @@
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
use ::settings::Settings;
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{anyhow, bail, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use futures::AsyncReadExt;
use futures::{io::BufReader, FutureExt as _};
use http::AsyncBody;
use indexed_docs::IndexedDocsDatabase;
use language::{
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
};
@@ -29,7 +26,6 @@ wasmtime::component::bindgen!({
path: "../extension_api/wit/since_v0.0.7",
with: {
"worktree": ExtensionWorktree,
"key-value-store": ExtensionKeyValueStore
},
});
@@ -41,31 +37,11 @@ mod settings {
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
pub fn linker() -> &'static Linker<WasmState> {
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
}
#[async_trait]
impl HostKeyValueStore for WasmState {
async fn insert(
&mut self,
kv_store: Resource<ExtensionKeyValueStore>,
key: String,
value: String,
) -> wasmtime::Result<Result<(), String>> {
let kv_store = self.table.get(&kv_store)?;
kv_store.insert(key, value).await.to_wasmtime_result()
}
fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
// We only ever hand out borrows of key-value stores.
Ok(())
}
}
#[async_trait]
impl HostWorktree for WasmState {
async fn id(
@@ -125,41 +101,6 @@ impl HostWorktree for WasmState {
#[async_trait]
impl common::Host for WasmState {}
#[async_trait]
impl http_client::Host for WasmState {
async fn fetch(
&mut self,
req: http_client::HttpRequest,
) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
maybe!(async {
let url = &req.url;
let mut response = self
.host
.http_client
.get(url, AsyncBody::default(), true)
.await?;
if response.status().is_client_error() || response.status().is_server_error() {
bail!("failed to fetch '{url}': status code {}", response.status())
}
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.with_context(|| format!("failed to read response body from '{url}'"))?;
Ok(http_client::HttpResponse {
body: String::from_utf8(body)?,
})
})
.await
.to_wasmtime_result()
}
}
#[async_trait]
impl nodejs::Host for WasmState {
async fn node_binary_path(&mut self) -> wasmtime::Result<Result<String, String>> {

View File

@@ -19,7 +19,6 @@ pub use wit::{
github_release_by_tag_name, latest_github_release, GithubRelease, GithubReleaseAsset,
GithubReleaseOptions,
},
zed::extension::http_client::{fetch, HttpRequest, HttpResponse},
zed::extension::nodejs::{
node_binary_path, npm_install_package, npm_package_installed_version,
npm_package_latest_version,
@@ -27,7 +26,7 @@ pub use wit::{
zed::extension::platform::{current_platform, Architecture, Os},
zed::extension::slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection},
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
LanguageServerInstallationStatus, Range, Worktree,
};
// Undocumented WIT re-exports.
@@ -127,15 +126,6 @@ pub trait Extension: Send + Sync {
) -> Result<SlashCommandOutput, String> {
Err("`run_slash_command` not implemented".to_string())
}
fn index_docs(
&self,
_provider: String,
_package: String,
_database: &KeyValueStore,
) -> Result<(), String> {
Err("`index_docs` not implemented".to_string())
}
}
/// Registers the provided type as a Zed extension.
@@ -258,14 +248,6 @@ impl wit::Guest for Component {
) -> Result<SlashCommandOutput, String> {
extension().run_slash_command(command, argument, worktree)
}
fn index_docs(
provider: String,
package: String,
database: &KeyValueStore,
) -> Result<(), String> {
extension().index_docs(provider, package, database)
}
}
/// The ID of a language server.

View File

@@ -2,7 +2,6 @@ package zed:extension;
world extension {
import github;
import http-client;
import platform;
import nodejs;
@@ -83,12 +82,6 @@ world extension {
shell-env: func() -> env-vars;
}
/// A key-value store.
resource key-value-store {
/// Inserts an entry under the specified key.
insert: func(key: string, value: string) -> result<_, string>;
}
/// Returns the command used to start up the language server.
export language-server-command: func(language-server-id: string, worktree: borrow<worktree>) -> result<command, string>;
@@ -134,7 +127,4 @@ world extension {
/// Returns the output from running the provided slash command.
export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<slash-command-output, string>;
/// Indexes the docs for the specified package.
export index-docs: func(provider-name: string, package-name: string, database: borrow<key-value-store>) -> result<_, string>;
}

View File

@@ -1,16 +0,0 @@
interface http-client {
/// An HTTP request.
record http-request {
/// The URL to which the request should be made.
url: string,
}
/// An HTTP response.
record http-response {
/// The response body.
body: string,
}
/// Performs an HTTP request and returns the response.
fetch: func(req: http-request) -> result<http-response, string>;
}

View File

@@ -29,11 +29,6 @@ impl FeatureFlag for Remoting {
const NAME: &'static str = "remoting";
}
pub struct TerminalInlineAssist {}
impl FeatureFlag for TerminalInlineAssist {
const NAME: &'static str = "terminal-inline-assist";
}
pub trait FeatureFlagViewExt<V: 'static> {
fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
where

View File

@@ -80,7 +80,6 @@ backtrace = "0.3"
collections = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }
http = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
[build-dependencies]
embed-resource = "2.4"
@@ -133,7 +132,6 @@ x11rb = { version = "0.13.0", features = [
"xinput",
"cursor",
"resource_manager",
"sync",
] }
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca4afad184ab6e7c16af", features = [
@@ -142,7 +140,6 @@ xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca
] }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
x11-clipboard = "0.9.2"
mio = { version = "1.0.0", features = ["os-poll", "os-ext"] }
[target.'cfg(windows)'.dependencies]
windows.workspace = true
@@ -160,11 +157,3 @@ path = "examples/image/image.rs"
[[example]]
name = "set_menus"
path = "examples/set_menus.rs"
[[example]]
name = "window_shadow"
path = "examples/window_shadow.rs"
[[example]]
name = "input"
path = "examples/input.rs"

View File

@@ -23,11 +23,6 @@ impl AssetSource for Assets {
}
}
const ARROW_CIRCLE_SVG: &'static str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/examples/image/arrow_circle.svg"
);
struct AnimationExample {}
impl Render for AnimationExample {
@@ -47,7 +42,7 @@ impl Render for AnimationExample {
.child(
svg()
.size_8()
.path(ARROW_CIRCLE_SVG)
.path("examples/image/arrow_circle.svg")
.text_color(black())
.with_animation(
"image_circle",

View File

@@ -23,7 +23,7 @@ impl Render for HelloWorld {
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),

View File

@@ -1,489 +0,0 @@
use std::ops::Range;
use gpui::*;
use unicode_segmentation::*;
actions!(
text_input,
[
Backspace,
Delete,
Left,
Right,
SelectLeft,
SelectRight,
SelectAll,
Home,
End,
ShowCharacterPalette
]
);
struct TextInput {
focus_handle: FocusHandle,
content: SharedString,
selected_range: Range<usize>,
selection_reversed: bool,
marked_range: Option<Range<usize>>,
last_layout: Option<ShapedLine>,
}
impl TextInput {
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
} else {
self.move_to(self.selected_range.start, cx)
}
}
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.move_to(self.next_boundary(self.selected_range.end), cx);
} else {
self.move_to(self.selected_range.end, cx)
}
}
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
self.select_to(self.previous_boundary(self.cursor_offset()), cx);
}
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
self.select_to(self.next_boundary(self.cursor_offset()), cx);
}
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
self.move_to(0, cx);
self.select_to(self.content.len(), cx)
}
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
self.move_to(0, cx);
}
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
self.move_to(self.content.len(), cx);
}
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
}
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
if self.selected_range.is_empty() {
self.select_to(self.next_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
}
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
cx.show_character_palette();
}
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
self.selected_range = offset..offset;
cx.notify()
}
fn cursor_offset(&self) -> usize {
if self.selection_reversed {
self.selected_range.start
} else {
self.selected_range.end
}
}
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
if self.selection_reversed {
self.selected_range.start = offset
} else {
self.selected_range.end = offset
};
if self.selected_range.end < self.selected_range.start {
self.selection_reversed = !self.selection_reversed;
self.selected_range = self.selected_range.end..self.selected_range.start;
}
cx.notify()
}
fn offset_from_utf16(&self, offset: usize) -> usize {
let mut utf8_offset = 0;
let mut utf16_count = 0;
for ch in self.content.chars() {
if utf16_count >= offset {
break;
}
utf16_count += ch.len_utf16();
utf8_offset += ch.len_utf8();
}
utf8_offset
}
fn offset_to_utf16(&self, offset: usize) -> usize {
let mut utf16_offset = 0;
let mut utf8_count = 0;
for ch in self.content.chars() {
if utf8_count >= offset {
break;
}
utf8_count += ch.len_utf8();
utf16_offset += ch.len_utf16();
}
utf16_offset
}
fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
}
fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
}
fn previous_boundary(&self, offset: usize) -> usize {
self.content
.grapheme_indices(true)
.rev()
.find_map(|(idx, _)| (idx < offset).then_some(idx))
.unwrap_or(0)
}
fn next_boundary(&self, offset: usize) -> usize {
self.content
.grapheme_indices(true)
.find_map(|(idx, _)| (idx > offset).then_some(idx))
.unwrap_or(self.content.len())
}
}
impl ViewInputHandler for TextInput {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
_cx: &mut ViewContext<Self>,
) -> Option<String> {
let range = self.range_from_utf16(&range_utf16);
Some(self.content[range].to_string())
}
fn selected_text_range(&mut self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
Some(self.range_to_utf16(&self.selected_range))
}
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
self.marked_range
.as_ref()
.map(|range| self.range_to_utf16(range))
}
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
self.marked_range = None;
}
fn replace_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
cx: &mut ViewContext<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
self.content =
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
.into();
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
self.marked_range.take();
cx.notify();
}
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range_utf16: Option<Range<usize>>,
cx: &mut ViewContext<Self>,
) {
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.or(self.marked_range.clone())
.unwrap_or(self.selected_range.clone());
self.content =
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
.into();
self.marked_range = Some(range.start..range.start + new_text.len());
self.selected_range = new_selected_range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
.map(|new_range| new_range.start + range.start..new_range.end + range.end)
.unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
cx.notify();
}
fn bounds_for_range(
&mut self,
range_utf16: Range<usize>,
bounds: Bounds<Pixels>,
_cx: &mut ViewContext<Self>,
) -> Option<Bounds<Pixels>> {
let Some(last_layout) = self.last_layout.as_ref() else {
return None;
};
let range = self.range_from_utf16(&range_utf16);
Some(Bounds::from_corners(
point(
bounds.left() + last_layout.x_for_index(range.start),
bounds.top(),
),
point(
bounds.left() + last_layout.x_for_index(range.end),
bounds.bottom(),
),
))
}
}
struct TextElement {
input: View<TextInput>,
}
struct PrepaintState {
line: Option<ShapedLine>,
cursor: Option<PaintQuad>,
selection: Option<PaintQuad>,
}
impl IntoElement for TextElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for TextElement {
type RequestLayoutState = ();
type PrepaintState = PrepaintState;
fn id(&self) -> Option<ElementId> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
cx: &mut WindowContext,
) -> (LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = cx.line_height().into();
(cx.request_layout(style, []), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
cx: &mut WindowContext,
) -> Self::PrepaintState {
let input = self.input.read(cx);
let content = input.content.clone();
let selected_range = input.selected_range.clone();
let cursor = input.cursor_offset();
let style = cx.text_style();
let run = TextRun {
len: input.content.len(),
font: style.font(),
color: style.color,
background_color: None,
underline: None,
strikethrough: None,
};
let runs = if let Some(marked_range) = input.marked_range.as_ref() {
vec![
TextRun {
len: marked_range.start,
..run.clone()
},
TextRun {
len: marked_range.end - marked_range.start,
underline: Some(UnderlineStyle {
color: Some(run.color),
thickness: px(1.0),
wavy: false,
}),
..run.clone()
},
TextRun {
len: input.content.len() - marked_range.end,
..run.clone()
},
]
.into_iter()
.filter(|run| run.len > 0)
.collect()
} else {
vec![run]
};
let font_size = style.font_size.to_pixels(cx.rem_size());
let line = cx
.text_system()
.shape_line(content, font_size, &runs)
.unwrap();
let cursor_pos = line.x_for_index(cursor);
let (selection, cursor) = if selected_range.is_empty() {
(
None,
Some(fill(
Bounds::new(
point(bounds.left() + cursor_pos, bounds.top()),
size(px(2.), bounds.bottom() - bounds.top()),
),
gpui::blue(),
)),
)
} else {
(
Some(fill(
Bounds::from_corners(
point(
bounds.left() + line.x_for_index(selected_range.start),
bounds.top(),
),
point(
bounds.left() + line.x_for_index(selected_range.end),
bounds.bottom(),
),
),
rgba(0x3311FF30),
)),
None,
)
};
PrepaintState {
line: Some(line),
cursor,
selection,
}
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut WindowContext,
) {
let focus_handle = self.input.read(cx).focus_handle.clone();
cx.handle_input(
&focus_handle,
ElementInputHandler::new(bounds, self.input.clone()),
);
if let Some(selection) = prepaint.selection.take() {
cx.paint_quad(selection)
}
let line = prepaint.line.take().unwrap();
line.paint(bounds.origin, cx.line_height(), cx).unwrap();
if let Some(cursor) = prepaint.cursor.take() {
cx.paint_quad(cursor);
}
self.input.update(cx, |input, _cx| {
input.last_layout = Some(line);
});
}
}
impl Render for TextInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.key_context("TextInput")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::backspace))
.on_action(cx.listener(Self::delete))
.on_action(cx.listener(Self::left))
.on_action(cx.listener(Self::right))
.on_action(cx.listener(Self::select_left))
.on_action(cx.listener(Self::select_right))
.on_action(cx.listener(Self::select_all))
.on_action(cx.listener(Self::home))
.on_action(cx.listener(Self::end))
.on_action(cx.listener(Self::show_character_palette))
.bg(rgb(0xeeeeee))
.size_full()
.line_height(px(30.))
.text_size(px(24.))
.child(
div()
.h(px(30. + 4. * 2.))
.w_full()
.p(px(4.))
.bg(white())
.child(TextElement {
input: cx.view().clone(),
}),
)
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
cx.bind_keys([
KeyBinding::new("backspace", Backspace, None),
KeyBinding::new("delete", Delete, None),
KeyBinding::new("left", Left, None),
KeyBinding::new("right", Right, None),
KeyBinding::new("shift-left", SelectLeft, None),
KeyBinding::new("shift-right", SelectRight, None),
KeyBinding::new("cmd-a", SelectAll, None),
KeyBinding::new("home", Home, None),
KeyBinding::new("end", End, None),
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
]);
let window = cx
.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|cx| {
cx.new_view(|cx| TextInput {
focus_handle: cx.focus_handle(),
content: "".into(),
selected_range: 0..0,
selection_reversed: false,
marked_range: None,
last_layout: None,
})
},
)
.unwrap();
window
.update(cx, |view, cx| {
view.focus_handle.focus(cx);
cx.activate(true)
})
.unwrap();
});
}

View File

@@ -51,8 +51,7 @@ fn main() {
kind: WindowKind::PopUp,
is_movable: false,
app_id: None,
window_min_size: None,
window_decorations: None,
window_min_size: Size::default(),
}
};

View File

@@ -1,222 +0,0 @@
use gpui::*;
use prelude::FluentBuilder;
struct WindowShadow {}
/*
Things to do:
1. We need a way of calculating which edge or corner the mouse is on,
and then dispatch on that
2. We need to improve the shadow rendering significantly
3. We need to implement the techniques in here in Zed
*/
impl Render for WindowShadow {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let decorations = cx.window_decorations();
let rounding = px(10.0);
let shadow_size = px(10.0);
let border_size = px(1.0);
let grey = rgb(0x808080);
cx.set_client_inset(shadow_size);
div()
.id("window-backdrop")
.bg(transparent_black())
.map(|div| match decorations {
Decorations::Server => div,
Decorations::Client { tiling, .. } => div
.bg(gpui::transparent_black())
.child(
canvas(
|_bounds, cx| {
cx.insert_hitbox(
Bounds::new(
point(px(0.0), px(0.0)),
cx.window_bounds().get_bounds().size,
),
false,
)
},
move |_bounds, hitbox, cx| {
let mouse = cx.mouse_position();
let size = cx.window_bounds().get_bounds().size;
let Some(edge) = resize_edge(mouse, shadow_size, size) else {
return;
};
cx.set_cursor_style(
match edge {
ResizeEdge::Top | ResizeEdge::Bottom => {
CursorStyle::ResizeUpDown
}
ResizeEdge::Left | ResizeEdge::Right => {
CursorStyle::ResizeLeftRight
}
ResizeEdge::TopLeft | ResizeEdge::BottomRight => {
CursorStyle::ResizeUpLeftDownRight
}
ResizeEdge::TopRight | ResizeEdge::BottomLeft => {
CursorStyle::ResizeUpRightDownLeft
}
},
&hitbox,
);
},
)
.size_full()
.absolute(),
)
.when(!(tiling.top || tiling.right), |div| {
div.rounded_tr(rounding)
})
.when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
.when(!tiling.top, |div| div.pt(shadow_size))
.when(!tiling.bottom, |div| div.pb(shadow_size))
.when(!tiling.left, |div| div.pl(shadow_size))
.when(!tiling.right, |div| div.pr(shadow_size))
.on_mouse_move(|_e, cx| cx.refresh())
.on_mouse_down(MouseButton::Left, move |e, cx| {
let size = cx.window_bounds().get_bounds().size;
let pos = e.position;
match resize_edge(pos, shadow_size, size) {
Some(edge) => cx.start_window_resize(edge),
None => cx.start_window_move(),
};
}),
})
.size_full()
.child(
div()
.cursor(CursorStyle::Arrow)
.map(|div| match decorations {
Decorations::Server => div,
Decorations::Client { tiling } => div
.border_color(grey)
.when(!(tiling.top || tiling.right), |div| {
div.rounded_tr(rounding)
})
.when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
.when(!tiling.top, |div| div.border_t(border_size))
.when(!tiling.bottom, |div| div.border_b(border_size))
.when(!tiling.left, |div| div.border_l(border_size))
.when(!tiling.right, |div| div.border_r(border_size))
.when(!tiling.is_tiled(), |div| {
div.shadow(smallvec::smallvec![gpui::BoxShadow {
color: Hsla {
h: 0.,
s: 0.,
l: 0.,
a: 0.4,
},
blur_radius: shadow_size / 2.,
spread_radius: px(0.),
offset: point(px(0.0), px(0.0)),
}])
}),
})
.on_mouse_move(|_e, cx| {
cx.stop_propagation();
})
.bg(gpui::rgb(0xCCCCFF))
.size_full()
.flex()
.flex_col()
.justify_around()
.child(
div().w_full().flex().flex_row().justify_around().child(
div()
.flex()
.bg(white())
.size(Length::Definite(Pixels(300.0).into()))
.justify_center()
.items_center()
.shadow_lg()
.border_1()
.border_color(rgb(0x0000ff))
.text_xl()
.text_color(rgb(0xffffff))
.child(
div()
.id("hello")
.w(px(200.0))
.h(px(100.0))
.bg(green())
.shadow(smallvec::smallvec![gpui::BoxShadow {
color: Hsla {
h: 0.,
s: 0.,
l: 0.,
a: 1.0,
},
blur_radius: px(20.0),
spread_radius: px(0.0),
offset: point(px(0.0), px(0.0)),
}])
.map(|div| match decorations {
Decorations::Server => div,
Decorations::Client { .. } => div
.on_mouse_down(MouseButton::Left, |_e, cx| {
cx.start_window_move();
})
.on_click(|e, cx| {
if e.down.button == MouseButton::Right {
cx.show_window_menu(e.up.position);
}
})
.text_color(black())
.child("this is the custom titlebar"),
}),
),
),
),
)
}
}
fn resize_edge(pos: Point<Pixels>, shadow_size: Pixels, size: Size<Pixels>) -> Option<ResizeEdge> {
let edge = if pos.y < shadow_size && pos.x < shadow_size {
ResizeEdge::TopLeft
} else if pos.y < shadow_size && pos.x > size.width - shadow_size {
ResizeEdge::TopRight
} else if pos.y < shadow_size {
ResizeEdge::Top
} else if pos.y > size.height - shadow_size && pos.x < shadow_size {
ResizeEdge::BottomLeft
} else if pos.y > size.height - shadow_size && pos.x > size.width - shadow_size {
ResizeEdge::BottomRight
} else if pos.y > size.height - shadow_size {
ResizeEdge::Bottom
} else if pos.x < shadow_size {
ResizeEdge::Left
} else if pos.x > size.width - shadow_size {
ResizeEdge::Right
} else {
return None;
};
Some(edge)
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
window_background: WindowBackgroundAppearance::Opaque,
window_decorations: Some(WindowDecorations::Client),
..Default::default()
},
|cx| {
cx.new_view(|cx| {
cx.observe_window_appearance(|_, cx| {
cx.refresh();
})
.detach();
WindowShadow {}
})
},
)
.unwrap();
});
}

View File

@@ -309,16 +309,6 @@ pub fn transparent_black() -> Hsla {
}
}
/// Transparent black in [`Hsla`]
pub fn transparent_white() -> Hsla {
Hsla {
h: 0.,
s: 0.,
l: 1.,
a: 0.,
}
}
/// Opaque grey in [`Hsla`], values will be clamped to the range [0, 1]
pub fn opaque_grey(lightness: f32, opacity: f32) -> Hsla {
Hsla {

Some files were not shown because too many files have changed in this diff Show More