Detailed changes
@@ -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:
@@ -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<_>)
- });
-
- Ok((config, queries, tasks))
+ 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(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;
@@ -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()?;
+
+ 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()?;
- for (adapter, server, delegate) in servers {
- let settings = adapter.workspace_configuration(&delegate, &mut cx).await?;
+ server
+ .notify::<lsp::notification::DidChangeConfiguration>(
+ lsp::DidChangeConfigurationParams { settings },
+ )
+ .ok();
+ }
+ Some(())
+ })
+ .await;
+ }
- server
- .notify::<lsp::notification::DidChangeConfiguration>(
- lsp::DidChangeConfigurationParams { settings },
- )
- .ok();
- }
+ 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,
@@ -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,
@@ -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
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -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();
+ }
+}
@@ -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'