Add language toolchains (#19576)

This PR adds support for selecting toolchains for a given language (e.g.
Rust toolchains or Python virtual environments) with support for SSH
projects provided out of the box. For Python we piggy-back off of
[PET](https://github.com/microsoft/python-environment-tools), a library
maintained by Microsoft.
Closes #16421
Closes #7646

Release Notes:

- Added toolchain selector to the status bar (with initial support for
Python virtual environments)
This commit is contained in:
Piotr Osiewicz 2024-10-28 15:34:03 +01:00 committed by GitHub
parent 03bd95405b
commit cdddb4d360
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 2221 additions and 133 deletions

View file

@ -192,6 +192,9 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on: hosted-windows-1
steps:
# more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git
run: git config --system core.longpaths true
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:

493
Cargo.lock generated
View file

@ -291,6 +291,12 @@ dependencies = [
"syn 2.0.76",
]
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "arrayref"
version = "0.3.8"
@ -385,7 +391,7 @@ dependencies = [
"ctor",
"db",
"editor",
"env_logger",
"env_logger 0.11.5",
"feature_flags",
"fs",
"futures 0.3.30",
@ -2551,7 +2557,7 @@ dependencies = [
"dashmap 6.0.1",
"derive_more",
"editor",
"env_logger",
"env_logger 0.11.5",
"envy",
"file_finder",
"fs",
@ -2706,7 +2712,7 @@ dependencies = [
"command_palette_hooks",
"ctor",
"editor",
"env_logger",
"env_logger 0.11.5",
"fuzzy",
"go_to_line",
"gpui",
@ -3483,7 +3489,7 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"language",
@ -3671,7 +3677,7 @@ dependencies = [
"ctor",
"db",
"emojis",
"env_logger",
"env_logger 0.11.5",
"file_icons",
"futures 0.3.30",
"fuzzy",
@ -3877,6 +3883,19 @@ dependencies = [
"regex",
]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.11.5"
@ -3985,7 +4004,7 @@ dependencies = [
"client",
"clock",
"collections",
"env_logger",
"env_logger 0.11.5",
"feature_flags",
"fs",
"git",
@ -4080,7 +4099,7 @@ dependencies = [
"client",
"collections",
"ctor",
"env_logger",
"env_logger 0.11.5",
"fs",
"futures 0.3.30",
"gpui",
@ -4122,7 +4141,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger",
"env_logger 0.11.5",
"extension",
"fs",
"language",
@ -4281,7 +4300,7 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger",
"env_logger 0.11.5",
"file_icons",
"futures 0.3.30",
"fuzzy",
@ -5036,7 +5055,7 @@ dependencies = [
"ctor",
"derive_more",
"embed-resource",
"env_logger",
"env_logger 0.11.5",
"etagere",
"filedescriptor",
"flume",
@ -5226,6 +5245,15 @@ dependencies = [
"serde",
]
[[package]]
name = "hashlink"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.5",
]
[[package]]
name = "hashlink"
version = "0.9.1"
@ -6184,7 +6212,7 @@ dependencies = [
"collections",
"ctor",
"ec4rs",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"fuzzy",
"git",
@ -6241,7 +6269,7 @@ dependencies = [
"copilot",
"ctor",
"editor",
"env_logger",
"env_logger 0.11.5",
"feature_flags",
"futures 0.3.30",
"google_ai",
@ -6298,7 +6326,7 @@ dependencies = [
"collections",
"copilot",
"editor",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"language",
@ -6332,6 +6360,11 @@ dependencies = [
"lsp",
"node_runtime",
"paths",
"pet",
"pet-conda",
"pet-core",
"pet-poetry",
"pet-reporter",
"project",
"regex",
"rope",
@ -6628,7 +6661,7 @@ dependencies = [
"async-pipe",
"collections",
"ctor",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"log",
@ -6711,7 +6744,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"assets",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"language",
@ -6824,7 +6857,7 @@ dependencies = [
"clap",
"clap_complete",
"elasticlunr-rs",
"env_logger",
"env_logger 0.11.5",
"futures-util",
"handlebars 5.1.2",
"ignore",
@ -7006,6 +7039,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "msvc_spectre_libs"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8661ace213a0a130c7c5b9542df5023aedf092a02008ccf477b39ff108990305"
dependencies = [
"cc",
]
[[package]]
name = "multi_buffer"
version = "0.1.0"
@ -7014,7 +7056,7 @@ dependencies = [
"clock",
"collections",
"ctor",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"itertools 0.13.0",
@ -7974,6 +8016,366 @@ dependencies = [
"sha2",
]
[[package]]
name = "pet"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"clap",
"env_logger 0.10.2",
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-conda",
"pet-core",
"pet-env-var-path",
"pet-fs",
"pet-global-virtualenvs",
"pet-homebrew",
"pet-jsonrpc",
"pet-linux-global-python",
"pet-mac-commandlinetools",
"pet-mac-python-org",
"pet-mac-xcode",
"pet-pipenv",
"pet-poetry",
"pet-pyenv",
"pet-python-utils",
"pet-reporter",
"pet-telemetry",
"pet-venv",
"pet-virtualenv",
"pet-virtualenvwrapper",
"pet-windows-registry",
"pet-windows-store",
"serde",
"serde_json",
]
[[package]]
name = "pet-conda"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-reporter",
"regex",
"serde",
"serde_json",
"yaml-rust2",
]
[[package]]
name = "pet-core"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"clap",
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-fs",
"regex",
"serde",
"serde_json",
]
[[package]]
name = "pet-env-var-path"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-conda",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
"regex",
]
[[package]]
name = "pet-fs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
]
[[package]]
name = "pet-global-virtualenvs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-conda",
"pet-core",
"pet-fs",
"pet-virtualenv",
]
[[package]]
name = "pet-homebrew"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-conda",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
"regex",
"serde",
"serde_json",
]
[[package]]
name = "pet-jsonrpc"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"env_logger 0.10.2",
"log",
"msvc_spectre_libs",
"pet-core",
"serde",
"serde_json",
]
[[package]]
name = "pet-linux-global-python"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
]
[[package]]
name = "pet-mac-commandlinetools"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
]
[[package]]
name = "pet-mac-python-org"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
]
[[package]]
name = "pet-mac-xcode"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
]
[[package]]
name = "pet-pipenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
]
[[package]]
name = "pet-poetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"base64 0.22.1",
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-reporter",
"pet-virtualenv",
"regex",
"serde",
"serde_json",
"sha2",
"toml 0.8.19",
]
[[package]]
name = "pet-pyenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-conda",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-reporter",
"regex",
"serde",
"serde_json",
]
[[package]]
name = "pet-python-utils"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"regex",
"serde",
"serde_json",
"sha2",
]
[[package]]
name = "pet-reporter"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"env_logger 0.10.2",
"log",
"msvc_spectre_libs",
"pet-core",
"pet-jsonrpc",
"serde",
"serde_json",
]
[[package]]
name = "pet-telemetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"regex",
]
[[package]]
name = "pet-venv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-core",
"pet-python-utils",
"pet-virtualenv",
]
[[package]]
name = "pet-virtualenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
]
[[package]]
name = "pet-virtualenvwrapper"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
]
[[package]]
name = "pet-windows-registry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-conda",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
"pet-windows-store",
"regex",
"winreg 0.52.0",
]
[[package]]
name = "pet-windows-store"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f28c46633abd5448a52b1f396c322e0d6c"
dependencies = [
"lazy_static",
"log",
"msvc_spectre_libs",
"pet-core",
"pet-fs",
"pet-python-utils",
"pet-virtualenv",
"regex",
"winreg 0.52.0",
]
[[package]]
name = "petgraph"
version = "0.6.5"
@ -8062,7 +8464,7 @@ dependencies = [
"anyhow",
"ctor",
"editor",
"env_logger",
"env_logger 0.11.5",
"gpui",
"menu",
"serde",
@ -8408,7 +8810,7 @@ dependencies = [
"client",
"clock",
"collections",
"env_logger",
"env_logger 0.11.5",
"fs",
"futures 0.3.30",
"fuzzy",
@ -9123,7 +9525,7 @@ dependencies = [
"clap",
"client",
"clock",
"env_logger",
"env_logger 0.11.5",
"fork",
"fs",
"futures 0.3.30",
@ -9174,7 +9576,7 @@ dependencies = [
"collections",
"command_palette_hooks",
"editor",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"http_client",
@ -9454,7 +9856,7 @@ dependencies = [
"arrayvec",
"criterion",
"ctor",
"env_logger",
"env_logger 0.11.5",
"gpui",
"log",
"rand 0.8.5",
@ -9485,7 +9887,7 @@ dependencies = [
"base64 0.22.1",
"chrono",
"collections",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"parking_lot",
@ -10074,7 +10476,7 @@ dependencies = [
"client",
"clock",
"collections",
"env_logger",
"env_logger 0.11.5",
"feature_flags",
"fs",
"futures 0.3.30",
@ -10767,7 +11169,7 @@ dependencies = [
"futures-io",
"futures-util",
"hashbrown 0.14.5",
"hashlink",
"hashlink 0.9.1",
"hex",
"indexmap 2.4.0",
"log",
@ -11091,7 +11493,7 @@ version = "0.1.0"
dependencies = [
"arrayvec",
"ctor",
"env_logger",
"env_logger 0.11.5",
"log",
"rand 0.8.5",
"rayon",
@ -11105,7 +11507,7 @@ dependencies = [
"client",
"collections",
"editor",
"env_logger",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"http_client",
@ -11404,7 +11806,7 @@ dependencies = [
"collections",
"ctor",
"editor",
"env_logger",
"env_logger 0.11.5",
"gpui",
"language",
"menu",
@ -11611,7 +12013,7 @@ dependencies = [
"clock",
"collections",
"ctor",
"env_logger",
"env_logger 0.11.5",
"gpui",
"http_client",
"log",
@ -12100,6 +12502,21 @@ dependencies = [
"winnow 0.6.18",
]
[[package]]
name = "toolchain_selector"
version = "0.1.0"
dependencies = [
"editor",
"fuzzy",
"gpui",
"language",
"picker",
"project",
"ui",
"util",
"workspace",
]
[[package]]
name = "topological-sort"
version = "0.2.2"
@ -14269,7 +14686,7 @@ dependencies = [
"collections",
"db",
"derive_more",
"env_logger",
"env_logger 0.11.5",
"fs",
"futures 0.3.30",
"git",
@ -14306,7 +14723,7 @@ dependencies = [
"anyhow",
"clock",
"collections",
"env_logger",
"env_logger 0.11.5",
"fs",
"futures 0.3.30",
"fuzzy",
@ -14476,6 +14893,17 @@ dependencies = [
"clap",
]
[[package]]
name = "yaml-rust2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8"
dependencies = [
"arraydeque",
"encoding_rs",
"hashlink 0.8.4",
]
[[package]]
name = "yansi"
version = "1.0.1"
@ -14589,7 +15017,7 @@ dependencies = [
"db",
"diagnostics",
"editor",
"env_logger",
"env_logger 0.11.5",
"extension",
"extensions_ui",
"feature_flags",
@ -14656,6 +15084,7 @@ dependencies = [
"theme",
"theme_selector",
"time",
"toolchain_selector",
"tree-sitter-md",
"tree-sitter-rust",
"ui",

View file

@ -117,6 +117,7 @@ members = [
"crates/theme_selector",
"crates/time_format",
"crates/title_bar",
"crates/toolchain_selector",
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
@ -290,6 +291,7 @@ theme_importer = { path = "crates/theme_importer" }
theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
toolchain_selector = { path = "crates/toolchain_selector" }
ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
@ -376,6 +378,11 @@ ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = "1.3.0"
profiling = "1"

View file

@ -779,6 +779,7 @@
"tasks": {
"variables": {}
},
"toolchain": { "name": "default", "path": "default" },
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
// use those languages.

View file

@ -8,7 +8,8 @@ use collections::HashMap;
use futures::{Future, FutureExt};
use gpui::AsyncAppContext;
use language::{
CodeLabel, HighlightId, Language, LanguageServerName, LspAdapter, LspAdapterDelegate,
CodeLabel, HighlightId, Language, LanguageServerName, LanguageToolchainStore, LspAdapter,
LspAdapterDelegate,
};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
use serde::Serialize;
@ -194,6 +195,7 @@ impl LspAdapter for ExtensionLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_cx: &mut AsyncAppContext,
) -> Result<Value> {
let delegate = delegate.clone();

View file

@ -37,7 +37,7 @@ use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use indexed_docs::{IndexedDocsRegistry, ProviderId};
use language::{
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
QUERY_FILENAME_PREFIXES,
LoadedLanguage, QUERY_FILENAME_PREFIXES,
};
use node_runtime::NodeRuntime;
use project::ContextProviderWithTasks;
@ -1102,14 +1102,21 @@ impl ExtensionStore {
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
let config: LanguageConfig = ::toml::from_str(&config)?;
let queries = load_plugin_queries(&language_path);
let tasks = std::fs::read_to_string(language_path.join("tasks.json"))
.ok()
.and_then(|contents| {
let definitions = serde_json_lenient::from_str(&contents).log_err()?;
Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
});
let context_provider =
std::fs::read_to_string(language_path.join("tasks.json"))
.ok()
.and_then(|contents| {
let definitions =
serde_json_lenient::from_str(&contents).log_err()?;
Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
});
Ok((config, queries, tasks))
Ok(LoadedLanguage {
config,
queries,
context_provider,
toolchain_provider: None,
})
},
);
}

View file

@ -15,6 +15,7 @@ mod outline;
pub mod proto;
mod syntax_map;
mod task_context;
mod toolchain;
#[cfg(test)]
pub mod buffer_tests;
@ -28,7 +29,7 @@ use futures::Future;
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
pub use highlight_map::HighlightMap;
use http_client::HttpClient;
pub use language_registry::LanguageName;
pub use language_registry::{LanguageName, LoadedLanguage};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
use parking_lot::Mutex;
use regex::Regex;
@ -61,6 +62,7 @@ use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
use task::RunnableTag;
pub use task_context::{ContextProvider, RunnableRange};
use theme::SyntaxTheme;
pub use toolchain::{LanguageToolchainStore, Toolchain, ToolchainList, ToolchainLister};
use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};
use util::serde::default_true;
@ -502,6 +504,7 @@ pub trait LspAdapter: 'static + Send + Sync {
async fn workspace_configuration(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_cx: &mut AsyncAppContext,
) -> Result<Value> {
Ok(serde_json::json!({}))
@ -855,6 +858,7 @@ pub struct Language {
pub(crate) config: LanguageConfig,
pub(crate) grammar: Option<Arc<Grammar>>,
pub(crate) context_provider: Option<Arc<dyn ContextProvider>>,
pub(crate) toolchain: Option<Arc<dyn ToolchainLister>>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
@ -983,6 +987,7 @@ impl Language {
})
}),
context_provider: None,
toolchain: None,
}
}
@ -991,6 +996,11 @@ impl Language {
self
}
pub fn with_toolchain_lister(mut self, provider: Option<Arc<dyn ToolchainLister>>) -> Self {
self.toolchain = provider;
self
}
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
if let Some(query) = queries.highlights {
self = self
@ -1361,6 +1371,10 @@ impl Language {
self.context_provider.clone()
}
pub fn toolchain_lister(&self) -> Option<Arc<dyn ToolchainLister>> {
self.toolchain.clone()
}
pub fn highlight_text<'a>(
self: &'a Arc<Self>,
text: &'a Rope,

View file

@ -4,7 +4,7 @@ use crate::{
},
task_context::ContextProvider,
with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
LanguageServerName, LspAdapter, PLAIN_TEXT,
LanguageServerName, LspAdapter, ToolchainLister, PLAIN_TEXT,
};
use anyhow::{anyhow, Context, Result};
use collections::{hash_map, HashMap, HashSet};
@ -75,6 +75,13 @@ impl<'a> From<&'a str> for LanguageName {
}
}
impl From<LanguageName> for String {
fn from(value: LanguageName) -> Self {
let value: &str = &value.0;
Self::from(value)
}
}
pub struct LanguageRegistry {
state: RwLock<LanguageRegistryState>,
language_server_download_dir: Option<Arc<Path>>,
@ -123,16 +130,7 @@ pub struct AvailableLanguage {
name: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
load: Arc<
dyn Fn() -> Result<(
LanguageConfig,
LanguageQueries,
Option<Arc<dyn ContextProvider>>,
)>
+ 'static
+ Send
+ Sync,
>,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
loaded: bool,
}
@ -200,6 +198,13 @@ struct LspBinaryStatusSender {
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, LanguageServerBinaryStatus)>>>>,
}
pub struct LoadedLanguage {
pub config: LanguageConfig,
pub queries: LanguageQueries,
pub context_provider: Option<Arc<dyn ContextProvider>>,
pub toolchain_provider: Option<Arc<dyn ToolchainLister>>,
}
impl LanguageRegistry {
pub fn new(executor: BackgroundExecutor) -> Self {
let this = Self {
@ -283,7 +288,14 @@ impl LanguageRegistry {
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || Ok((config.clone(), Default::default(), None)),
move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: Default::default(),
toolchain_provider: None,
context_provider: None,
})
},
)
}
@ -424,14 +436,7 @@ impl LanguageRegistry {
name: LanguageName,
grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher,
load: impl Fn() -> Result<(
LanguageConfig,
LanguageQueries,
Option<Arc<dyn ContextProvider>>,
)>
+ 'static
+ Send
+ Sync,
load: impl Fn() -> Result<LoadedLanguage> + 'static + Send + Sync,
) {
let load = Arc::new(load);
let state = &mut *self.state.write();
@ -726,16 +731,18 @@ impl LanguageRegistry {
self.executor
.spawn(async move {
let language = async {
let (config, queries, provider) = (language_load)()?;
if let Some(grammar) = config.grammar.clone() {
let loaded_language = (language_load)()?;
if let Some(grammar) = loaded_language.config.grammar.clone() {
let grammar = Some(this.get_or_load_grammar(grammar).await?);
Language::new_with_id(id, config, grammar)
.with_context_provider(provider)
.with_queries(queries)
Language::new_with_id(id, loaded_language.config, grammar)
.with_context_provider(loaded_language.context_provider)
.with_toolchain_lister(loaded_language.toolchain_provider)
.with_queries(loaded_language.queries)
} else {
Ok(Language::new_with_id(id, config, None)
.with_context_provider(provider))
Ok(Language::new_with_id(id, loaded_language.config, None)
.with_context_provider(loaded_language.context_provider)
.with_toolchain_lister(loaded_language.toolchain_provider))
}
}
.await;

View file

@ -0,0 +1,65 @@
//! Provides support for language toolchains.
//!
//! A language can have associated toolchains,
//! which is a set of tools used to interact with the projects written in said language.
//! For example, a Python project can have an associated virtual environment; a Rust project can have a toolchain override.
use std::{path::PathBuf, sync::Arc};
use async_trait::async_trait;
use gpui::{AsyncAppContext, SharedString};
use settings::WorktreeId;
use crate::LanguageName;
/// Represents a single toolchain.
#[derive(Clone, Debug, PartialEq)]
pub struct Toolchain {
/// User-facing label
pub name: SharedString,
pub path: SharedString,
pub language_name: LanguageName,
}
#[async_trait(?Send)]
pub trait ToolchainLister: Send + Sync {
async fn list(&self, _: PathBuf) -> ToolchainList;
}
#[async_trait(?Send)]
pub trait LanguageToolchainStore {
async fn active_toolchain(
self: Arc<Self>,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &mut AsyncAppContext,
) -> Option<Toolchain>;
}
type DefaultIndex = usize;
#[derive(Default, Clone)]
pub struct ToolchainList {
pub toolchains: Vec<Toolchain>,
pub default: Option<DefaultIndex>,
pub groups: Box<[(usize, SharedString)]>,
}
impl ToolchainList {
pub fn toolchains(&self) -> &[Toolchain] {
&self.toolchains
}
pub fn default_toolchain(&self) -> Option<Toolchain> {
self.default.and_then(|ix| self.toolchains.get(ix)).cloned()
}
pub fn group_for_index(&self, index: usize) -> Option<(usize, SharedString)> {
if index >= self.toolchains.len() {
return None;
}
let first_equal_or_greater = self
.groups
.partition_point(|(group_lower_bound, _)| group_lower_bound <= &index);
self.groups
.get(first_equal_or_greater.checked_sub(1)?)
.cloned()
}
}

View file

@ -47,6 +47,11 @@ log.workspace = true
lsp.workspace = true
node_runtime.workspace = true
paths.workspace = true
pet.workspace = true
pet-core.workspace = true
pet-conda.workspace = true
pet-poetry.workspace = true
pet-reporter.workspace = true
project.workspace = true
regex.workspace = true
rope.workspace = true

View file

@ -7,7 +7,9 @@ use feature_flags::FeatureFlagAppExt;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext};
use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
use language::{
LanguageRegistry, LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
};
use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime;
use project::ContextProviderWithTasks;
@ -198,6 +200,7 @@ impl LspAdapter for JsonLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
cx.update(|cx| {

View file

@ -3,7 +3,7 @@ use gpui::{AppContext, UpdateGlobal};
use json::json_task_context;
pub use language::*;
use node_runtime::NodeRuntime;
use python::PythonContextProvider;
use python::{PythonContextProvider, PythonToolchainProvider};
use rust_embed::RustEmbed;
use settings::SettingsStore;
use smol::stream::StreamExt;
@ -61,7 +61,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || Ok((config.clone(), load_queries($name), None)),
move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: load_queries($name),
context_provider: None,
toolchain_provider: None,
})
},
);
};
($name:literal, $adapters:expr) => {
@ -75,7 +82,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || Ok((config.clone(), load_queries($name), None)),
move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: load_queries($name),
context_provider: None,
toolchain_provider: None,
})
},
);
};
($name:literal, $adapters:expr, $context_provider:expr) => {
@ -90,11 +104,33 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
config.grammar.clone(),
config.matcher.clone(),
move || {
Ok((
config.clone(),
load_queries($name),
Some(Arc::new($context_provider)),
))
Ok(LoadedLanguage {
config: config.clone(),
queries: load_queries($name),
context_provider: Some(Arc::new($context_provider)),
toolchain_provider: None,
})
},
);
};
($name:literal, $adapters:expr, $context_provider:expr, $toolchain_provider:expr) => {
let config = load_config($name);
// typeck helper
let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
for adapter in adapters {
languages.register_lsp_adapter(config.name.clone(), adapter);
}
languages.register_language(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: load_queries($name),
context_provider: Some(Arc::new($context_provider)),
toolchain_provider: Some($toolchain_provider),
})
},
);
};
@ -141,7 +177,8 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
vec![Arc::new(python::PythonLspAdapter::new(
node_runtime.clone(),
))],
PythonContextProvider
PythonContextProvider,
Arc::new(PythonToolchainProvider::default()) as Arc<dyn ToolchainLister>
);
language!(
"rust",

View file

@ -3,9 +3,16 @@ use async_trait::async_trait;
use collections::HashMap;
use gpui::AppContext;
use gpui::AsyncAppContext;
use language::LanguageName;
use language::LanguageToolchainStore;
use language::Toolchain;
use language::ToolchainList;
use language::ToolchainLister;
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime;
use pet_core::python_environment::PythonEnvironmentKind;
use pet_core::Configuration;
use project::lsp_store::language_server_settings;
use serde_json::Value;
@ -200,12 +207,35 @@ impl LspAdapter for PythonLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
cx.update(|cx| {
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
.and_then(|s| s.settings.clone())
.unwrap_or_default()
let toolchain = toolchains
.active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
.await;
cx.update(move |cx| {
let mut user_settings =
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
.and_then(|s| s.settings.clone())
.unwrap_or_default();
// If python.pythonPath is not set in user config, do so using our toolchain picker.
if let Some(toolchain) = toolchain {
if user_settings.is_null() {
user_settings = Value::Object(serde_json::Map::default());
}
let object = user_settings.as_object_mut().unwrap();
if let Some(python) = object
.entry("python")
.or_insert(Value::Object(serde_json::Map::default()))
.as_object_mut()
{
python
.entry("pythonPath")
.or_insert(Value::String(toolchain.path.into()));
}
}
user_settings
})
}
}
@ -320,6 +350,83 @@ fn python_module_name_from_relative_path(relative_path: &str) -> String {
.to_string()
}
#[derive(Default)]
pub(crate) struct PythonToolchainProvider {}
static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[
// Prioritize non-Conda environments.
PythonEnvironmentKind::Poetry,
PythonEnvironmentKind::Pipenv,
PythonEnvironmentKind::VirtualEnvWrapper,
PythonEnvironmentKind::Venv,
PythonEnvironmentKind::VirtualEnv,
PythonEnvironmentKind::Conda,
PythonEnvironmentKind::Pyenv,
PythonEnvironmentKind::GlobalPaths,
PythonEnvironmentKind::Homebrew,
];
fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
if let Some(kind) = kind {
ENV_PRIORITY_LIST
.iter()
.position(|blessed_env| blessed_env == &kind)
.unwrap_or(ENV_PRIORITY_LIST.len())
} else {
// Unknown toolchains are less useful than non-blessed ones.
ENV_PRIORITY_LIST.len() + 1
}
}
#[async_trait(?Send)]
impl ToolchainLister for PythonToolchainProvider {
async fn list(&self, worktree_root: PathBuf) -> ToolchainList {
let environment = pet_core::os_environment::EnvironmentApi::new();
let locators = pet::locators::create_locators(
Arc::new(pet_conda::Conda::from(&environment)),
Arc::new(pet_poetry::Poetry::from(&environment)),
&environment,
);
let mut config = Configuration::default();
config.workspace_directories = Some(vec![worktree_root]);
let reporter = pet_reporter::collect::create_reporter();
pet::find::find_and_report_envs(&reporter, config, &locators, &environment, None);
let mut toolchains = reporter
.environments
.lock()
.ok()
.map_or(Vec::new(), |mut guard| std::mem::take(&mut guard));
toolchains.sort_by(|lhs, rhs| {
env_priority(lhs.kind)
.cmp(&env_priority(rhs.kind))
.then_with(|| lhs.executable.cmp(&rhs.executable))
});
let mut toolchains: Vec<_> = toolchains
.into_iter()
.filter_map(|toolchain| {
let name = if let Some(version) = &toolchain.version {
format!("Python {version} ({:?})", toolchain.kind?)
} else {
format!("{:?}", toolchain.kind?)
}
.into();
Some(Toolchain {
name,
path: toolchain.executable?.to_str()?.to_owned().into(),
language_name: LanguageName::new("Python"),
})
})
.collect();
toolchains.dedup();
ToolchainList {
toolchains,
default: None,
groups: Default::default(),
}
}
}
#[cfg(test)]
mod tests {
use gpui::{BorrowAppContext, Context, ModelContext, TestAppContext};

View file

@ -3,7 +3,7 @@ use async_trait::async_trait;
use collections::HashMap;
use futures::StreamExt;
use gpui::AsyncAppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime;
use project::lsp_store::language_server_settings;
@ -111,6 +111,7 @@ impl LspAdapter for TailwindLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let tailwind_user_settings = cx.update(|cx| {

View file

@ -5,7 +5,7 @@ use async_trait::async_trait;
use collections::HashMap;
use gpui::AsyncAppContext;
use http_client::github::{build_asset_url, AssetKind, GitHubLspBinaryVersion};
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary};
use node_runtime::NodeRuntime;
use project::lsp_store::language_server_settings;
@ -230,6 +230,7 @@ impl LspAdapter for TypeScriptLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let override_options = cx.update(|cx| {
@ -325,6 +326,7 @@ impl LspAdapter for EsLintLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let workspace_root = delegate.worktree_root_path();

View file

@ -2,7 +2,7 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait;
use collections::HashMap;
use gpui::AsyncAppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use language::{LanguageServerName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary};
use node_runtime::NodeRuntime;
use project::lsp_store::language_server_settings;
@ -183,6 +183,7 @@ impl LspAdapter for VtslsLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let tsdk_path = Self::tsdk_path(delegate).await;

View file

@ -3,7 +3,8 @@ use async_trait::async_trait;
use futures::StreamExt;
use gpui::AsyncAppContext;
use language::{
language_settings::AllLanguageSettings, LanguageServerName, LspAdapter, LspAdapterDelegate,
language_settings::AllLanguageSettings, LanguageServerName, LanguageToolchainStore, LspAdapter,
LspAdapterDelegate,
};
use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime;
@ -92,6 +93,7 @@ impl LspAdapter for YamlLspAdapter {
async fn workspace_configuration(
self: Arc<Self>,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let location = SettingsLocation {

View file

@ -7,10 +7,11 @@ use crate::{
prettier_store::{self, PrettierStore, PrettierStoreEvent},
project_settings::{LspSettings, ProjectSettings},
relativize_path, resolve_path,
toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
worktree_store::{WorktreeStore, WorktreeStoreEvent},
yarn::YarnPathStore,
CodeAction, Completion, CoreCompletion, Hover, InlayHint, Item as _, ProjectPath,
ProjectTransaction, ResolveState, Symbol,
ProjectTransaction, ResolveState, Symbol, ToolchainStore,
};
use anyhow::{anyhow, Context as _, Result};
use async_trait::async_trait;
@ -36,9 +37,9 @@ use language::{
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageName,
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LocalFile, LspAdapter,
LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
Unclipped,
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LanguageToolchainStore,
LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset,
ToPointUtf16, Transaction, Unclipped,
};
use lsp::{
CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
@ -707,12 +708,13 @@ pub struct LspStore {
nonce: u128,
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
toolchain_store: Option<Model<ToolchainStore>>,
buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
pub languages: Arc<LanguageRegistry>,
language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
pub language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
active_entry: Option<ProjectEntryId>,
_maintain_workspace_config: Task<Result<()>>,
_maintain_workspace_config: (Task<Result<()>>, watch::Sender<()>),
_maintain_buffer_languages: Task<()>,
next_diagnostic_group_id: usize,
diagnostic_summaries:
@ -871,6 +873,7 @@ impl LspStore {
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
prettier_store: Model<PrettierStore>,
toolchain_store: Model<ToolchainStore>,
environment: Model<ProjectEnvironment>,
languages: Arc<LanguageRegistry>,
http_client: Arc<dyn HttpClient>,
@ -884,9 +887,15 @@ impl LspStore {
.detach();
cx.subscribe(&prettier_store, Self::on_prettier_store_event)
.detach();
cx.subscribe(&toolchain_store, Self::on_toolchain_store_event)
.detach();
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
.detach();
let _maintain_workspace_config = {
let (sender, receiver) = watch::channel();
(Self::maintain_workspace_config(receiver, cx), sender)
};
Self {
mode: LspStoreMode::Local(LocalLspStore {
supplementary_language_servers: Default::default(),
@ -909,6 +918,7 @@ impl LspStore {
downstream_client: None,
buffer_store,
worktree_store,
toolchain_store: Some(toolchain_store),
languages: languages.clone(),
language_server_ids: Default::default(),
language_server_statuses: Default::default(),
@ -919,7 +929,7 @@ impl LspStore {
diagnostics: Default::default(),
active_entry: None,
_maintain_workspace_config: Self::maintain_workspace_config(cx),
_maintain_workspace_config,
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
}
}
@ -942,9 +952,10 @@ impl LspStore {
})
}
pub fn new_remote(
pub(super) fn new_remote(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
toolchain_store: Option<Model<ToolchainStore>>,
languages: Arc<LanguageRegistry>,
upstream_client: AnyProtoClient,
project_id: u64,
@ -954,7 +965,10 @@ impl LspStore {
.detach();
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
let _maintain_workspace_config = {
let (sender, receiver) = watch::channel();
(Self::maintain_workspace_config(receiver, cx), sender)
};
Self {
mode: LspStoreMode::Remote(RemoteLspStore {
upstream_client: Some(upstream_client),
@ -972,7 +986,8 @@ impl LspStore {
diagnostic_summaries: Default::default(),
diagnostics: Default::default(),
active_entry: None,
_maintain_workspace_config: Self::maintain_workspace_config(cx),
toolchain_store,
_maintain_workspace_config,
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
}
}
@ -1063,6 +1078,22 @@ impl LspStore {
}
}
fn on_toolchain_store_event(
&mut self,
_: Model<ToolchainStore>,
event: &ToolchainStoreEvent,
_: &mut ModelContext<Self>,
) {
match event {
ToolchainStoreEvent::ToolchainActivated { .. } => {
self.request_workspace_config_refresh()
}
}
}
fn request_workspace_config_refresh(&mut self) {
*self._maintain_workspace_config.1.borrow_mut() = ();
}
// todo!
pub fn prettier_store(&self) -> Option<Model<PrettierStore>> {
self.as_local().map(|local| local.prettier_store.clone())
@ -3029,17 +3060,13 @@ impl LspStore {
None
}
fn maintain_workspace_config(cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
let settings_observation = cx.observe_global::<SettingsStore>(move |_, _| {
*settings_changed_tx.borrow_mut() = ();
});
cx.spawn(move |this, mut cx| async move {
while let Some(()) = settings_changed_rx.next().await {
let servers = this.update(&mut cx, |this, cx| {
pub(crate) async fn refresh_workspace_configurations(
this: &WeakModel<Self>,
mut cx: AsyncAppContext,
) {
maybe!(async move {
let servers = this
.update(&mut cx, |this, cx| {
this.language_server_ids
.iter()
.filter_map(|((worktree_id, _), server_id)| {
@ -3061,17 +3088,52 @@ impl LspStore {
}
})
.collect::<Vec<_>>()
})?;
})
.ok()?;
for (adapter, server, delegate) in servers {
let settings = adapter.workspace_configuration(&delegate, &mut cx).await?;
let toolchain_store = this
.update(&mut cx, |this, cx| this.toolchain_store(cx))
.ok()?;
for (adapter, server, delegate) in servers {
let settings = adapter
.workspace_configuration(&delegate, toolchain_store.clone(), &mut cx)
.await
.ok()?;
server
.notify::<lsp::notification::DidChangeConfiguration>(
lsp::DidChangeConfigurationParams { settings },
)
.ok();
}
server
.notify::<lsp::notification::DidChangeConfiguration>(
lsp::DidChangeConfigurationParams { settings },
)
.ok();
}
Some(())
})
.await;
}
fn toolchain_store(&self, cx: &AppContext) -> Arc<dyn LanguageToolchainStore> {
if let Some(toolchain_store) = self.toolchain_store.as_ref() {
toolchain_store.read(cx).as_language_toolchain_store()
} else {
Arc::new(EmptyToolchainStore)
}
}
fn maintain_workspace_config(
external_refresh_requests: watch::Receiver<()>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
let settings_observation = cx.observe_global::<SettingsStore>(move |_, _| {
*settings_changed_tx.borrow_mut() = ();
});
let mut joint_future =
futures::stream::select(settings_changed_rx, external_refresh_requests);
cx.spawn(move |this, cx| async move {
while let Some(()) = joint_future.next().await {
Self::refresh_workspace_configurations(&this, cx.clone()).await;
}
drop(settings_observation);
@ -5517,6 +5579,9 @@ impl LspStore {
let delegate = delegate.clone();
let adapter = adapter.clone();
let this = this.clone();
let toolchains = this
.update(&mut cx, |this, cx| this.toolchain_store(cx))
.ok()?;
let mut cx = cx.clone();
async move {
let language_server = pending_server.await?;
@ -5524,7 +5589,7 @@ impl LspStore {
let workspace_config = adapter
.adapter
.clone()
.workspace_configuration(&delegate, &mut cx)
.workspace_configuration(&delegate, toolchains.clone(), &mut cx)
.await?;
let mut initialization_options = adapter
@ -5864,17 +5929,21 @@ impl LspStore {
}
})
.detach();
language_server
.on_request::<lsp::request::WorkspaceConfiguration, _, _>({
let adapter = adapter.adapter.clone();
let delegate = delegate.clone();
let this = this.clone();
move |params, mut cx| {
let adapter = adapter.clone();
let delegate = delegate.clone();
let this = this.clone();
async move {
let workspace_config =
adapter.workspace_configuration(&delegate, &mut cx).await?;
let toolchains =
this.update(&mut cx, |this, cx| this.toolchain_store(cx))?;
let workspace_config = adapter
.workspace_configuration(&delegate, toolchains, &mut cx)
.await?;
Ok(params
.items
.into_iter()

View file

@ -11,6 +11,7 @@ pub mod search;
mod task_inventory;
pub mod task_store;
pub mod terminals;
pub mod toolchain_store;
pub mod worktree_store;
#[cfg(test)]
@ -44,8 +45,8 @@ use itertools::Itertools;
use language::{
language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent,
CachedLspAdapter, Capability, CodeLabel, DiagnosticEntry, Documentation, File as _, Language,
LanguageRegistry, LanguageServerName, PointUtf16, ToOffset, ToPointUtf16, Transaction,
Unclipped,
LanguageName, LanguageRegistry, LanguageServerName, PointUtf16, ToOffset, ToPointUtf16,
Toolchain, ToolchainList, Transaction, Unclipped,
};
use lsp::{
CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId,
@ -101,7 +102,7 @@ pub use lsp_store::{
LanguageServerStatus, LanguageServerToQuery, LspStore, LspStoreEvent,
SERVER_PROGRESS_THROTTLE_TIMEOUT,
};
pub use toolchain_store::ToolchainStore;
const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500;
const MAX_SEARCH_RESULT_FILES: usize = 5_000;
const MAX_SEARCH_RESULT_RANGES: usize = 10_000;
@ -158,6 +159,7 @@ pub struct Project {
snippets: Model<SnippetProvider>,
environment: Model<ProjectEnvironment>,
settings_observer: Model<SettingsObserver>,
toolchain_store: Option<Model<ToolchainStore>>,
}
#[derive(Default)]
@ -579,6 +581,7 @@ impl Project {
LspStore::init(&client);
SettingsObserver::init(&client);
TaskStore::init(Some(&client));
ToolchainStore::init(&client);
}
pub fn local(
@ -635,12 +638,15 @@ impl Project {
});
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
.detach();
let toolchain_store = cx.new_model(|cx| {
ToolchainStore::local(languages.clone(), worktree_store.clone(), cx)
});
let lsp_store = cx.new_model(|cx| {
LspStore::new_local(
buffer_store.clone(),
worktree_store.clone(),
prettier_store.clone(),
toolchain_store.clone(),
environment.clone(),
languages.clone(),
client.http_client(),
@ -681,6 +687,8 @@ impl Project {
search_included_history: Self::new_search_history(),
search_excluded_history: Self::new_search_history(),
toolchain_store: Some(toolchain_store),
}
})
}
@ -737,10 +745,14 @@ impl Project {
.detach();
let environment = ProjectEnvironment::new(&worktree_store, None, cx);
let toolchain_store = Some(cx.new_model(|cx| {
ToolchainStore::remote(SSH_PROJECT_ID, ssh.read(cx).proto_client(), cx)
}));
let lsp_store = cx.new_model(|cx| {
LspStore::new_remote(
buffer_store.clone(),
worktree_store.clone(),
toolchain_store.clone(),
languages.clone(),
ssh_proto.clone(),
SSH_PROJECT_ID,
@ -798,6 +810,8 @@ impl Project {
search_included_history: Self::new_search_history(),
search_excluded_history: Self::new_search_history(),
toolchain_store,
};
let ssh = ssh.read(cx);
@ -818,6 +832,7 @@ impl Project {
LspStore::init(&ssh_proto);
SettingsObserver::init(&ssh_proto);
TaskStore::init(Some(&ssh_proto));
ToolchainStore::init(&ssh_proto);
this
})
@ -905,6 +920,7 @@ impl Project {
let mut lsp_store = LspStore::new_remote(
buffer_store.clone(),
worktree_store.clone(),
None,
languages.clone(),
client.clone().into(),
remote_id,
@ -993,6 +1009,7 @@ impl Project {
search_excluded_history: Self::new_search_history(),
environment: ProjectEnvironment::new(&worktree_store, None, cx),
remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
toolchain_store: None,
};
this.set_role(role, cx);
for worktree in worktrees {
@ -2346,6 +2363,46 @@ impl Project {
.map_err(|e| anyhow!(e))
}
pub fn available_toolchains(
&self,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &AppContext,
) -> Task<Option<ToolchainList>> {
if let Some(toolchain_store) = self.toolchain_store.as_ref() {
toolchain_store
.read(cx)
.list_toolchains(worktree_id, language_name, cx)
} else {
Task::ready(None)
}
}
pub fn activate_toolchain(
&self,
worktree_id: WorktreeId,
toolchain: Toolchain,
cx: &mut AppContext,
) -> Task<Option<()>> {
let Some(toolchain_store) = self.toolchain_store.clone() else {
return Task::ready(None);
};
toolchain_store.update(cx, |this, cx| {
this.activate_toolchain(worktree_id, toolchain, cx)
})
}
pub fn active_toolchain(
&self,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &AppContext,
) -> Task<Option<Toolchain>> {
let Some(toolchain_store) = self.toolchain_store.clone() else {
return Task::ready(None);
};
toolchain_store
.read(cx)
.active_toolchain(worktree_id, language_name, cx)
}
pub fn language_server_statuses<'a>(
&'a self,
cx: &'a AppContext,

View file

@ -0,0 +1,416 @@
use std::sync::Arc;
use anyhow::{bail, Result};
use async_trait::async_trait;
use collections::BTreeMap;
use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
WeakModel,
};
use language::{LanguageName, LanguageRegistry, LanguageToolchainStore, Toolchain, ToolchainList};
use rpc::{proto, AnyProtoClient, TypedEnvelope};
use settings::WorktreeId;
use util::ResultExt as _;
use crate::worktree_store::WorktreeStore;
pub struct ToolchainStore(ToolchainStoreInner);
enum ToolchainStoreInner {
Local(Model<LocalToolchainStore>, #[allow(dead_code)] Subscription),
Remote(Model<RemoteToolchainStore>),
}
impl EventEmitter<ToolchainStoreEvent> for ToolchainStore {}
impl ToolchainStore {
pub fn init(client: &AnyProtoClient) {
client.add_model_request_handler(Self::handle_activate_toolchain);
client.add_model_request_handler(Self::handle_list_toolchains);
client.add_model_request_handler(Self::handle_active_toolchain);
}
pub fn local(
languages: Arc<LanguageRegistry>,
worktree_store: Model<WorktreeStore>,
cx: &mut ModelContext<Self>,
) -> Self {
let model = cx.new_model(|_| LocalToolchainStore {
languages,
worktree_store,
active_toolchains: Default::default(),
});
let subscription = cx.subscribe(&model, |_, _, e: &ToolchainStoreEvent, cx| {
cx.emit(e.clone())
});
Self(ToolchainStoreInner::Local(model, subscription))
}
pub(super) fn remote(project_id: u64, client: AnyProtoClient, cx: &mut AppContext) -> Self {
Self(ToolchainStoreInner::Remote(
cx.new_model(|_| RemoteToolchainStore { client, project_id }),
))
}
pub(crate) fn activate_toolchain(
&self,
worktree_id: WorktreeId,
toolchain: Toolchain,
cx: &mut AppContext,
) -> Task<Option<()>> {
match &self.0 {
ToolchainStoreInner::Local(local, _) => local.update(cx, |this, cx| {
this.activate_toolchain(worktree_id, toolchain, cx)
}),
ToolchainStoreInner::Remote(remote) => {
remote
.read(cx)
.activate_toolchain(worktree_id, toolchain, cx)
}
}
}
pub(crate) fn list_toolchains(
&self,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &AppContext,
) -> Task<Option<ToolchainList>> {
match &self.0 {
ToolchainStoreInner::Local(local, _) => {
local
.read(cx)
.list_toolchains(worktree_id, language_name, cx)
}
ToolchainStoreInner::Remote(remote) => {
remote
.read(cx)
.list_toolchains(worktree_id, language_name, cx)
}
}
}
pub(crate) fn active_toolchain(
&self,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &AppContext,
) -> Task<Option<Toolchain>> {
match &self.0 {
ToolchainStoreInner::Local(local, _) => {
local
.read(cx)
.active_toolchain(worktree_id, language_name, cx)
}
ToolchainStoreInner::Remote(remote) => {
remote
.read(cx)
.active_toolchain(worktree_id, language_name, cx)
}
}
}
async fn handle_activate_toolchain(
this: Model<Self>,
envelope: TypedEnvelope<proto::ActivateToolchain>,
mut cx: AsyncAppContext,
) -> Result<proto::Ack> {
this.update(&mut cx, |this, cx| {
let language_name = LanguageName::from_proto(envelope.payload.language_name);
let Some(toolchain) = envelope.payload.toolchain else {
bail!("Missing `toolchain` in payload");
};
let toolchain = Toolchain {
name: toolchain.name.into(),
path: toolchain.path.into(),
language_name,
};
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
Ok(this.activate_toolchain(worktree_id, toolchain, cx))
})??
.await;
Ok(proto::Ack {})
}
async fn handle_active_toolchain(
this: Model<Self>,
envelope: TypedEnvelope<proto::ActiveToolchain>,
mut cx: AsyncAppContext,
) -> Result<proto::ActiveToolchainResponse> {
let toolchain = this
.update(&mut cx, |this, cx| {
let language_name = LanguageName::from_proto(envelope.payload.language_name);
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
this.active_toolchain(worktree_id, language_name, cx)
})?
.await;
Ok(proto::ActiveToolchainResponse {
toolchain: toolchain.map(|toolchain| proto::Toolchain {
name: toolchain.name.into(),
path: toolchain.path.into(),
}),
})
}
async fn handle_list_toolchains(
this: Model<Self>,
envelope: TypedEnvelope<proto::ListToolchains>,
mut cx: AsyncAppContext,
) -> Result<proto::ListToolchainsResponse> {
let toolchains = this
.update(&mut cx, |this, cx| {
let language_name = LanguageName::from_proto(envelope.payload.language_name);
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
this.list_toolchains(worktree_id, language_name, cx)
})?
.await;
let has_values = toolchains.is_some();
let groups = if let Some(toolchains) = &toolchains {
toolchains
.groups
.iter()
.filter_map(|group| {
Some(proto::ToolchainGroup {
start_index: u64::try_from(group.0).ok()?,
name: String::from(group.1.as_ref()),
})
})
.collect()
} else {
vec![]
};
let toolchains = if let Some(toolchains) = toolchains {
toolchains
.toolchains
.into_iter()
.map(|toolchain| proto::Toolchain {
name: toolchain.name.to_string(),
path: toolchain.path.to_string(),
})
.collect::<Vec<_>>()
} else {
vec![]
};
Ok(proto::ListToolchainsResponse {
has_values,
toolchains,
groups,
})
}
pub(crate) fn as_language_toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
match &self.0 {
ToolchainStoreInner::Local(local, _) => Arc::new(LocalStore(local.downgrade())),
ToolchainStoreInner::Remote(remote) => Arc::new(RemoteStore(remote.downgrade())),
}
}
}
struct LocalToolchainStore {
languages: Arc<LanguageRegistry>,
worktree_store: Model<WorktreeStore>,
active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>,
}
#[async_trait(?Send)]
impl language::LanguageToolchainStore for LocalStore {
async fn active_toolchain(
self: Arc<Self>,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &mut AsyncAppContext,
) -> Option<Toolchain> {
self.0
.update(cx, |this, cx| {
this.active_toolchain(worktree_id, language_name, cx)
})
.ok()?
.await
}
}
#[async_trait(?Send)]
impl language::LanguageToolchainStore for RemoteStore {
async fn active_toolchain(
self: Arc<Self>,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &mut AsyncAppContext,
) -> Option<Toolchain> {
self.0
.update(cx, |this, cx| {
this.active_toolchain(worktree_id, language_name, cx)
})
.ok()?
.await
}
}
pub(crate) struct EmptyToolchainStore;
#[async_trait(?Send)]
impl language::LanguageToolchainStore for EmptyToolchainStore {
async fn active_toolchain(
self: Arc<Self>,
_: WorktreeId,
_: LanguageName,
_: &mut AsyncAppContext,
) -> Option<Toolchain> {
None
}
}
struct LocalStore(WeakModel<LocalToolchainStore>);
struct RemoteStore(WeakModel<RemoteToolchainStore>);
#[derive(Clone)]
pub(crate) enum ToolchainStoreEvent {
ToolchainActivated,
}
impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
impl LocalToolchainStore {
pub(crate) fn activate_toolchain(
&self,
worktree_id: WorktreeId,
toolchain: Toolchain,
cx: &mut ModelContext<Self>,
) -> Task<Option<()>> {
cx.spawn(move |this, mut cx| async move {
this.update(&mut cx, |this, cx| {
this.active_toolchains.insert(
(worktree_id, toolchain.language_name.clone()),
toolchain.clone(),
);
cx.emit(ToolchainStoreEvent::ToolchainActivated);
})
.ok();
Some(())
})
}
pub(crate) fn list_toolchains(
&self,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &AppContext,
) -> Task<Option<ToolchainList>> {
let registry = self.languages.clone();
let Some(root) = self
.worktree_store
.read(cx)
.worktree_for_id(worktree_id, cx)
.map(|worktree| worktree.read(cx).abs_path())
else {
return Task::ready(None);
};
cx.spawn(|_| async move {
let language = registry.language_for_name(&language_name.0).await.ok()?;
let toolchains = language.toolchain_lister()?.list(root.to_path_buf()).await;
Some(toolchains)
})
}
pub(crate) fn active_toolchain(
&self,
worktree_id: WorktreeId,
language_name: LanguageName,
_: &AppContext,
) -> Task<Option<Toolchain>> {
Task::ready(
self.active_toolchains
.get(&(worktree_id, language_name))
.cloned(),
)
}
}
struct RemoteToolchainStore {
client: AnyProtoClient,
project_id: u64,
}
impl RemoteToolchainStore {
pub(crate) fn activate_toolchain(
&self,
worktree_id: WorktreeId,
toolchain: Toolchain,
cx: &AppContext,
) -> Task<Option<()>> {
let project_id = self.project_id;
let client = self.client.clone();
cx.spawn(move |_| async move {
let _ = client
.request(proto::ActivateToolchain {
project_id,
worktree_id: worktree_id.to_proto(),
language_name: toolchain.language_name.into(),
toolchain: Some(proto::Toolchain {
name: toolchain.name.into(),
path: toolchain.path.into(),
}),
})
.await
.log_err()?;
Some(())
})
}
pub(crate) fn list_toolchains(
&self,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &AppContext,
) -> Task<Option<ToolchainList>> {
let project_id = self.project_id;
let client = self.client.clone();
cx.spawn(move |_| async move {
let response = client
.request(proto::ListToolchains {
project_id,
worktree_id: worktree_id.to_proto(),
language_name: language_name.clone().into(),
})
.await
.log_err()?;
if !response.has_values {
return None;
}
let toolchains = response
.toolchains
.into_iter()
.map(|toolchain| Toolchain {
language_name: language_name.clone(),
name: toolchain.name.into(),
path: toolchain.path.into(),
})
.collect();
let groups = response
.groups
.into_iter()
.filter_map(|group| {
Some((usize::try_from(group.start_index).ok()?, group.name.into()))
})
.collect();
Some(ToolchainList {
toolchains,
default: None,
groups,
})
})
}
pub(crate) fn active_toolchain(
&self,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: &AppContext,
) -> Task<Option<Toolchain>> {
let project_id = self.project_id;
let client = self.client.clone();
cx.spawn(move |_| async move {
let response = client
.request(proto::ActiveToolchain {
project_id,
worktree_id: worktree_id.to_proto(),
language_name: language_name.clone().into(),
})
.await
.log_err()?;
response.toolchain.map(|toolchain| Toolchain {
language_name: language_name.clone(),
name: toolchain.name.into(),
path: toolchain.path.into(),
})
})
}
}

View file

@ -280,11 +280,15 @@ message Envelope {
LanguageServerPromptRequest language_server_prompt_request = 268;
LanguageServerPromptResponse language_server_prompt_response = 269;
GitBranches git_branches = 270;
GitBranchesResponse git_branches_response = 271;
UpdateGitBranch update_git_branch = 272; // current max
UpdateGitBranch update_git_branch = 272;
ListToolchains list_toolchains = 273;
ListToolchainsResponse list_toolchains_response = 274;
ActivateToolchain activate_toolchain = 275;
ActiveToolchain active_toolchain = 276;
ActiveToolchainResponse active_toolchain_response = 277; // current max
}
@ -2393,7 +2397,6 @@ message GetPermalinkToLine {
message GetPermalinkToLineResponse {
string permalink = 1;
}
message FlushBufferedMessages {}
message FlushBufferedMessagesResponse {}
@ -2419,6 +2422,45 @@ message LanguageServerPromptResponse {
optional uint64 action_response = 1;
}
message ListToolchains {
uint64 project_id = 1;
uint64 worktree_id = 2;
string language_name = 3;
}
message Toolchain {
string name = 1;
string path = 2;
}
message ToolchainGroup {
uint64 start_index = 1;
string name = 2;
}
message ListToolchainsResponse {
repeated Toolchain toolchains = 1;
bool has_values = 2;
repeated ToolchainGroup groups = 3;
}
message ActivateToolchain {
uint64 project_id = 1;
uint64 worktree_id = 2;
Toolchain toolchain = 3;
string language_name = 4;
}
message ActiveToolchain {
uint64 project_id = 1;
uint64 worktree_id = 2;
string language_name = 3;
}
message ActiveToolchainResponse {
optional Toolchain toolchain = 1;
}
message Branch {
bool is_head = 1;
string name = 2;
@ -2438,4 +2480,5 @@ message UpdateGitBranch {
uint64 project_id = 1;
string branch_name = 2;
ProjectPath repository = 3;
}

View file

@ -358,7 +358,12 @@ messages!(
(LanguageServerPromptResponse, Foreground),
(GitBranches, Background),
(GitBranchesResponse, Background),
(UpdateGitBranch, Background)
(UpdateGitBranch, Background),
(ListToolchains, Foreground),
(ListToolchainsResponse, Foreground),
(ActivateToolchain, Foreground),
(ActiveToolchain, Foreground),
(ActiveToolchainResponse, Foreground)
);
request_messages!(
@ -475,7 +480,10 @@ request_messages!(
(FlushBufferedMessages, Ack),
(LanguageServerPromptRequest, LanguageServerPromptResponse),
(GitBranches, GitBranchesResponse),
(UpdateGitBranch, Ack)
(UpdateGitBranch, Ack),
(ListToolchains, ListToolchainsResponse),
(ActivateToolchain, Ack),
(ActiveToolchain, ActiveToolchainResponse)
);
entity_messages!(
@ -555,7 +563,10 @@ entity_messages!(
GetPermalinkToLine,
LanguageServerPromptRequest,
GitBranches,
UpdateGitBranch
UpdateGitBranch,
ListToolchains,
ActivateToolchain,
ActiveToolchain
);
entity_messages!(

View file

@ -10,7 +10,7 @@ use project::{
search::SearchQuery,
task_store::TaskStore,
worktree_store::WorktreeStore,
LspStore, LspStoreEvent, PrettierStore, ProjectPath, WorktreeId,
LspStore, LspStoreEvent, PrettierStore, ProjectPath, ToolchainStore, WorktreeId,
};
use remote::ssh_session::ChannelClient;
use rpc::{
@ -108,11 +108,14 @@ impl HeadlessProject {
observer.shared(SSH_PROJECT_ID, session.clone().into(), cx);
observer
});
let toolchain_store =
cx.new_model(|cx| ToolchainStore::local(languages.clone(), worktree_store.clone(), cx));
let lsp_store = cx.new_model(|cx| {
let mut lsp_store = LspStore::new_local(
buffer_store.clone(),
worktree_store.clone(),
prettier_store.clone(),
toolchain_store.clone(),
environment,
languages.clone(),
http_client,
@ -143,6 +146,7 @@ impl HeadlessProject {
session.subscribe_to_entity(SSH_PROJECT_ID, &cx.handle());
session.subscribe_to_entity(SSH_PROJECT_ID, &lsp_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &task_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &toolchain_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &settings_observer);
client.add_request_handler(cx.weak_model(), Self::handle_list_remote_directory);
@ -166,6 +170,7 @@ impl HeadlessProject {
SettingsObserver::init(&client);
LspStore::init(&client);
TaskStore::init(Some(&client));
ToolchainStore::init(&client);
HeadlessProject {
session: client,

View file

@ -0,0 +1,24 @@
[package]
name = "toolchain_selector"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[dependencies]
editor.workspace = true
fuzzy.workspace = true
gpui.workspace = true
language.workspace = true
picker.workspace = true
project.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
[lints]
workspace = true
[lib]
path = "src/toolchain_selector.rs"
doctest = false

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -0,0 +1,173 @@
use editor::Editor;
use gpui::{
div, AsyncWindowContext, EventEmitter, IntoElement, ParentElement, Render, Subscription, Task,
View, ViewContext, WeakModel, WeakView,
};
use language::{Buffer, BufferEvent, LanguageName, Toolchain};
use project::WorktreeId;
use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip};
use workspace::{item::ItemHandle, StatusItemView, Workspace};
use crate::ToolchainSelector;
pub struct ActiveToolchain {
active_toolchain: Option<Toolchain>,
workspace: WeakView<Workspace>,
active_buffer: Option<(WorktreeId, WeakModel<Buffer>, Subscription)>,
_observe_language_changes: Subscription,
_update_toolchain_task: Task<Option<()>>,
}
struct LanguageChanged;
impl EventEmitter<LanguageChanged> for ActiveToolchain {}
impl ActiveToolchain {
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
let view = cx.view().clone();
Self {
active_toolchain: None,
active_buffer: None,
workspace: workspace.weak_handle(),
_observe_language_changes: cx.subscribe(&view, |this, _, _: &LanguageChanged, cx| {
this._update_toolchain_task = Self::spawn_tracker_task(cx);
}),
_update_toolchain_task: Self::spawn_tracker_task(cx),
}
}
fn spawn_tracker_task(cx: &mut ViewContext<Self>) -> Task<Option<()>> {
cx.spawn(|this, mut cx| async move {
let active_file = this
.update(&mut cx, |this, _| {
this.active_buffer
.as_ref()
.map(|(_, buffer, _)| buffer.clone())
})
.ok()
.flatten()?;
let workspace = this
.update(&mut cx, |this, _| this.workspace.clone())
.ok()?;
let language_name = active_file
.update(&mut cx, |this, _| Some(this.language()?.name()))
.ok()
.flatten()?;
let worktree_id = active_file
.update(&mut cx, |this, cx| Some(this.file()?.worktree_id(cx)))
.ok()
.flatten()?;
let toolchain =
Self::active_toolchain(workspace, worktree_id, language_name, cx.clone()).await?;
let _ = this.update(&mut cx, |this, cx| {
this.active_toolchain = Some(toolchain);
cx.notify();
});
Some(())
})
}
fn update_lister(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
if let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx)) {
let subscription = cx.subscribe(&buffer, |_, _, event: &BufferEvent, cx| {
if let BufferEvent::LanguageChanged = event {
cx.emit(LanguageChanged)
}
});
self.active_buffer = Some((worktree_id, buffer.downgrade(), subscription));
cx.emit(LanguageChanged);
}
}
cx.notify();
}
fn active_toolchain(
workspace: WeakView<Workspace>,
worktree_id: WorktreeId,
language_name: LanguageName,
cx: AsyncWindowContext,
) -> Task<Option<Toolchain>> {
cx.spawn(move |mut cx| async move {
let workspace_id = workspace
.update(&mut cx, |this, _| this.database_id())
.ok()
.flatten()?;
let selected_toolchain = workspace
.update(&mut cx, |this, cx| {
this.project()
.read(cx)
.active_toolchain(worktree_id, language_name.clone(), cx)
})
.ok()?
.await;
if let Some(toolchain) = selected_toolchain {
Some(toolchain)
} else {
let project = workspace
.update(&mut cx, |this, _| this.project().clone())
.ok()?;
let toolchains = cx
.update(|cx| {
project
.read(cx)
.available_toolchains(worktree_id, language_name, cx)
})
.ok()?
.await?;
if let Some(toolchain) = toolchains.toolchains.first() {
// Since we don't have a selected toolchain, pick one for user here.
workspace::WORKSPACE_DB
.set_toolchain(workspace_id, worktree_id, toolchain.clone())
.await
.ok()?;
project
.update(&mut cx, |this, cx| {
this.activate_toolchain(worktree_id, toolchain.clone(), cx)
})
.ok()?
.await;
}
toolchains.toolchains.first().cloned()
}
})
}
}
impl Render for ActiveToolchain {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
el.child(
Button::new("change-toolchain", active_toolchain.name.clone())
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, cx| {
if let Some(workspace) = this.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
ToolchainSelector::toggle(workspace, cx)
});
}
}))
.tooltip(|cx| Tooltip::text("Select Toolchain", cx)),
)
})
}
}
impl StatusItemView for ActiveToolchain {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
self.active_toolchain.take();
self.update_lister(editor, cx);
}
cx.notify();
}
}

View file

@ -0,0 +1,343 @@
mod active_toolchain;
pub use active_toolchain::ActiveToolchain;
use editor::Editor;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
actions, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
};
use language::{LanguageName, Toolchain, ToolchainList};
use picker::{Picker, PickerDelegate};
use project::{Project, WorktreeId};
use std::{path::Path, sync::Arc};
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ModalView, Workspace};
actions!(toolchain, [Select]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(ToolchainSelector::register).detach();
}
pub struct ToolchainSelector {
picker: View<Picker<ToolchainSelectorDelegate>>,
}
impl ToolchainSelector {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(move |workspace, _: &Select, cx| {
Self::toggle(workspace, cx);
});
}
fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<()> {
let (_, buffer, _) = workspace
.active_item(cx)?
.act_as::<Editor>(cx)?
.read(cx)
.active_excerpt(cx)?;
let project = workspace.project().clone();
let language_name = buffer.read(cx).language()?.name();
let worktree_id = buffer.read(cx).file()?.worktree_id(cx);
let worktree_root_path = project
.read(cx)
.worktree_for_id(worktree_id, cx)?
.read(cx)
.abs_path();
let workspace_id = workspace.database_id()?;
let weak = workspace.weak_handle();
cx.spawn(move |workspace, mut cx| async move {
let active_toolchain = workspace::WORKSPACE_DB
.toolchain(workspace_id, worktree_id, language_name.clone())
.await
.ok()
.flatten();
workspace
.update(&mut cx, |this, cx| {
this.toggle_modal(cx, move |cx| {
ToolchainSelector::new(
weak,
project,
active_toolchain,
worktree_id,
worktree_root_path,
language_name,
cx,
)
});
})
.ok();
})
.detach();
Some(())
}
fn new(
workspace: WeakView<Workspace>,
project: Model<Project>,
active_toolchain: Option<Toolchain>,
worktree_id: WorktreeId,
worktree_root: Arc<Path>,
language_name: LanguageName,
cx: &mut ViewContext<Self>,
) -> Self {
let view = cx.view().downgrade();
let picker = cx.new_view(|cx| {
let delegate = ToolchainSelectorDelegate::new(
active_toolchain,
view,
workspace,
worktree_id,
worktree_root,
project,
language_name,
cx,
);
Picker::uniform_list(delegate, cx)
});
Self { picker }
}
}
impl Render for ToolchainSelector {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex().w(rems(34.)).child(self.picker.clone())
}
}
impl FocusableView for ToolchainSelector {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl EventEmitter<DismissEvent> for ToolchainSelector {}
impl ModalView for ToolchainSelector {}
pub struct ToolchainSelectorDelegate {
toolchain_selector: WeakView<ToolchainSelector>,
candidates: ToolchainList,
matches: Vec<StringMatch>,
selected_index: usize,
workspace: WeakView<Workspace>,
worktree_id: WorktreeId,
worktree_abs_path_root: Arc<Path>,
_fetch_candidates_task: Task<Option<()>>,
}
impl ToolchainSelectorDelegate {
#[allow(clippy::too_many_arguments)]
fn new(
active_toolchain: Option<Toolchain>,
language_selector: WeakView<ToolchainSelector>,
workspace: WeakView<Workspace>,
worktree_id: WorktreeId,
worktree_abs_path_root: Arc<Path>,
project: Model<Project>,
language_name: LanguageName,
cx: &mut ViewContext<Picker<Self>>,
) -> Self {
let _fetch_candidates_task = cx.spawn({
let project = project.clone();
move |this, mut cx| async move {
let available_toolchains = project
.update(&mut cx, |this, cx| {
this.available_toolchains(worktree_id, language_name, cx)
})
.ok()?
.await?;
let _ = this.update(&mut cx, move |this, cx| {
this.delegate.candidates = available_toolchains;
if let Some(active_toolchain) = active_toolchain {
if let Some(position) = this
.delegate
.candidates
.toolchains
.iter()
.position(|toolchain| *toolchain == active_toolchain)
{
this.delegate.set_selected_index(position, cx);
}
}
this.update_matches(this.query(cx), cx);
});
Some(())
}
});
Self {
toolchain_selector: language_selector,
candidates: Default::default(),
matches: vec![],
selected_index: 0,
workspace,
worktree_id,
worktree_abs_path_root,
_fetch_candidates_task,
}
}
fn relativize_path(path: SharedString, worktree_root: &Path) -> SharedString {
Path::new(&path.as_ref())
.strip_prefix(&worktree_root)
.ok()
.map(|suffix| Path::new(".").join(suffix))
.and_then(|path| path.to_str().map(String::from).map(SharedString::from))
.unwrap_or(path)
}
}
impl PickerDelegate for ToolchainSelectorDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Select a toolchain...".into()
}
fn match_count(&self) -> usize {
self.matches.len()
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(string_match) = self.matches.get(self.selected_index) {
let toolchain = self.candidates.toolchains[string_match.candidate_id].clone();
if let Some(workspace_id) = self
.workspace
.update(cx, |this, _| this.database_id())
.ok()
.flatten()
{
let workspace = self.workspace.clone();
let worktree_id = self.worktree_id;
cx.spawn(|_, mut cx| async move {
workspace::WORKSPACE_DB
.set_toolchain(workspace_id, worktree_id, toolchain.clone())
.await
.log_err();
workspace
.update(&mut cx, |this, cx| {
this.project().update(cx, |this, cx| {
this.activate_toolchain(worktree_id, toolchain, cx)
})
})
.ok()?
.await;
Some(())
})
.detach();
}
}
self.dismissed(cx);
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.toolchain_selector
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
fn update_matches(
&mut self,
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
let background = cx.background_executor().clone();
let candidates = self.candidates.clone();
let worktree_root_path = self.worktree_abs_path_root.clone();
cx.spawn(|this, mut cx| async move {
let matches = if query.is_empty() {
candidates
.toolchains
.into_iter()
.enumerate()
.map(|(index, candidate)| {
let path = Self::relativize_path(candidate.path, &worktree_root_path);
let string = format!("{}{}", candidate.name, path);
StringMatch {
candidate_id: index,
string,
positions: Vec::new(),
score: 0.0,
}
})
.collect()
} else {
let candidates = candidates
.toolchains
.into_iter()
.enumerate()
.map(|(candidate_id, toolchain)| {
let path = Self::relativize_path(toolchain.path, &worktree_root_path);
let string = format!("{}{}", toolchain.name, path);
StringMatchCandidate::new(candidate_id, string)
})
.collect::<Vec<_>>();
match_strings(
&candidates,
&query,
false,
100,
&Default::default(),
background,
)
.await
};
this.update(&mut cx, |this, cx| {
let delegate = &mut this.delegate;
delegate.matches = matches;
delegate.selected_index = delegate
.selected_index
.min(delegate.matches.len().saturating_sub(1));
cx.notify();
})
.log_err();
})
}
fn render_match(
&self,
ix: usize,
selected: bool,
_: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let mat = &self.matches[ix];
let toolchain = &self.candidates.toolchains[mat.candidate_id];
let label = toolchain.name.clone();
let path = Self::relativize_path(toolchain.path.clone(), &self.worktree_abs_path_root);
let (name_highlights, mut path_highlights) = mat
.positions
.iter()
.cloned()
.partition::<Vec<_>, _>(|index| *index < label.len());
path_highlights.iter_mut().for_each(|index| {
*index -= label.len();
});
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
.child(HighlightedLabel::new(label, name_highlights))
.child(
HighlightedLabel::new(path, path_highlights)
.size(LabelSize::Small)
.color(Color::Muted),
),
)
}
}

View file

@ -7,6 +7,8 @@ use client::DevServerProjectId;
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::{point, size, Axis, Bounds, WindowBounds, WindowId};
use language::{LanguageName, Toolchain};
use project::WorktreeId;
use remote::ssh_session::SshProjectId;
use sqlez::{
bindable::{Bind, Column, StaticColumnCount},
@ -204,7 +206,8 @@ define_connection! {
// preview: bool // Indicates if this item is a preview item
// )
pub static ref DB: WorkspaceDb<()> =
&[sql!(
&[
sql!(
CREATE TABLE workspaces(
workspace_id INTEGER PRIMARY KEY,
workspace_location BLOB UNIQUE,
@ -367,6 +370,16 @@ define_connection! {
sql!(
ALTER TABLE ssh_projects RENAME COLUMN path TO paths;
),
sql!(
CREATE TABLE toolchains (
workspace_id INTEGER,
worktree_id INTEGER,
language_name TEXT NOT NULL,
name TEXT NOT NULL,
path TEXT NOT NULL,
PRIMARY KEY (workspace_id, worktree_id, language_name)
);
),
];
}
@ -528,6 +541,7 @@ impl WorkspaceDb {
match workspace.location {
SerializedWorkspaceLocation::Local(local_paths, local_paths_order) => {
conn.exec_bound(sql!(
DELETE FROM toolchains WHERE workspace_id = ?1;
DELETE FROM workspaces WHERE local_paths = ? AND workspace_id != ?
))?((&local_paths, workspace.id))
.context("clearing out old locations")?;
@ -576,6 +590,7 @@ impl WorkspaceDb {
}
SerializedWorkspaceLocation::Ssh(ssh_project) => {
conn.exec_bound(sql!(
DELETE FROM toolchains WHERE workspace_id = ?1;
DELETE FROM workspaces WHERE ssh_project_id = ? AND workspace_id != ?
))?((ssh_project.id.0, workspace.id))
.context("clearing out old locations")?;
@ -737,6 +752,7 @@ impl WorkspaceDb {
query! {
pub async fn delete_workspace_by_id(id: WorkspaceId) -> Result<()> {
DELETE FROM toolchains WHERE workspace_id = ?1;
DELETE FROM workspaces
WHERE workspace_id IS ?
}
@ -751,6 +767,7 @@ impl WorkspaceDb {
DELETE FROM dev_server_projects WHERE id = ?
))?(id.0)?;
conn.exec_bound(sql!(
DELETE FROM toolchains WHERE workspace_id = ?1;
DELETE FROM workspaces
WHERE dev_server_project_id IS ?
))?(id.0)
@ -1053,6 +1070,83 @@ impl WorkspaceDb {
WHERE workspace_id = ?1
}
}
pub async fn toolchain(
&self,
workspace_id: WorkspaceId,
worktree_id: WorktreeId,
language_name: LanguageName,
) -> Result<Option<Toolchain>> {
self.write(move |this| {
let mut select = this
.select_bound(sql!(
SELECT name, path FROM toolchains WHERE workspace_id = ? AND language_name = ? AND worktree_id = ?
))
.context("Preparing insertion")?;
let toolchain: Vec<(String, String)> =
select((workspace_id, language_name.0.to_owned(), worktree_id.to_usize()))?;
Ok(toolchain.into_iter().next().map(|(name, path)| Toolchain {
name: name.into(),
path: path.into(),
language_name,
}))
})
.await
}
pub(crate) async fn toolchains(
&self,
workspace_id: WorkspaceId,
) -> Result<Vec<(Toolchain, WorktreeId)>> {
self.write(move |this| {
let mut select = this
.select_bound(sql!(
SELECT name, path, worktree_id, language_name FROM toolchains WHERE workspace_id = ?
))
.context("Preparing insertion")?;
let toolchain: Vec<(String, String, u64, String)> =
select(workspace_id)?;
Ok(toolchain.into_iter().map(|(name, path, worktree_id, language_name)| (Toolchain {
name: name.into(),
path: path.into(),
language_name: LanguageName::new(&language_name),
}, WorktreeId::from_proto(worktree_id))).collect())
})
.await
}
pub async fn set_toolchain(
&self,
workspace_id: WorkspaceId,
worktree_id: WorktreeId,
toolchain: Toolchain,
) -> Result<()> {
self.write(move |conn| {
let mut insert = conn
.exec_bound(sql!(
INSERT INTO toolchains(workspace_id, worktree_id, language_name, name, path) VALUES (?, ?, ?, ?, ?)
ON CONFLICT DO
UPDATE SET
name = ?4,
path = ?5
))
.context("Preparing insertion")?;
insert((
workspace_id,
worktree_id.to_usize(),
toolchain.language_name.0.as_ref(),
toolchain.name.as_ref(),
toolchain.path.as_ref(),
))?;
Ok(())
}).await
}
}
#[cfg(test)]

View file

@ -1153,6 +1153,14 @@ impl Workspace {
DB.next_id().await.unwrap_or_else(|_| Default::default())
};
let toolchains = DB.toolchains(workspace_id).await?;
for (toolchain, worktree_id) in toolchains {
project_handle
.update(&mut cx, |this, cx| {
this.activate_toolchain(worktree_id, toolchain, cx)
})?
.await;
}
let window = if let Some(window) = requesting_window {
cx.update_window(window.into(), |_, cx| {
cx.replace_root_view(|cx| {
@ -5522,6 +5530,14 @@ pub fn open_ssh_project(
)
})?;
let toolchains = DB.toolchains(workspace_id).await?;
for (toolchain, worktree_id) in toolchains {
project
.update(&mut cx, |this, cx| {
this.activate_toolchain(worktree_id, toolchain, cx)
})?
.await;
}
let mut project_paths_to_open = vec![];
let mut project_path_errors = vec![];

View file

@ -104,6 +104,7 @@ terminal_view.workspace = true
theme.workspace = true
theme_selector.workspace = true
time.workspace = true
toolchain_selector.workspace = true
ui.workspace = true
reqwest_client.workspace = true
url.workspace = true

View file

@ -441,6 +441,7 @@ fn main() {
terminal_view::init(cx);
journal::init(app_state.clone(), cx);
language_selector::init(cx);
toolchain_selector::init(cx);
theme_selector::init(cx);
language_tools::init(cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);

View file

@ -208,6 +208,8 @@ pub fn initialize_workspace(
activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
let active_buffer_language =
cx.new_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
let active_toolchain_language =
cx.new_view(|cx| toolchain_selector::ActiveToolchain::new(workspace, cx));
let vim_mode_indicator = cx.new_view(vim::ModeIndicator::new);
let cursor_position =
cx.new_view(|_| go_to_line::cursor_position::CursorPosition::new(workspace));
@ -216,6 +218,7 @@ pub fn initialize_workspace(
status_bar.add_left_item(activity_indicator, cx);
status_bar.add_right_item(inline_completion_button, cx);
status_bar.add_right_item(active_buffer_language, cx);
status_bar.add_right_item(active_toolchain_language, cx);
status_bar.add_right_item(vim_mode_indicator, cx);
status_bar.add_right_item(cursor_position, cx);
});

View file

@ -36,3 +36,141 @@ license = "BSD-3-Clause"
[[fuchsia-cprng.clarify.files]]
path = 'LICENSE'
checksum = '03b114f53e6587a398931762ee11e2395bfdba252a329940e2c8c9e81813845b'
[pet.clarify]
license = "MIT"
[[pet.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-conda.clarify]
license = "MIT"
[[pet-conda.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-core.clarify]
license = "MIT"
[[pet-core.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-env-var-path.clarify]
license = "MIT"
[[pet-env-var-path.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-fs.clarify]
license = "MIT"
[[pet-fs.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-global-virtualenvs.clarify]
license = "MIT"
[[pet-global-virtualenvs.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-homebrew.clarify]
license = "MIT"
[[pet-homebrew.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-jsonrpc.clarify]
license = "MIT"
[[pet-jsonrpc.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-linux-global-python.clarify]
license = "MIT"
[[pet-linux-global-python.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-mac-commandlinetools.clarify]
license = "MIT"
[[pet-mac-commandlinetools.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-mac-python-org.clarify]
license = "MIT"
[[pet-mac-python-org.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-mac-xcode.clarify]
license = "MIT"
[[pet-mac-xcode.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-pipenv.clarify]
license = "MIT"
[[pet-pipenv.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-poetry.clarify]
license = "MIT"
[[pet-poetry.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-pyenv.clarify]
license = "MIT"
[[pet-pyenv.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-python-utils.clarify]
license = "MIT"
[[pet-python-utils.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-reporter.clarify]
license = "MIT"
[[pet-reporter.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-telemetry.clarify]
license = "MIT"
[[pet-telemetry.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-venv.clarify]
license = "MIT"
[[pet-venv.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-virtualenv.clarify]
license = "MIT"
[[pet-virtualenv.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-virtualenvwrapper.clarify]
license = "MIT"
[[pet-virtualenvwrapper.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-windows-registry.clarify]
license = "MIT"
[[pet-windows-registry.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'
[pet-windows-store.clarify]
license = "MIT"
[[pet-windows-store.clarify.git]]
path = 'LICENSE'
checksum = 'c2cfccb812fe482101a8f04597dfc5a9991a6b2748266c47ac91b6a5aae15383'