diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4c4864fa32..b81becdeff 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -173,6 +173,11 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v4
with:
+ # We need to fetch more than one commit so that `script/draft-release-notes`
+ # is able to diff between the current and previous tag.
+ #
+ # 25 was chosen arbitrarily.
+ fetch-depth: 25
clean: false
submodules: "recursive"
@@ -205,7 +210,9 @@ jobs:
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
- script/draft-release-notes "$version" "$channel" > target/release-notes.md
+ mkdir -p target/
+ # Ignore any errors that occur while drafting release notes to not fail the build.
+ script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
- name: Generate license file
run: script/generate-licenses
diff --git a/.gitignore b/.gitignore
index 0b0f4d4ef8..1e5e9b0bd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@
/plugins/bin
/script/node_modules
/crates/theme/schemas/theme.json
-/crates/collab/.admins.json
+/crates/collab/seed.json
/assets/*licenses.md
**/venv
.build
diff --git a/.zed/settings.json b/.zed/settings.json
index dbafa2115a..eedf2f3753 100644
--- a/.zed/settings.json
+++ b/.zed/settings.json
@@ -21,5 +21,7 @@
"formatter": "prettier"
}
},
- "formatter": "auto"
+ "formatter": "auto",
+ "remove_trailing_whitespace_on_save": true,
+ "ensure_final_newline_on_save": true
}
diff --git a/.zed/tasks.json b/.zed/tasks.json
index 80465969e2..c95cf5ffb1 100644
--- a/.zed/tasks.json
+++ b/.zed/tasks.json
@@ -3,5 +3,10 @@
"label": "clippy",
"command": "cargo",
"args": ["xtask", "clippy"]
+ },
+ {
+ "label": "assistant2",
+ "command": "cargo",
+ "args": ["run", "-p", "assistant2", "--example", "assistant_example"]
}
]
diff --git a/Cargo.lock b/Cargo.lock
index 3b243f7d59..52c2558ed4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -284,21 +284,21 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "ash"
-version = "0.37.3+1.3.251"
+version = "0.38.0+1.3.281"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
+checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
dependencies = [
- "libloading 0.7.4",
+ "libloading 0.8.0",
]
[[package]]
name = "ash-window"
-version = "0.12.0"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b912285a7c29f3a8f87ca6f55afc48768624e5e33ec17dbd2f2075903f5e35ab"
+checksum = "52bca67b61cb81e5553babde81b8211f713cb6db79766f80168f3e5f40ea6c82"
dependencies = [
"ash",
- "raw-window-handle 0.5.2",
+ "raw-window-handle 0.6.0",
"raw-window-metal",
]
@@ -371,6 +371,52 @@ dependencies = [
"workspace",
]
+[[package]]
+name = "assistant2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "assets",
+ "assistant_tooling",
+ "client",
+ "editor",
+ "env_logger",
+ "feature_flags",
+ "fs",
+ "futures 0.3.28",
+ "gpui",
+ "language",
+ "languages",
+ "log",
+ "nanoid",
+ "node_runtime",
+ "open_ai",
+ "project",
+ "rand 0.8.5",
+ "release_channel",
+ "rich_text",
+ "schemars",
+ "semantic_index",
+ "serde",
+ "serde_json",
+ "settings",
+ "theme",
+ "ui",
+ "util",
+ "workspace",
+]
+
+[[package]]
+name = "assistant_tooling"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "gpui",
+ "schemars",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "async-broadcast"
version = "0.7.0"
@@ -643,7 +689,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -710,7 +756,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -741,7 +787,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -1385,7 +1431,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn 2.0.48",
+ "syn 2.0.59",
"which 4.4.2",
]
@@ -1434,7 +1480,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.4.0"
-source = "git+https://github.com/kvark/blade?rev=810ec594358aafea29a4a3d8ab601d25292b2ce4#810ec594358aafea29a4a3d8ab601d25292b2ce4"
+source = "git+https://github.com/kvark/blade?rev=e82eec97691c3acdb43494484be60d661edfebf3#e82eec97691c3acdb43494484be60d661edfebf3"
dependencies = [
"ash",
"ash-window",
@@ -1455,7 +1501,7 @@ dependencies = [
"mint",
"naga",
"objc",
- "raw-window-handle 0.5.2",
+ "raw-window-handle 0.6.0",
"slab",
"wasm-bindgen",
"web-sys",
@@ -1464,11 +1510,11 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.2.1"
-source = "git+https://github.com/kvark/blade?rev=810ec594358aafea29a4a3d8ab601d25292b2ce4#810ec594358aafea29a4a3d8ab601d25292b2ce4"
+source = "git+https://github.com/kvark/blade?rev=e82eec97691c3acdb43494484be60d661edfebf3#e82eec97691c3acdb43494484be60d661edfebf3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -1634,7 +1680,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -2019,7 +2065,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -2047,6 +2093,7 @@ dependencies = [
"core-services",
"ipc-channel",
"plist",
+ "release_channel",
"serde",
"util",
]
@@ -2253,6 +2300,7 @@ dependencies = [
"prost",
"rand 0.8.5",
"release_channel",
+ "remote_projects",
"reqwest",
"rpc",
"rustc-demangle",
@@ -2298,7 +2346,6 @@ dependencies = [
"editor",
"emojis",
"extensions_ui",
- "feature_flags",
"futures 0.3.28",
"fuzzy",
"gpui",
@@ -2958,7 +3005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
dependencies = [
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -3135,13 +3182,17 @@ dependencies = [
"anyhow",
"client",
"collections",
+ "ctor",
"editor",
+ "env_logger",
"futures 0.3.28",
"gpui",
"language",
"log",
"lsp",
+ "pretty_assertions",
"project",
+ "rand 0.8.5",
"schemars",
"serde",
"serde_json",
@@ -3388,10 +3439,18 @@ dependencies = [
]
[[package]]
-name = "embed-manifest"
-version = "1.4.0"
+name = "embed-resource"
+version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41cd446c890d6bed1d8b53acef5f240069ebef91d6fae7c5f52efe61fe8b5eae"
+checksum = "c6985554d0688b687c5cb73898a34fbe3ad6c24c58c238a4d91d5e840670ee9d"
+dependencies = [
+ "cc",
+ "memchr",
+ "rustc_version",
+ "toml 0.8.10",
+ "vswhom",
+ "winreg 0.52.0",
+]
[[package]]
name = "emojis"
@@ -3441,7 +3500,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -3770,6 +3829,7 @@ dependencies = [
"ctor",
"editor",
"env_logger",
+ "futures 0.3.28",
"fuzzy",
"gpui",
"itertools 0.11.0",
@@ -3798,6 +3858,17 @@ dependencies = [
"util",
]
+[[package]]
+name = "filedescriptor"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
+dependencies = [
+ "libc",
+ "thiserror",
+ "winapi",
+]
+
[[package]]
name = "filetime"
version = "0.2.22"
@@ -3942,7 +4013,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -4182,7 +4253,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -4435,9 +4506,9 @@ dependencies = [
[[package]]
name = "gpu-alloc-ash"
-version = "0.6.0"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2424bc9be88170e1a56e57c25d3d0e2dfdd22e8f328e892786aeb4da1415732"
+checksum = "cbda7a18a29bc98c2e0de0435c347df935bf59489935d0cbd0b73f1679b6f79a"
dependencies = [
"ash",
"gpu-alloc-types",
@@ -4479,8 +4550,10 @@ dependencies = [
"cosmic-text",
"ctor",
"derive_more",
+ "embed-resource",
"env_logger",
"etagere",
+ "filedescriptor",
"flume",
"font-kit",
"foreign-types 0.5.0",
@@ -4503,7 +4576,6 @@ dependencies = [
"postage",
"profiling",
"rand 0.8.5",
- "raw-window-handle 0.5.2",
"raw-window-handle 0.6.0",
"refineable",
"resvg",
@@ -4666,6 +4738,7 @@ dependencies = [
"project",
"rpc",
"settings",
+ "shellexpand",
"util",
]
@@ -5048,7 +5121,7 @@ checksum = "ce243b1bfa62ffc028f1cc3b6034ec63d649f3031bc8a4fbbb004e1ac17d1f68"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -5397,6 +5470,7 @@ dependencies = [
"globset",
"gpui",
"indoc",
+ "itertools 0.11.0",
"lazy_static",
"log",
"lsp",
@@ -5493,12 +5567,9 @@ dependencies = [
"regex",
"rope",
"rust-embed",
- "schemars",
"serde",
- "serde_derive",
"serde_json",
"settings",
- "shellexpand",
"smol",
"task",
"text",
@@ -5509,12 +5580,10 @@ dependencies = [
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-css",
- "tree-sitter-elixir",
"tree-sitter-embedded-template",
"tree-sitter-go",
"tree-sitter-gomod",
"tree-sitter-gowork",
- "tree-sitter-heex",
"tree-sitter-jsdoc",
"tree-sitter-json 0.20.0",
"tree-sitter-markdown",
@@ -5669,7 +5738,7 @@ checksum = "ba125974b109d512fccbc6c0244e7580143e460895dfd6ea7f8bbb692fd94396"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -5848,6 +5917,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-recursion 1.0.5",
+ "collections",
"editor",
"gpui",
"language",
@@ -5904,9 +5974,9 @@ dependencies = [
[[package]]
name = "memchr"
-version = "2.6.3"
+version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "memfd"
@@ -6630,7 +6700,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -6706,7 +6776,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -6786,7 +6856,7 @@ checksum = "e8890702dbec0bad9116041ae586f84805b13eecd1d8b1df27c29998a9969d6d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -6964,7 +7034,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -7015,7 +7085,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -7239,7 +7309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
dependencies = [
"proc-macro2",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -7296,9 +7366,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.78"
+version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [
"unicode-ident",
]
@@ -7319,7 +7389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -7672,14 +7742,14 @@ checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
[[package]]
name = "raw-window-metal"
-version = "0.3.2"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac4ea493258d54c24cb46aa9345d099e58e2ea3f30dd63667fc54fc892f18e76"
+checksum = "76e8caa82e31bb98fee12fa8f051c94a6aa36b07cddb03f0d4fc558988360ff1"
dependencies = [
"cocoa",
"core-graphics",
"objc",
- "raw-window-handle 0.5.2",
+ "raw-window-handle 0.6.0",
]
[[package]]
@@ -7715,7 +7785,9 @@ dependencies = [
name = "recent_projects"
version = "0.1.0"
dependencies = [
+ "anyhow",
"editor",
+ "feature_flags",
"fuzzy",
"gpui",
"language",
@@ -7723,10 +7795,15 @@ dependencies = [
"ordered-float 2.10.0",
"picker",
"project",
+ "remote_projects",
+ "rpc",
"serde",
"serde_json",
+ "settings",
"smol",
+ "theme",
"ui",
+ "ui_text_field",
"util",
"workspace",
]
@@ -7853,6 +7930,18 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "remote_projects"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "client",
+ "gpui",
+ "rpc",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "rend"
version = "0.4.0"
@@ -7896,7 +7985,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "winreg",
+ "winreg 0.50.0",
]
[[package]]
@@ -8143,7 +8232,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
- "syn 2.0.48",
+ "syn 2.0.59",
"walkdir",
]
@@ -8417,7 +8506,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -8458,7 +8547,7 @@ dependencies = [
"proc-macro2",
"quote",
"sea-bae",
- "syn 2.0.48",
+ "syn 2.0.59",
"unicode-ident",
]
@@ -8642,7 +8731,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -8707,7 +8796,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -9410,7 +9499,6 @@ dependencies = [
"ctrlc",
"dialoguer",
"editor",
- "embed-manifest",
"fuzzy",
"gpui",
"indoc",
@@ -9473,7 +9561,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -9602,9 +9690,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.48"
+version = "2.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
dependencies = [
"proc-macro2",
"quote",
@@ -9752,7 +9840,6 @@ dependencies = [
"serde_json",
"settings",
"task",
- "terminal",
"tree-sitter-rust",
"tree-sitter-typescript",
"ui",
@@ -9850,6 +9937,7 @@ dependencies = [
"shellexpand",
"smol",
"task",
+ "tasks_ui",
"terminal",
"theme",
"ui",
@@ -9969,7 +10057,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -10148,7 +10236,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -10373,7 +10461,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -10441,7 +10529,7 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.20.100"
-source = "git+https://github.com/tree-sitter/tree-sitter?rev=7f21c3b98c0749ac192da67a0d65dfe3eabc4a63#7f21c3b98c0749ac192da67a0d65dfe3eabc4a63"
+source = "git+https://github.com/tree-sitter/tree-sitter?rev=528bcd2274814ca53711a57d71d1e3cf7abd73fe#528bcd2274814ca53711a57d71d1e3cf7abd73fe"
dependencies = [
"cc",
"regex",
@@ -10553,7 +10641,7 @@ dependencies = [
[[package]]
name = "tree-sitter-jsdoc"
version = "0.20.0"
-source = "git+https://github.com/tree-sitter/tree-sitter-jsdoc#6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55"
+source = "git+https://github.com/tree-sitter/tree-sitter-jsdoc?rev=6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55#6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55"
dependencies = [
"cc",
"tree-sitter",
@@ -11023,6 +11111,7 @@ dependencies = [
"futures 0.3.28",
"gpui",
"indoc",
+ "itertools 0.11.0",
"language",
"log",
"lsp",
@@ -11058,6 +11147,26 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
+[[package]]
+name = "vswhom"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
+dependencies = [
+ "libc",
+ "vswhom-sys",
+]
+
+[[package]]
+name = "vswhom-sys"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18"
+dependencies = [
+ "cc",
+ "libc",
+]
+
[[package]]
name = "vte"
version = "0.13.0"
@@ -11140,7 +11249,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
"wasm-bindgen-shared",
]
@@ -11174,7 +11283,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -11311,7 +11420,7 @@ dependencies = [
"anyhow",
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
"wasmtime-component-util",
"wasmtime-wit-bindgen",
"wit-parser",
@@ -11472,7 +11581,7 @@ checksum = "6d6d967f01032da7d4c6303da32f6a00d5efe1bac124b156e7342d8ace6ffdfc"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -11752,7 +11861,7 @@ dependencies = [
"proc-macro2",
"quote",
"shellexpand",
- "syn 2.0.48",
+ "syn 2.0.59",
"witx",
]
@@ -11764,7 +11873,7 @@ checksum = "512d816dbcd0113103b2eb2402ec9018e7f0755202a5b3e67db726f229d8dcae"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
"wiggle-generate",
]
@@ -11882,7 +11991,7 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -11893,7 +12002,7 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -12131,6 +12240,16 @@ dependencies = [
"windows-sys 0.48.0",
]
+[[package]]
+name = "winreg"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "winresource"
version = "0.1.17"
@@ -12210,7 +12329,7 @@ dependencies = [
"anyhow",
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
"wit-bindgen-core",
"wit-bindgen-rust",
]
@@ -12290,6 +12409,7 @@ dependencies = [
"parking_lot",
"postage",
"project",
+ "remote_projects",
"schemars",
"serde",
"serde_json",
@@ -12528,12 +12648,13 @@ dependencies = [
[[package]]
name = "zed"
-version = "0.133.0"
+version = "0.134.0"
dependencies = [
"activity_indicator",
"anyhow",
"assets",
"assistant",
+ "assistant2",
"audio",
"auto_update",
"backtrace",
@@ -12553,7 +12674,6 @@ dependencies = [
"db",
"diagnostics",
"editor",
- "embed-manifest",
"env_logger",
"extension",
"extensions_ui",
@@ -12588,6 +12708,7 @@ dependencies = [
"quick_action_bar",
"recent_projects",
"release_channel",
+ "remote_projects",
"rope",
"search",
"serde",
@@ -12643,6 +12764,20 @@ dependencies = [
[[package]]
name = "zed_dart"
+version = "0.0.2"
+dependencies = [
+ "zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "zed_deno"
+version = "0.0.1"
+dependencies = [
+ "zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "zed_elixir"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -12770,7 +12905,7 @@ dependencies = [
[[package]]
name = "zed_terraform"
-version = "0.0.2"
+version = "0.0.3"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -12826,7 +12961,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
@@ -12846,7 +12981,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.48",
+ "syn 2.0.59",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 4b9bad5554..579ef9799d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,8 @@ members = [
"crates/anthropic",
"crates/assets",
"crates/assistant",
+ "crates/assistant_tooling",
+ "crates/assistant2",
"crates/audio",
"crates/auto_update",
"crates/breadcrumbs",
@@ -67,6 +69,7 @@ members = [
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/release_channel",
+ "crates/remote_projects",
"crates/rich_text",
"crates/rope",
"crates/rpc",
@@ -106,6 +109,8 @@ members = [
"extensions/clojure",
"extensions/csharp",
"extensions/dart",
+ "extensions/deno",
+ "extensions/elixir",
"extensions/elm",
"extensions/emmet",
"extensions/erlang",
@@ -136,6 +141,8 @@ ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
+assistant2 = { path = "crates/assistant2" }
+assistant_tooling = { path = "crates/assistant_tooling" }
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
base64 = "0.13"
@@ -200,12 +207,14 @@ project_symbols = { path = "crates/project_symbols" }
quick_action_bar = { path = "crates/quick_action_bar" }
recent_projects = { path = "crates/recent_projects" }
release_channel = { path = "crates/release_channel" }
+remote_projects = { path = "crates/remote_projects" }
rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
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" }
settings = { path = "crates/settings" }
snippet = { path = "crates/snippet" }
@@ -241,9 +250,8 @@ async-recursion = "1.0.0"
async-tar = "0.4.2"
async-trait = "0.1"
bitflags = "2.4.2"
-blade-graphics = { git = "https://github.com/kvark/blade", rev = "810ec594358aafea29a4a3d8ab601d25292b2ce4" }
-blade-macros = { git = "https://github.com/kvark/blade", rev = "810ec594358aafea29a4a3d8ab601d25292b2ce4" }
-blade-rwh = { package = "raw-window-handle", version = "0.5" }
+blade-graphics = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
+blade-macros = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
cap-std = "3.0"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
@@ -275,6 +283,7 @@ itertools = "0.11.0"
lazy_static = "1.4.0"
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
+nanoid = "0.4"
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
@@ -333,7 +342,7 @@ tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work" }
rustc-demangle = "0.1.23"
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
tree-sitter-html = "0.19.0"
-tree-sitter-jsdoc = { git = "https://github.com/tree-sitter/tree-sitter-jsdoc", ref = "6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55" }
+tree-sitter-jsdoc = { git = "https://github.com/tree-sitter/tree-sitter-jsdoc", rev = "6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55" }
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
@@ -399,7 +408,7 @@ features = [
]
[patch.crates-io]
-tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7f21c3b98c0749ac192da67a0d65dfe3eabc4a63" }
+tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "528bcd2274814ca53711a57d71d1e3cf7abd73fe" }
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }
diff --git a/README.md b/README.md
index 186aa4ab70..1c17a950fd 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,51 @@
-# Zed
-
-[](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
-
-Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
-
-## Installation
-
-You can [download](https://zed.dev/download) Zed today for macOS (v10.15+).
-
-Support for additional platforms is on our [roadmap](https://zed.dev/roadmap):
-
-- Linux ([tracking issue](https://github.com/zed-industries/zed/issues/7015))
-- Windows ([tracking issue](https://github.com/zed-industries/zed/issues/5394))
-- Web ([tracking issue](https://github.com/zed-industries/zed/issues/5396))
-
-For macOS users, you can also install Zed using [Homebrew](https://brew.sh/):
-
-```sh
-brew install --cask zed
-```
-
-Alternatively, to install the Preview release:
-
-```sh
-brew tap homebrew/cask-versions
-brew install zed-preview
-```
-
-## Developing Zed
-
-- [Building Zed for macOS](./docs/src/developing_zed__building_zed_macos.md)
-- [Building Zed for Linux](./docs/src/developing_zed__building_zed_linux.md)
-- [Building Zed for Windows](./docs/src/developing_zed__building_zed_windows.md)
-- [Running Collaboration Locally](./docs/src/developing_zed__local_collaboration.md)
-
-## Contributing
-
-See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
-
-Also... we're hiring! Check out our [jobs](https://zed.dev/jobs) page for open roles.
-
-## Licensing
-
-License information for third party dependencies must be correctly provided for CI to pass.
-
-We use [`cargo-about`](https://github.com/EmbarkStudios/cargo-about) to automatically comply with open source licenses. If CI is failing, check the following:
-
-- Is it showing a `no license specified` error for a crate you've created? If so, add `publish = false` under `[package]` in your crate's Cargo.toml.
-- Is the error `failed to satisfy license requirements` for a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to the `accepted` array in `script/licenses/zed-licenses.toml`.
-- Is `cargo-about` unable to find the license for a dependency? If so, add a clarification field at the end of `script/licenses/zed-licenses.toml`, as specified in the [cargo-about book](https://embarkstudios.github.io/cargo-about/cli/generate/config.html#crate-configuration).
+# Zed
+
+[](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
+
+Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
+
+## Installation
+
+You can [download](https://zed.dev/download) Zed today for macOS (v10.15+).
+
+Support for additional platforms is on our [roadmap](https://zed.dev/roadmap):
+
+- Linux ([tracking issue](https://github.com/zed-industries/zed/issues/7015))
+- Windows ([tracking issue](https://github.com/zed-industries/zed/issues/5394))
+- Web ([tracking issue](https://github.com/zed-industries/zed/issues/5396))
+
+For macOS users, you can also install Zed using [Homebrew](https://brew.sh/):
+
+```sh
+brew install --cask zed
+```
+
+Alternatively, to install the Preview release:
+
+```sh
+brew tap homebrew/cask-versions
+brew install zed-preview
+```
+
+## Developing Zed
+
+- [Building Zed for macOS](./docs/src/developing_zed__building_zed_macos.md)
+- [Building Zed for Linux](./docs/src/developing_zed__building_zed_linux.md)
+- [Building Zed for Windows](./docs/src/developing_zed__building_zed_windows.md)
+- [Running Collaboration Locally](./docs/src/developing_zed__local_collaboration.md)
+
+## Contributing
+
+See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
+
+Also... we're hiring! Check out our [jobs](https://zed.dev/jobs) page for open roles.
+
+## Licensing
+
+License information for third party dependencies must be correctly provided for CI to pass.
+
+We use [`cargo-about`](https://github.com/EmbarkStudios/cargo-about) to automatically comply with open source licenses. If CI is failing, check the following:
+
+- Is it showing a `no license specified` error for a crate you've created? If so, add `publish = false` under `[package]` in your crate's Cargo.toml.
+- Is the error `failed to satisfy license requirements` for a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to the `accepted` array in `script/licenses/zed-licenses.toml`.
+- Is `cargo-about` unable to find the license for a dependency? If so, add a clarification field at the end of `script/licenses/zed-licenses.toml`, as specified in the [cargo-about book](https://embarkstudios.github.io/cargo-about/cli/generate/config.html#crate-configuration).
diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json
index 25aa6c2aa0..0ee203c3c7 100644
--- a/assets/icons/file_icons/file_types.json
+++ b/assets/icons/file_icons/file_types.json
@@ -161,6 +161,8 @@
"webp": "image",
"wma": "audio",
"wmv": "video",
+ "woff": "font",
+ "woff2": "font",
"wv": "audio",
"xls": "document",
"xlsx": "document",
@@ -327,7 +329,7 @@
},
"tcl": {
"icon": "icons/file_icons/tcl.svg"
- },
+ },
"vcs": {
"icon": "icons/file_icons/git.svg"
},
diff --git a/assets/icons/mail_open.svg b/assets/icons/mail_open.svg
index b63915bd73..b857037b86 100644
--- a/assets/icons/mail_open.svg
+++ b/assets/icons/mail_open.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/assets/icons/server.svg b/assets/icons/server.svg
index 10fbdcbff4..a8b6ad92b3 100644
--- a/assets/icons/server.svg
+++ b/assets/icons/server.svg
@@ -1,5 +1,16 @@
-
-
diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json
index 209699b3cd..6fb4647798 100644
--- a/assets/keymaps/default-linux.json
+++ b/assets/keymaps/default-linux.json
@@ -297,13 +297,8 @@
"ctrl-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
- "ctrl-alt-shift-up": [
- "editor::DuplicateLine",
- {
- "move_upwards": true
- }
- ],
- "ctrl-alt-shift-down": "editor::DuplicateLine",
+ "ctrl-alt-shift-up": "editor::DuplicateLineUp",
+ "ctrl-alt-shift-down": "editor::DuplicateLineDown",
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectLargerSyntaxNode", //todo(linux) tmp keybinding
@@ -593,12 +588,6 @@
"tab": "channel_modal::ToggleMode"
}
},
- {
- "context": "ChatPanel > MessageEditor",
- "bindings": {
- "escape": "chat_panel::CloseReplyPreview"
- }
- },
{
"context": "FileFinder",
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index f909bd48c5..4650df181d 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -209,7 +209,15 @@
}
},
{
- "context": "AssistantPanel",
+ "context": "AssistantChat > Editor", // Used in the assistant2 crate
+ "bindings": {
+ "enter": ["assistant2::Submit", "Simple"],
+ "cmd-enter": ["assistant2::Submit", "Codebase"],
+ "escape": "assistant2::Cancel"
+ }
+ },
+ {
+ "context": "AssistantPanel", // Used in the assistant crate, which we're replacing
"bindings": {
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPrevMatch"
diff --git a/assets/keymaps/storybook.json b/assets/keymaps/storybook.json
index 117bbde09b..5e375821e0 100644
--- a/assets/keymaps/storybook.json
+++ b/assets/keymaps/storybook.json
@@ -17,7 +17,11 @@
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
- "cmd-q": "storybook::Quit"
+ "cmd-q": "storybook::Quit",
+ "backspace": "editor::Backspace",
+ "delete": "editor::Delete",
+ "left": "editor::MoveLeft",
+ "right": "editor::MoveRight"
}
}
]
diff --git a/assets/settings/default.json b/assets/settings/default.json
index b709bc2b34..666ff1a429 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -294,6 +294,10 @@
"show_call_status_icon": true,
// Whether to use language servers to provide code intelligence.
"enable_language_server": true,
+ // The list of language servers to use (or disable) for all languages.
+ //
+ // This is typically customized on a per-language basis.
+ "language_servers": ["..."],
// When to automatically save edited buffers. This setting can
// take four values.
//
@@ -561,31 +565,6 @@
// Existing terminals will not pick up this change until they are recreated.
// "max_scroll_history_lines": 10000,
},
- // Settings specific to our elixir integration
- "elixir": {
- // Change the LSP zed uses for elixir.
- // Note that changing this setting requires a restart of Zed
- // to take effect.
- //
- // May take 3 values:
- // 1. Use the standard ElixirLS, this is the default
- // "lsp": "elixir_ls"
- // 2. Use the experimental NextLs
- // "lsp": "next_ls",
- // 3. Use a language server installed locally on your machine:
- // "lsp": {
- // "local": {
- // "path": "~/next-ls/bin/start",
- // "arguments": ["--stdio"]
- // }
- // },
- //
- "lsp": "elixir_ls"
- },
- // Settings specific to our deno integration
- "deno": {
- "enable": false
- },
"code_actions_on_format": {},
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
@@ -600,6 +579,13 @@
// }
//
"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
+ // and change the value to `false`.
+ "auto_install_extensions": {
+ "html": true
+ },
// Different settings for specific languages.
"languages": {
"C++": {
diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml
index 8fcb1f9cfe..06f91da59f 100644
--- a/crates/assets/Cargo.toml
+++ b/crates/assets/Cargo.toml
@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
+[lib]
+path = "src/assets.rs"
+
[lints]
workspace = true
diff --git a/crates/assets/src/lib.rs b/crates/assets/src/assets.rs
similarity index 62%
rename from crates/assets/src/lib.rs
rename to crates/assets/src/assets.rs
index 4f013dd5af..b0a32a9d9c 100644
--- a/crates/assets/src/lib.rs
+++ b/crates/assets/src/assets.rs
@@ -1,7 +1,7 @@
// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build.
use anyhow::anyhow;
-use gpui::{AssetSource, Result, SharedString};
+use gpui::{AppContext, AssetSource, Result, SharedString};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
@@ -34,3 +34,19 @@ impl AssetSource for Assets {
.collect())
}
}
+
+impl Assets {
+ /// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
+ pub fn load_fonts(&self, cx: &AppContext) -> gpui::Result<()> {
+ let font_paths = self.list("fonts")?;
+ let mut embedded_fonts = Vec::new();
+ for font_path in font_paths {
+ if font_path.ends_with(".ttf") {
+ let font_bytes = cx.asset_source().load(&font_path)?;
+ embedded_fonts.push(font_bytes);
+ }
+ }
+
+ cx.text_system().add_fonts(embedded_fonts)
+ }
+}
diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs
index 9d72b512a1..46eeb4c095 100644
--- a/crates/assistant/src/assistant.rs
+++ b/crates/assistant/src/assistant.rs
@@ -128,6 +128,8 @@ impl LanguageModelRequestMessage {
Role::System => proto::LanguageModelRole::LanguageModelSystem,
} as i32,
content: self.content.clone(),
+ tool_calls: Vec::new(),
+ tool_call_id: None,
}
}
}
@@ -147,6 +149,8 @@ impl LanguageModelRequest {
messages: self.messages.iter().map(|m| m.to_proto()).collect(),
stop: self.stop.clone(),
temperature: self.temperature,
+ tool_choice: None,
+ tools: Vec::new(),
}
}
}
diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs
index 61c5c38d12..499feae388 100644
--- a/crates/assistant/src/assistant_panel.rs
+++ b/crates/assistant/src/assistant_panel.rs
@@ -1108,7 +1108,7 @@ impl AssistantPanel {
)
.track_scroll(scroll_handle)
.into_any_element();
- saved_conversations.layout(
+ saved_conversations.prepaint_as_root(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
@@ -2873,7 +2873,7 @@ impl InlineAssistant {
cx.theme().colors().text
},
font_family: settings.ui_font.family.clone(),
- font_features: settings.ui_font.features,
+ font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
diff --git a/crates/assistant/src/completion_provider/open_ai.rs b/crates/assistant/src/completion_provider/open_ai.rs
index f4c29a47e8..9a7398ef7f 100644
--- a/crates/assistant/src/completion_provider/open_ai.rs
+++ b/crates/assistant/src/completion_provider/open_ai.rs
@@ -140,14 +140,24 @@ impl OpenAiCompletionProvider {
messages: request
.messages
.into_iter()
- .map(|msg| RequestMessage {
- role: msg.role.into(),
- content: msg.content,
+ .map(|msg| match msg.role {
+ Role::User => RequestMessage::User {
+ content: msg.content,
+ },
+ Role::Assistant => RequestMessage::Assistant {
+ content: Some(msg.content),
+ tool_calls: Vec::new(),
+ },
+ Role::System => RequestMessage::System {
+ content: msg.content,
+ },
})
.collect(),
stream: true,
stop: request.stop,
temperature: request.temperature,
+ tools: Vec::new(),
+ tool_choice: None,
}
}
}
@@ -231,7 +241,7 @@ impl AuthenticationPrompt {
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
- font_features: settings.ui_font.features,
+ font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
diff --git a/crates/assistant/src/completion_provider/zed.rs b/crates/assistant/src/completion_provider/zed.rs
index 1ec852da19..ed84f1f7c6 100644
--- a/crates/assistant/src/completion_provider/zed.rs
+++ b/crates/assistant/src/completion_provider/zed.rs
@@ -123,6 +123,8 @@ impl ZedDotDevCompletionProvider {
.collect(),
stop: request.stop,
temperature: request.temperature,
+ tools: Vec::new(),
+ tool_choice: None,
};
self.client
diff --git a/crates/assistant2/Cargo.toml b/crates/assistant2/Cargo.toml
new file mode 100644
index 0000000000..82b43dbaa4
--- /dev/null
+++ b/crates/assistant2/Cargo.toml
@@ -0,0 +1,58 @@
+[package]
+name = "assistant2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+[lib]
+path = "src/assistant2.rs"
+
+[[example]]
+name = "assistant_example"
+path = "examples/assistant_example.rs"
+crate-type = ["bin"]
+
+[dependencies]
+anyhow.workspace = true
+assistant_tooling.workspace = true
+client.workspace = true
+editor.workspace = true
+feature_flags.workspace = true
+fs.workspace = true
+futures.workspace = true
+gpui.workspace = true
+language.workspace = true
+log.workspace = true
+nanoid.workspace = true
+open_ai.workspace = true
+project.workspace = true
+rich_text.workspace = true
+schemars.workspace = true
+semantic_index.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+settings.workspace = true
+theme.workspace = true
+ui.workspace = true
+util.workspace = true
+workspace.workspace = true
+
+[dev-dependencies]
+assets.workspace = true
+editor = { workspace = true, features = ["test-support"] }
+env_logger.workspace = true
+gpui = { workspace = true, features = ["test-support"] }
+language = { workspace = true, features = ["test-support"] }
+languages.workspace = true
+node_runtime.workspace = true
+project = { workspace = true, features = ["test-support"] }
+rand.workspace = true
+release_channel.workspace = true
+settings = { workspace = true, features = ["test-support"] }
+theme = { workspace = true, features = ["test-support"] }
+util = { workspace = true, features = ["test-support"] }
+workspace = { workspace = true, features = ["test-support"] }
+
+[lints]
+workspace = true
diff --git a/crates/assistant2/LICENSE-GPL b/crates/assistant2/LICENSE-GPL
new file mode 120000
index 0000000000..89e542f750
--- /dev/null
+++ b/crates/assistant2/LICENSE-GPL
@@ -0,0 +1 @@
+../../LICENSE-GPL
\ No newline at end of file
diff --git a/crates/assistant2/examples/assistant_example.rs b/crates/assistant2/examples/assistant_example.rs
new file mode 100644
index 0000000000..260c3bc8f9
--- /dev/null
+++ b/crates/assistant2/examples/assistant_example.rs
@@ -0,0 +1,129 @@
+use anyhow::Context as _;
+use assets::Assets;
+use assistant2::{tools::ProjectIndexTool, AssistantPanel};
+use assistant_tooling::ToolRegistry;
+use client::Client;
+use gpui::{actions, App, AppContext, KeyBinding, Task, View, WindowOptions};
+use language::LanguageRegistry;
+use project::Project;
+use semantic_index::{OpenAiEmbeddingModel, OpenAiEmbeddingProvider, SemanticIndex};
+use settings::{KeymapFile, DEFAULT_KEYMAP_PATH};
+use std::{
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use theme::LoadThemes;
+use ui::{div, prelude::*, Render};
+use util::{http::HttpClientWithUrl, ResultExt as _};
+
+actions!(example, [Quit]);
+
+fn main() {
+ let args: Vec = std::env::args().collect();
+
+ env_logger::init();
+ App::new().with_assets(Assets).run(|cx| {
+ cx.bind_keys(Some(KeyBinding::new("cmd-q", Quit, None)));
+ cx.on_action(|_: &Quit, cx: &mut AppContext| {
+ cx.quit();
+ });
+
+ if args.len() < 2 {
+ eprintln!(
+ "Usage: cargo run --example assistant_example -p assistant2 -- "
+ );
+ cx.quit();
+ return;
+ }
+
+ settings::init(cx);
+ language::init(cx);
+ Project::init_settings(cx);
+ editor::init(cx);
+ theme::init(LoadThemes::JustBase, cx);
+ Assets.load_fonts(cx).unwrap();
+ KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, cx).unwrap();
+ client::init_settings(cx);
+ release_channel::init("0.130.0", cx);
+
+ let client = Client::production(cx);
+ {
+ let client = client.clone();
+ cx.spawn(|cx| async move { client.authenticate_and_connect(false, &cx).await })
+ .detach_and_log_err(cx);
+ }
+ assistant2::init(client.clone(), cx);
+
+ let language_registry = Arc::new(LanguageRegistry::new(
+ Task::ready(()),
+ cx.background_executor().clone(),
+ ));
+ let node_runtime = node_runtime::RealNodeRuntime::new(client.http_client());
+ languages::init(language_registry.clone(), node_runtime, cx);
+
+ let http = Arc::new(HttpClientWithUrl::new("http://localhost:11434"));
+
+ let api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY not set");
+ let embedding_provider = OpenAiEmbeddingProvider::new(
+ http.clone(),
+ OpenAiEmbeddingModel::TextEmbedding3Small,
+ open_ai::OPEN_AI_API_URL.to_string(),
+ api_key,
+ );
+
+ cx.spawn(|mut cx| async move {
+ let mut semantic_index = SemanticIndex::new(
+ PathBuf::from("/tmp/semantic-index-db.mdb"),
+ Arc::new(embedding_provider),
+ &mut cx,
+ )
+ .await?;
+
+ let project_path = Path::new(&args[1]);
+ let project = Project::example([project_path], &mut cx).await;
+
+ cx.update(|cx| {
+ let fs = project.read(cx).fs().clone();
+
+ let project_index = semantic_index.project_index(project.clone(), cx);
+
+ let mut tool_registry = ToolRegistry::new();
+ tool_registry
+ .register(ProjectIndexTool::new(project_index.clone(), fs.clone()))
+ .context("failed to register ProjectIndexTool")
+ .log_err();
+
+ let tool_registry = Arc::new(tool_registry);
+
+ cx.open_window(WindowOptions::default(), |cx| {
+ cx.new_view(|cx| Example::new(language_registry, tool_registry, cx))
+ });
+ cx.activate(true);
+ })
+ })
+ .detach_and_log_err(cx);
+ })
+}
+
+struct Example {
+ assistant_panel: View,
+}
+
+impl Example {
+ fn new(
+ language_registry: Arc,
+ tool_registry: Arc,
+ cx: &mut ViewContext,
+ ) -> Self {
+ Self {
+ assistant_panel: cx
+ .new_view(|cx| AssistantPanel::new(language_registry, tool_registry, cx)),
+ }
+ }
+}
+
+impl Render for Example {
+ fn render(&mut self, _cx: &mut ViewContext) -> impl ui::prelude::IntoElement {
+ div().size_full().child(self.assistant_panel.clone())
+ }
+}
diff --git a/crates/assistant2/examples/chat_with_functions.rs b/crates/assistant2/examples/chat_with_functions.rs
new file mode 100644
index 0000000000..6c2870e680
--- /dev/null
+++ b/crates/assistant2/examples/chat_with_functions.rs
@@ -0,0 +1,241 @@
+//! This example creates a basic Chat UI with a function for rolling a die.
+
+use anyhow::{Context as _, Result};
+use assets::Assets;
+use assistant2::AssistantPanel;
+use assistant_tooling::{LanguageModelTool, ToolRegistry};
+use client::Client;
+use gpui::{actions, AnyElement, App, AppContext, KeyBinding, Task, View, WindowOptions};
+use language::LanguageRegistry;
+use project::Project;
+use rand::Rng;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings::{KeymapFile, DEFAULT_KEYMAP_PATH};
+use std::sync::Arc;
+use theme::LoadThemes;
+use ui::{div, prelude::*, Render};
+use util::ResultExt as _;
+
+actions!(example, [Quit]);
+
+struct RollDiceTool {}
+
+impl RollDiceTool {
+ fn new() -> Self {
+ Self {}
+ }
+}
+
+#[derive(Serialize, Deserialize, JsonSchema, Clone)]
+#[serde(rename_all = "snake_case")]
+enum Die {
+ D6 = 6,
+ D20 = 20,
+}
+
+impl Die {
+ fn into_str(&self) -> &'static str {
+ match self {
+ Die::D6 => "d6",
+ Die::D20 => "d20",
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, JsonSchema, Clone)]
+struct DiceParams {
+ /// The number of dice to roll.
+ num_dice: u8,
+ /// Which die to roll. Defaults to a d6 if not provided.
+ die_type: Option,
+}
+
+#[derive(Serialize, Deserialize)]
+struct DieRoll {
+ die: Die,
+ roll: u8,
+}
+
+impl DieRoll {
+ fn render(&self) -> AnyElement {
+ match self.die {
+ Die::D6 => {
+ let face = match self.roll {
+ 6 => div().child("⚅"),
+ 5 => div().child("⚄"),
+ 4 => div().child("⚃"),
+ 3 => div().child("⚂"),
+ 2 => div().child("⚁"),
+ 1 => div().child("⚀"),
+ _ => div().child("😅"),
+ };
+ face.text_3xl().into_any_element()
+ }
+ _ => div()
+ .child(format!("{}", self.roll))
+ .text_3xl()
+ .into_any_element(),
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+struct DiceRoll {
+ rolls: Vec,
+}
+
+pub struct DiceView {
+ result: Result,
+}
+
+impl Render for DiceView {
+ fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement {
+ let output = match &self.result {
+ Ok(output) => output,
+ Err(_) => return "Somehow dice failed 🎲".into_any_element(),
+ };
+
+ h_flex()
+ .children(
+ output
+ .rolls
+ .iter()
+ .map(|roll| div().p_2().child(roll.render())),
+ )
+ .into_any_element()
+ }
+}
+
+impl LanguageModelTool for RollDiceTool {
+ type Input = DiceParams;
+ type Output = DiceRoll;
+ type View = DiceView;
+
+ fn name(&self) -> String {
+ "roll_dice".to_string()
+ }
+
+ fn description(&self) -> String {
+ "Rolls N many dice and returns the results.".to_string()
+ }
+
+ fn execute(&self, input: &Self::Input, _cx: &AppContext) -> Task> {
+ let rolls = (0..input.num_dice)
+ .map(|_| {
+ let die_type = input.die_type.as_ref().unwrap_or(&Die::D6).clone();
+
+ DieRoll {
+ die: die_type.clone(),
+ roll: rand::thread_rng().gen_range(1..=die_type as u8),
+ }
+ })
+ .collect();
+
+ return Task::ready(Ok(DiceRoll { rolls }));
+ }
+
+ fn new_view(
+ _tool_call_id: String,
+ _input: Self::Input,
+ result: Result,
+ cx: &mut WindowContext,
+ ) -> gpui::View {
+ cx.new_view(|_cx| DiceView { result })
+ }
+
+ fn format(_: &Self::Input, output: &Result) -> String {
+ let output = match output {
+ Ok(output) => output,
+ Err(_) => return "Somehow dice failed 🎲".to_string(),
+ };
+
+ let mut result = String::new();
+ for roll in &output.rolls {
+ let die = &roll.die;
+ result.push_str(&format!("{}: {}\n", die.into_str(), roll.roll));
+ }
+ result
+ }
+}
+
+fn main() {
+ env_logger::init();
+ App::new().with_assets(Assets).run(|cx| {
+ cx.bind_keys(Some(KeyBinding::new("cmd-q", Quit, None)));
+ cx.on_action(|_: &Quit, cx: &mut AppContext| {
+ cx.quit();
+ });
+
+ settings::init(cx);
+ language::init(cx);
+ Project::init_settings(cx);
+ editor::init(cx);
+ theme::init(LoadThemes::JustBase, cx);
+ Assets.load_fonts(cx).unwrap();
+ KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, cx).unwrap();
+ client::init_settings(cx);
+ release_channel::init("0.130.0", cx);
+
+ let client = Client::production(cx);
+ {
+ let client = client.clone();
+ cx.spawn(|cx| async move { client.authenticate_and_connect(false, &cx).await })
+ .detach_and_log_err(cx);
+ }
+ assistant2::init(client.clone(), cx);
+
+ let language_registry = Arc::new(LanguageRegistry::new(
+ Task::ready(()),
+ cx.background_executor().clone(),
+ ));
+ let node_runtime = node_runtime::RealNodeRuntime::new(client.http_client());
+ languages::init(language_registry.clone(), node_runtime, cx);
+
+ cx.spawn(|cx| async move {
+ cx.update(|cx| {
+ let mut tool_registry = ToolRegistry::new();
+ tool_registry
+ .register(RollDiceTool::new())
+ .context("failed to register DummyTool")
+ .log_err();
+
+ let tool_registry = Arc::new(tool_registry);
+
+ println!("Tools registered");
+ for definition in tool_registry.definitions() {
+ println!("{}", definition);
+ }
+
+ cx.open_window(WindowOptions::default(), |cx| {
+ cx.new_view(|cx| Example::new(language_registry, tool_registry, cx))
+ });
+ cx.activate(true);
+ })
+ })
+ .detach_and_log_err(cx);
+ })
+}
+
+struct Example {
+ assistant_panel: View,
+}
+
+impl Example {
+ fn new(
+ language_registry: Arc,
+ tool_registry: Arc,
+ cx: &mut ViewContext,
+ ) -> Self {
+ Self {
+ assistant_panel: cx
+ .new_view(|cx| AssistantPanel::new(language_registry, tool_registry, cx)),
+ }
+ }
+}
+
+impl Render for Example {
+ fn render(&mut self, _cx: &mut ViewContext) -> impl ui::prelude::IntoElement {
+ div().size_full().child(self.assistant_panel.clone())
+ }
+}
diff --git a/crates/assistant2/examples/file_interactions.rs b/crates/assistant2/examples/file_interactions.rs
new file mode 100644
index 0000000000..c810085b86
--- /dev/null
+++ b/crates/assistant2/examples/file_interactions.rs
@@ -0,0 +1,221 @@
+//! This example creates a basic Chat UI for interacting with the filesystem.
+
+use anyhow::{Context as _, Result};
+use assets::Assets;
+use assistant2::AssistantPanel;
+use assistant_tooling::{LanguageModelTool, ToolRegistry};
+use client::Client;
+use fs::Fs;
+use futures::StreamExt;
+use gpui::{actions, App, AppContext, KeyBinding, Task, View, WindowOptions};
+use language::LanguageRegistry;
+use project::Project;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings::{KeymapFile, DEFAULT_KEYMAP_PATH};
+use std::path::PathBuf;
+use std::sync::Arc;
+use theme::LoadThemes;
+use ui::{div, prelude::*, Render};
+use util::ResultExt as _;
+
+actions!(example, [Quit]);
+
+struct FileBrowserTool {
+ fs: Arc,
+ root_dir: PathBuf,
+}
+
+impl FileBrowserTool {
+ fn new(fs: Arc, root_dir: PathBuf) -> Self {
+ Self { fs, root_dir }
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+struct FileBrowserParams {
+ command: FileBrowserCommand,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+enum FileBrowserCommand {
+ Ls { path: PathBuf },
+ Cat { path: PathBuf },
+}
+
+#[derive(Serialize, Deserialize)]
+enum FileBrowserOutput {
+ Ls { entries: Vec },
+ Cat { content: String },
+}
+
+pub struct FileBrowserView {
+ result: Result,
+}
+
+impl Render for FileBrowserView {
+ fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
+ let Ok(output) = self.result.as_ref() else {
+ return h_flex().child("Failed to perform operation");
+ };
+
+ match output {
+ FileBrowserOutput::Ls { entries } => v_flex().children(
+ entries
+ .into_iter()
+ .map(|entry| h_flex().text_ui(cx).child(entry.clone())),
+ ),
+ FileBrowserOutput::Cat { content } => h_flex().child(content.clone()),
+ }
+ }
+}
+
+impl LanguageModelTool for FileBrowserTool {
+ type Input = FileBrowserParams;
+ type Output = FileBrowserOutput;
+ type View = FileBrowserView;
+
+ fn name(&self) -> String {
+ "file_browser".to_string()
+ }
+
+ fn description(&self) -> String {
+ "A tool for browsing the filesystem.".to_string()
+ }
+
+ fn execute(&self, input: &Self::Input, cx: &AppContext) -> Task> {
+ cx.spawn({
+ let fs = self.fs.clone();
+ let root_dir = self.root_dir.clone();
+ let input = input.clone();
+ |_cx| async move {
+ match input.command {
+ FileBrowserCommand::Ls { path } => {
+ let path = root_dir.join(path);
+
+ let mut output = fs.read_dir(&path).await?;
+
+ let mut entries = Vec::new();
+ while let Some(entry) = output.next().await {
+ let entry = entry?;
+ entries.push(entry.display().to_string());
+ }
+
+ Ok(FileBrowserOutput::Ls { entries })
+ }
+ FileBrowserCommand::Cat { path } => {
+ let path = root_dir.join(path);
+
+ let output = fs.load(&path).await?;
+
+ Ok(FileBrowserOutput::Cat { content: output })
+ }
+ }
+ }
+ })
+ }
+
+ fn new_view(
+ _tool_call_id: String,
+ _input: Self::Input,
+ result: Result,
+ cx: &mut WindowContext,
+ ) -> gpui::View {
+ cx.new_view(|_cx| FileBrowserView { result })
+ }
+
+ fn format(_input: &Self::Input, output: &Result) -> String {
+ let Ok(output) = output else {
+ return "Failed to perform command: {input:?}".to_string();
+ };
+
+ match output {
+ FileBrowserOutput::Ls { entries } => entries.join("\n"),
+ FileBrowserOutput::Cat { content } => content.to_owned(),
+ }
+ }
+}
+
+fn main() {
+ env_logger::init();
+ App::new().with_assets(Assets).run(|cx| {
+ cx.bind_keys(Some(KeyBinding::new("cmd-q", Quit, None)));
+ cx.on_action(|_: &Quit, cx: &mut AppContext| {
+ cx.quit();
+ });
+
+ settings::init(cx);
+ language::init(cx);
+ Project::init_settings(cx);
+ editor::init(cx);
+ theme::init(LoadThemes::JustBase, cx);
+ Assets.load_fonts(cx).unwrap();
+ KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, cx).unwrap();
+ client::init_settings(cx);
+ release_channel::init("0.130.0", cx);
+
+ let client = Client::production(cx);
+ {
+ let client = client.clone();
+ cx.spawn(|cx| async move { client.authenticate_and_connect(false, &cx).await })
+ .detach_and_log_err(cx);
+ }
+ assistant2::init(client.clone(), cx);
+
+ let language_registry = Arc::new(LanguageRegistry::new(
+ Task::ready(()),
+ cx.background_executor().clone(),
+ ));
+ let node_runtime = node_runtime::RealNodeRuntime::new(client.http_client());
+ languages::init(language_registry.clone(), node_runtime, cx);
+
+ cx.spawn(|cx| async move {
+ cx.update(|cx| {
+ let fs = Arc::new(fs::RealFs::new(None));
+ let cwd = std::env::current_dir().expect("Failed to get current working directory");
+
+ let mut tool_registry = ToolRegistry::new();
+ tool_registry
+ .register(FileBrowserTool::new(fs, cwd))
+ .context("failed to register FileBrowserTool")
+ .log_err();
+
+ let tool_registry = Arc::new(tool_registry);
+
+ println!("Tools registered");
+ for definition in tool_registry.definitions() {
+ println!("{}", definition);
+ }
+
+ cx.open_window(WindowOptions::default(), |cx| {
+ cx.new_view(|cx| Example::new(language_registry, tool_registry, cx))
+ });
+ cx.activate(true);
+ })
+ })
+ .detach_and_log_err(cx);
+ })
+}
+
+struct Example {
+ assistant_panel: View,
+}
+
+impl Example {
+ fn new(
+ language_registry: Arc,
+ tool_registry: Arc,
+ cx: &mut ViewContext,
+ ) -> Self {
+ Self {
+ assistant_panel: cx
+ .new_view(|cx| AssistantPanel::new(language_registry, tool_registry, cx)),
+ }
+ }
+}
+
+impl Render for Example {
+ fn render(&mut self, _cx: &mut ViewContext) -> impl ui::prelude::IntoElement {
+ div().size_full().child(self.assistant_panel.clone())
+ }
+}
diff --git a/crates/assistant2/src/assistant2.rs b/crates/assistant2/src/assistant2.rs
new file mode 100644
index 0000000000..8204dc3654
--- /dev/null
+++ b/crates/assistant2/src/assistant2.rs
@@ -0,0 +1,962 @@
+mod assistant_settings;
+mod completion_provider;
+pub mod tools;
+
+use anyhow::{Context, Result};
+use assistant_tooling::{ToolFunctionCall, ToolRegistry};
+use client::{proto, Client};
+use completion_provider::*;
+use editor::Editor;
+use feature_flags::FeatureFlagAppExt as _;
+use futures::{channel::oneshot, future::join_all, Future, FutureExt, StreamExt};
+use gpui::{
+ list, prelude::*, AnyElement, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
+ FocusableView, Global, ListAlignment, ListState, Model, Render, Task, View, WeakView,
+};
+use language::{language_settings::SoftWrap, LanguageRegistry};
+use open_ai::{FunctionContent, ToolCall, ToolCallContent};
+use project::Fs;
+use rich_text::RichText;
+use semantic_index::{CloudEmbeddingProvider, ProjectIndex, SemanticIndex};
+use serde::Deserialize;
+use settings::Settings;
+use std::{cmp, sync::Arc};
+use theme::ThemeSettings;
+use tools::ProjectIndexTool;
+use ui::{popover_menu, prelude::*, ButtonLike, CollapsibleContainer, Color, ContextMenu, Tooltip};
+use util::{paths::EMBEDDINGS_DIR, ResultExt};
+use workspace::{
+ dock::{DockPosition, Panel, PanelEvent},
+ Workspace,
+};
+
+pub use assistant_settings::AssistantSettings;
+
+const MAX_COMPLETION_CALLS_PER_SUBMISSION: usize = 5;
+
+#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
+pub struct Submit(SubmitMode);
+
+/// There are multiple different ways to submit a model request, represented by this enum.
+#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
+pub enum SubmitMode {
+ /// Only include the conversation.
+ Simple,
+ /// Send the current file as context.
+ CurrentFile,
+ /// Search the codebase and send relevant excerpts.
+ Codebase,
+}
+
+gpui::actions!(assistant2, [Cancel, ToggleFocus]);
+gpui::impl_actions!(assistant2, [Submit]);
+
+pub fn init(client: Arc, cx: &mut AppContext) {
+ AssistantSettings::register(cx);
+
+ cx.spawn(|mut cx| {
+ let client = client.clone();
+ async move {
+ let embedding_provider = CloudEmbeddingProvider::new(client.clone());
+ let semantic_index = SemanticIndex::new(
+ EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
+ Arc::new(embedding_provider),
+ &mut cx,
+ )
+ .await?;
+ cx.update(|cx| cx.set_global(semantic_index))
+ }
+ })
+ .detach();
+
+ cx.set_global(CompletionProvider::new(CloudCompletionProvider::new(
+ client,
+ )));
+
+ cx.observe_new_views(
+ |workspace: &mut Workspace, _cx: &mut ViewContext| {
+ workspace.register_action(|workspace, _: &ToggleFocus, cx| {
+ workspace.toggle_panel_focus::(cx);
+ });
+ },
+ )
+ .detach();
+}
+
+pub fn enabled(cx: &AppContext) -> bool {
+ cx.is_staff()
+}
+
+pub struct AssistantPanel {
+ chat: View,
+ width: Option,
+}
+
+impl AssistantPanel {
+ pub fn load(
+ workspace: WeakView,
+ cx: AsyncWindowContext,
+ ) -> Task>> {
+ cx.spawn(|mut cx| async move {
+ let (app_state, project) = workspace.update(&mut cx, |workspace, _| {
+ (workspace.app_state().clone(), workspace.project().clone())
+ })?;
+
+ cx.new_view(|cx| {
+ // todo!("this will panic if the semantic index failed to load or has not loaded yet")
+ let project_index = cx.update_global(|semantic_index: &mut SemanticIndex, cx| {
+ semantic_index.project_index(project.clone(), cx)
+ });
+
+ let mut tool_registry = ToolRegistry::new();
+ tool_registry
+ .register(ProjectIndexTool::new(
+ project_index.clone(),
+ app_state.fs.clone(),
+ ))
+ .context("failed to register ProjectIndexTool")
+ .log_err();
+
+ let tool_registry = Arc::new(tool_registry);
+
+ Self::new(app_state.languages.clone(), tool_registry, cx)
+ })
+ })
+ }
+
+ pub fn new(
+ language_registry: Arc,
+ tool_registry: Arc,
+ cx: &mut ViewContext,
+ ) -> Self {
+ let chat = cx.new_view(|cx| {
+ AssistantChat::new(language_registry.clone(), tool_registry.clone(), cx)
+ });
+
+ Self { width: None, chat }
+ }
+}
+
+impl Render for AssistantPanel {
+ fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
+ div()
+ .size_full()
+ .v_flex()
+ .p_2()
+ .bg(cx.theme().colors().background)
+ .child(self.chat.clone())
+ }
+}
+
+impl Panel for AssistantPanel {
+ fn persistent_name() -> &'static str {
+ "AssistantPanelv2"
+ }
+
+ fn position(&self, _cx: &WindowContext) -> workspace::dock::DockPosition {
+ // todo!("Add a setting / use assistant settings")
+ DockPosition::Right
+ }
+
+ fn position_is_valid(&self, position: workspace::dock::DockPosition) -> bool {
+ matches!(position, DockPosition::Right)
+ }
+
+ fn set_position(&mut self, _: workspace::dock::DockPosition, _: &mut ViewContext) {
+ // Do nothing until we have a setting for this
+ }
+
+ fn size(&self, _cx: &WindowContext) -> Pixels {
+ self.width.unwrap_or(px(400.))
+ }
+
+ fn set_size(&mut self, size: Option, cx: &mut ViewContext) {
+ self.width = size;
+ cx.notify();
+ }
+
+ fn icon(&self, _cx: &WindowContext) -> Option {
+ Some(IconName::Ai)
+ }
+
+ fn icon_tooltip(&self, _: &WindowContext) -> Option<&'static str> {
+ Some("Assistant Panel ✨")
+ }
+
+ fn toggle_action(&self) -> Box {
+ Box::new(ToggleFocus)
+ }
+}
+
+impl EventEmitter for AssistantPanel {}
+
+impl FocusableView for AssistantPanel {
+ fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+ self.chat
+ .read(cx)
+ .messages
+ .iter()
+ .rev()
+ .find_map(|msg| msg.focus_handle(cx))
+ .expect("no user message in chat")
+ }
+}
+
+struct AssistantChat {
+ model: String,
+ messages: Vec,
+ list_state: ListState,
+ language_registry: Arc,
+ next_message_id: MessageId,
+ pending_completion: Option>,
+ tool_registry: Arc,
+}
+
+impl AssistantChat {
+ fn new(
+ language_registry: Arc,
+ tool_registry: Arc,
+ cx: &mut ViewContext,
+ ) -> Self {
+ let model = CompletionProvider::get(cx).default_model();
+ let view = cx.view().downgrade();
+ let list_state = ListState::new(
+ 0,
+ ListAlignment::Bottom,
+ px(1024.),
+ move |ix, cx: &mut WindowContext| {
+ view.update(cx, |this, cx| this.render_message(ix, cx))
+ .unwrap()
+ },
+ );
+
+ let mut this = Self {
+ model,
+ messages: Vec::new(),
+ list_state,
+ language_registry,
+ next_message_id: MessageId(0),
+ pending_completion: None,
+ tool_registry,
+ };
+ this.push_new_user_message(true, cx);
+ this
+ }
+
+ fn focused_message_id(&self, cx: &WindowContext) -> Option {
+ self.messages.iter().find_map(|message| match message {
+ ChatMessage::User(message) => message
+ .body
+ .focus_handle(cx)
+ .contains_focused(cx)
+ .then_some(message.id),
+ ChatMessage::Assistant(_) => None,
+ })
+ }
+
+ fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) {
+ if self.pending_completion.take().is_none() {
+ cx.propagate();
+ return;
+ }
+
+ if let Some(ChatMessage::Assistant(message)) = self.messages.last() {
+ if message.body.text.is_empty() {
+ self.pop_message(cx);
+ } else {
+ self.push_new_user_message(false, cx);
+ }
+ }
+ }
+
+ fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext) {
+ let Some(focused_message_id) = self.focused_message_id(cx) else {
+ log::error!("unexpected state: no user message editor is focused.");
+ return;
+ };
+
+ self.truncate_messages(focused_message_id, cx);
+
+ let mode = *mode;
+ self.pending_completion = Some(cx.spawn(move |this, mut cx| async move {
+ Self::request_completion(
+ this.clone(),
+ mode,
+ MAX_COMPLETION_CALLS_PER_SUBMISSION,
+ &mut cx,
+ )
+ .await
+ .log_err();
+
+ this.update(&mut cx, |this, cx| {
+ let focus = this
+ .user_message(focused_message_id)
+ .body
+ .focus_handle(cx)
+ .contains_focused(cx);
+ this.push_new_user_message(focus, cx);
+ this.pending_completion = None;
+ })
+ .context("Failed to push new user message")
+ .log_err();
+ }));
+ }
+
+ async fn request_completion(
+ this: WeakView,
+ mode: SubmitMode,
+ limit: usize,
+ cx: &mut AsyncWindowContext,
+ ) -> Result<()> {
+ let mut call_count = 0;
+ loop {
+ let complete = async {
+ let completion = this.update(cx, |this, cx| {
+ this.push_new_assistant_message(cx);
+
+ let definitions = if call_count < limit
+ && matches!(mode, SubmitMode::Codebase | SubmitMode::Simple)
+ {
+ this.tool_registry.definitions()
+ } else {
+ &[]
+ };
+ call_count += 1;
+
+ let messages = this.completion_messages(cx);
+
+ CompletionProvider::get(cx).complete(
+ this.model.clone(),
+ messages,
+ Vec::new(),
+ 1.0,
+ definitions,
+ )
+ });
+
+ let mut stream = completion?.await?;
+ let mut body = String::new();
+ while let Some(delta) = stream.next().await {
+ let delta = delta?;
+ this.update(cx, |this, cx| {
+ if let Some(ChatMessage::Assistant(AssistantMessage {
+ body: message_body,
+ tool_calls: message_tool_calls,
+ ..
+ })) = this.messages.last_mut()
+ {
+ if let Some(content) = &delta.content {
+ body.push_str(content);
+ }
+
+ for tool_call in delta.tool_calls {
+ let index = tool_call.index as usize;
+ if index >= message_tool_calls.len() {
+ message_tool_calls.resize_with(index + 1, Default::default);
+ }
+ let call = &mut message_tool_calls[index];
+
+ if let Some(id) = &tool_call.id {
+ call.id.push_str(id);
+ }
+
+ match tool_call.variant {
+ Some(proto::tool_call_delta::Variant::Function(tool_call)) => {
+ if let Some(name) = &tool_call.name {
+ call.name.push_str(name);
+ }
+ if let Some(arguments) = &tool_call.arguments {
+ call.arguments.push_str(arguments);
+ }
+ }
+ None => {}
+ }
+ }
+
+ *message_body =
+ RichText::new(body.clone(), &[], &this.language_registry);
+ cx.notify();
+ } else {
+ unreachable!()
+ }
+ })?;
+ }
+
+ anyhow::Ok(())
+ }
+ .await;
+
+ let mut tool_tasks = Vec::new();
+ this.update(cx, |this, cx| {
+ if let Some(ChatMessage::Assistant(AssistantMessage {
+ error: message_error,
+ tool_calls,
+ ..
+ })) = this.messages.last_mut()
+ {
+ if let Err(error) = complete {
+ message_error.replace(SharedString::from(error.to_string()));
+ cx.notify();
+ } else {
+ for tool_call in tool_calls.iter() {
+ tool_tasks.push(this.tool_registry.call(tool_call, cx));
+ }
+ }
+ }
+ })?;
+
+ if tool_tasks.is_empty() {
+ return Ok(());
+ }
+
+ let tools = join_all(tool_tasks.into_iter()).await;
+ // If the WindowContext went away for any tool's view we don't include it
+ // especially since the below call would fail for the same reason.
+ let tools = tools.into_iter().filter_map(|tool| tool.ok()).collect();
+
+ this.update(cx, |this, cx| {
+ if let Some(ChatMessage::Assistant(AssistantMessage { tool_calls, .. })) =
+ this.messages.last_mut()
+ {
+ *tool_calls = tools;
+ cx.notify();
+ }
+ })?;
+ }
+ }
+
+ fn user_message(&mut self, message_id: MessageId) -> &mut UserMessage {
+ self.messages
+ .iter_mut()
+ .find_map(|message| match message {
+ ChatMessage::User(user_message) if user_message.id == message_id => {
+ Some(user_message)
+ }
+ _ => None,
+ })
+ .expect("User message not found")
+ }
+
+ fn push_new_user_message(&mut self, focus: bool, cx: &mut ViewContext) {
+ let id = self.next_message_id.post_inc();
+ let body = cx.new_view(|cx| {
+ let mut editor = Editor::auto_height(80, cx);
+ editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
+ if focus {
+ cx.focus_self();
+ }
+ editor
+ });
+ let message = ChatMessage::User(UserMessage {
+ id,
+ body,
+ contexts: Vec::new(),
+ });
+ self.push_message(message, cx);
+ }
+
+ fn push_new_assistant_message(&mut self, cx: &mut ViewContext) {
+ let message = ChatMessage::Assistant(AssistantMessage {
+ id: self.next_message_id.post_inc(),
+ body: RichText::default(),
+ tool_calls: Vec::new(),
+ error: None,
+ });
+ self.push_message(message, cx);
+ }
+
+ fn push_message(&mut self, message: ChatMessage, cx: &mut ViewContext) {
+ let old_len = self.messages.len();
+ let focus_handle = Some(message.focus_handle(cx));
+ self.messages.push(message);
+ self.list_state
+ .splice_focusable(old_len..old_len, focus_handle);
+ cx.notify();
+ }
+
+ fn pop_message(&mut self, cx: &mut ViewContext) {
+ if self.messages.is_empty() {
+ return;
+ }
+
+ self.messages.pop();
+ self.list_state
+ .splice(self.messages.len()..self.messages.len() + 1, 0);
+ cx.notify();
+ }
+
+ fn truncate_messages(&mut self, last_message_id: MessageId, cx: &mut ViewContext) {
+ if let Some(index) = self.messages.iter().position(|message| match message {
+ ChatMessage::User(message) => message.id == last_message_id,
+ ChatMessage::Assistant(message) => message.id == last_message_id,
+ }) {
+ self.list_state.splice(index + 1..self.messages.len(), 0);
+ self.messages.truncate(index + 1);
+ cx.notify();
+ }
+ }
+
+ fn render_error(
+ &self,
+ error: Option,
+ _ix: usize,
+ cx: &mut ViewContext,
+ ) -> AnyElement {
+ let theme = cx.theme();
+
+ if let Some(error) = error {
+ div()
+ .py_1()
+ .px_2()
+ .neg_mx_1()
+ .rounded_md()
+ .border()
+ .border_color(theme.status().error_border)
+ // .bg(theme.status().error_background)
+ .text_color(theme.status().error)
+ .child(error.clone())
+ .into_any_element()
+ } else {
+ div().into_any_element()
+ }
+ }
+
+ fn render_message(&self, ix: usize, cx: &mut ViewContext) -> AnyElement {
+ let is_last = ix == self.messages.len() - 1;
+
+ match &self.messages[ix] {
+ ChatMessage::User(UserMessage {
+ body,
+ contexts: _contexts,
+ ..
+ }) => div()
+ .when(!is_last, |element| element.mb_2())
+ .child(div().p_2().child(Label::new("You").color(Color::Default)))
+ .child(
+ div()
+ .on_action(cx.listener(Self::submit))
+ .p_2()
+ .text_color(cx.theme().colors().editor_foreground)
+ .font(ThemeSettings::get_global(cx).buffer_font.clone())
+ .bg(cx.theme().colors().editor_background)
+ .child(body.clone()), // .children(contexts.iter().map(|context| context.render(cx))),
+ )
+ .into_any(),
+ ChatMessage::Assistant(AssistantMessage {
+ id,
+ body,
+ error,
+ tool_calls,
+ ..
+ }) => {
+ let assistant_body = if body.text.is_empty() && !tool_calls.is_empty() {
+ div()
+ } else {
+ div().p_2().child(body.element(ElementId::from(id.0), cx))
+ };
+
+ div()
+ .when(!is_last, |element| element.mb_2())
+ .child(
+ div()
+ .p_2()
+ .child(Label::new("Assistant").color(Color::Modified)),
+ )
+ .child(assistant_body)
+ .child(self.render_error(error.clone(), ix, cx))
+ .children(tool_calls.iter().map(|tool_call| {
+ let result = &tool_call.result;
+ let name = tool_call.name.clone();
+ match result {
+ Some(result) => {
+ div().p_2().child(result.into_any_element(&name)).into_any()
+ }
+ None => div()
+ .p_2()
+ .child(Label::new(name).color(Color::Modified))
+ .child("Running...")
+ .into_any(),
+ }
+ }))
+ .into_any()
+ }
+ }
+ }
+
+ fn completion_messages(&self, cx: &mut WindowContext) -> Vec {
+ let mut completion_messages = Vec::new();
+
+ for message in &self.messages {
+ match message {
+ ChatMessage::User(UserMessage { body, contexts, .. }) => {
+ // setup context for model
+ contexts.iter().for_each(|context| {
+ completion_messages.extend(context.completion_messages(cx))
+ });
+
+ // Show user's message last so that the assistant is grounded in the user's request
+ completion_messages.push(CompletionMessage::User {
+ content: body.read(cx).text(cx),
+ });
+ }
+ ChatMessage::Assistant(AssistantMessage {
+ body, tool_calls, ..
+ }) => {
+ // In no case do we want to send an empty message. This shouldn't happen, but we might as well
+ // not break the Chat API if it does.
+ if body.text.is_empty() && tool_calls.is_empty() {
+ continue;
+ }
+
+ let tool_calls_from_assistant = tool_calls
+ .iter()
+ .map(|tool_call| ToolCall {
+ content: ToolCallContent::Function {
+ function: FunctionContent {
+ name: tool_call.name.clone(),
+ arguments: tool_call.arguments.clone(),
+ },
+ },
+ id: tool_call.id.clone(),
+ })
+ .collect();
+
+ completion_messages.push(CompletionMessage::Assistant {
+ content: Some(body.text.to_string()),
+ tool_calls: tool_calls_from_assistant,
+ });
+
+ for tool_call in tool_calls {
+ // todo!(): we should not be sending when the tool is still running / has no result
+ // For now I'm going to have to assume we send an empty string because otherwise
+ // the Chat API will break -- there is a required message for every tool call by ID
+ let content = match &tool_call.result {
+ Some(result) => result.format(&tool_call.name),
+ None => "".to_string(),
+ };
+
+ completion_messages.push(CompletionMessage::Tool {
+ content,
+ tool_call_id: tool_call.id.clone(),
+ });
+ }
+ }
+ }
+ }
+
+ completion_messages
+ }
+
+ fn render_model_dropdown(&self, cx: &mut ViewContext) -> impl IntoElement {
+ let this = cx.view().downgrade();
+ div().h_flex().justify_end().child(
+ div().w_32().child(
+ popover_menu("user-menu")
+ .menu(move |cx| {
+ ContextMenu::build(cx, |mut menu, cx| {
+ for model in CompletionProvider::get(cx).available_models() {
+ menu = menu.custom_entry(
+ {
+ let model = model.clone();
+ move |_| Label::new(model.clone()).into_any_element()
+ },
+ {
+ let this = this.clone();
+ move |cx| {
+ _ = this.update(cx, |this, cx| {
+ this.model = model.clone();
+ cx.notify();
+ });
+ }
+ },
+ );
+ }
+ menu
+ })
+ .into()
+ })
+ .trigger(
+ ButtonLike::new("active-model")
+ .child(
+ h_flex()
+ .w_full()
+ .gap_0p5()
+ .child(
+ div()
+ .overflow_x_hidden()
+ .flex_grow()
+ .whitespace_nowrap()
+ .child(Label::new(self.model.clone())),
+ )
+ .child(div().child(
+ Icon::new(IconName::ChevronDown).color(Color::Muted),
+ )),
+ )
+ .style(ButtonStyle::Subtle)
+ .tooltip(move |cx| Tooltip::text("Change Model", cx)),
+ )
+ .anchor(gpui::AnchorCorner::TopRight),
+ ),
+ )
+ }
+}
+
+impl Render for AssistantChat {
+ fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
+ div()
+ .relative()
+ .flex_1()
+ .v_flex()
+ .key_context("AssistantChat")
+ .on_action(cx.listener(Self::cancel))
+ .text_color(Color::Default.color(cx))
+ .child(self.render_model_dropdown(cx))
+ .child(list(self.list_state.clone()).flex_1())
+ }
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+struct MessageId(usize);
+
+impl MessageId {
+ fn post_inc(&mut self) -> Self {
+ let id = *self;
+ self.0 += 1;
+ id
+ }
+}
+
+enum ChatMessage {
+ User(UserMessage),
+ Assistant(AssistantMessage),
+}
+
+impl ChatMessage {
+ fn focus_handle(&self, cx: &AppContext) -> Option {
+ match self {
+ ChatMessage::User(UserMessage { body, .. }) => Some(body.focus_handle(cx)),
+ ChatMessage::Assistant(_) => None,
+ }
+ }
+}
+
+struct UserMessage {
+ id: MessageId,
+ body: View,
+ contexts: Vec,
+}
+
+struct AssistantMessage {
+ id: MessageId,
+ body: RichText,
+ tool_calls: Vec,
+ error: Option,
+}
+
+// Since we're swapping out for direct query usage, we might not need to use this injected context
+// It will be useful though for when the user _definitely_ wants the model to see a specific file,
+// query, error, etc.
+#[allow(dead_code)]
+enum AssistantContext {
+ Codebase(View),
+}
+
+#[allow(dead_code)]
+struct CodebaseExcerpt {
+ element_id: ElementId,
+ path: SharedString,
+ text: SharedString,
+ score: f32,
+ expanded: bool,
+}
+
+impl AssistantContext {
+ #[allow(dead_code)]
+ fn render(&self, _cx: &mut ViewContext) -> AnyElement {
+ match self {
+ AssistantContext::Codebase(context) => context.clone().into_any_element(),
+ }
+ }
+
+ fn completion_messages(&self, cx: &WindowContext) -> Vec {
+ match self {
+ AssistantContext::Codebase(context) => context.read(cx).completion_messages(),
+ }
+ }
+}
+
+enum CodebaseContext {
+ Pending { _task: Task<()> },
+ Done(Result>),
+}
+
+impl CodebaseContext {
+ fn toggle_expanded(&mut self, element_id: ElementId, cx: &mut ViewContext) {
+ if let CodebaseContext::Done(Ok(excerpts)) = self {
+ if let Some(excerpt) = excerpts
+ .iter_mut()
+ .find(|excerpt| excerpt.element_id == element_id)
+ {
+ excerpt.expanded = !excerpt.expanded;
+ cx.notify();
+ }
+ }
+ }
+}
+
+impl Render for CodebaseContext {
+ fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement {
+ match self {
+ CodebaseContext::Pending { .. } => div()
+ .h_flex()
+ .items_center()
+ .gap_1()
+ .child(Icon::new(IconName::Ai).color(Color::Muted).into_element())
+ .child("Searching codebase..."),
+ CodebaseContext::Done(Ok(excerpts)) => {
+ div()
+ .v_flex()
+ .gap_2()
+ .children(excerpts.iter().map(|excerpt| {
+ let expanded = excerpt.expanded;
+ let element_id = excerpt.element_id.clone();
+
+ CollapsibleContainer::new(element_id.clone(), expanded)
+ .start_slot(
+ h_flex()
+ .gap_1()
+ .child(Icon::new(IconName::File).color(Color::Muted))
+ .child(Label::new(excerpt.path.clone()).color(Color::Muted)),
+ )
+ .on_click(cx.listener(move |this, _, cx| {
+ this.toggle_expanded(element_id.clone(), cx);
+ }))
+ .child(
+ div()
+ .p_2()
+ .rounded_md()
+ .bg(cx.theme().colors().editor_background)
+ .child(
+ excerpt.text.clone(), // todo!(): Show as an editor block
+ ),
+ )
+ }))
+ }
+ CodebaseContext::Done(Err(error)) => div().child(error.to_string()),
+ }
+ }
+}
+
+impl CodebaseContext {
+ #[allow(dead_code)]
+ fn new(
+ query: impl 'static + Future