mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 10:40:54 +00:00
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:
parent
03bd95405b
commit
cdddb4d360
33 changed files with 2221 additions and 133 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -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
493
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
65
crates/language/src/toolchain.rs
Normal file
65
crates/language/src/toolchain.rs
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
416
crates/project/src/toolchain_store.rs
Normal file
416
crates/project/src/toolchain_store.rs
Normal 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(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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,
|
||||
|
|
24
crates/toolchain_selector/Cargo.toml
Normal file
24
crates/toolchain_selector/Cargo.toml
Normal 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
|
1
crates/toolchain_selector/LICENSE-GPL
Symbolic link
1
crates/toolchain_selector/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-GPL
|
173
crates/toolchain_selector/src/active_toolchain.rs
Normal file
173
crates/toolchain_selector/src/active_toolchain.rs
Normal 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();
|
||||
}
|
||||
}
|
343
crates/toolchain_selector/src/toolchain_selector.rs
Normal file
343
crates/toolchain_selector/src/toolchain_selector.rs
Normal 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),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)]
|
||||
|
|
|
@ -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![];
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue