Skip to content

Commit 360e5d4

Browse files
authored
Automatically download and update PowerShell Editor Services for LSP (#7)
* Fix syntax schemes to compile * Update to latest grammar version * Update to latest Extension API * Download Editor Services as required * Add support for psm1 files * Move operators to an array
1 parent 25b5e2a commit 360e5d4

File tree

7 files changed

+162
-137
lines changed

7 files changed

+162
-137
lines changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ path = "src/powershell.rs"
88
crate-type = ["cdylib"]
99

1010
[dependencies]
11-
zed_extension_api = "0.0.6"
11+
zed_extension_api = "0.2.0"

extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ repository = "https://github.com/wingyplus/zed-powershell"
88

99
[grammars.powershell]
1010
repository = "https://github.com/airbus-cert/tree-sitter-powershell"
11-
commit = "804d86fd4ad286bd0cc1c1f0f7b28bd7af6755ad"
11+
commit = "b3fe65637fca35e426dc8c889d86c8724aaa73d5" # v0.24.4
1212

1313
[language_servers.powershell-es]
1414
name = "PowerShell Editor Services"

languages/powershell/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "PowerShell"
22
grammar = "powershell"
3-
path_suffixes = ["ps1"]
3+
path_suffixes = ["ps1", "psm1"]
44
line_comments = ["# "]
55
# autoclose_before = ";:.,=}])>"
66
brackets = [

languages/powershell/highlights.scm

Lines changed: 75 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
; Keywords
12
[
23
"begin"
34
"break"
45
"catch"
56
"class"
6-
"clean"
7+
; "clean"
78
"continue"
89
"data"
9-
"define"
10+
; "define"
1011
"do"
1112
"dynamicparam"
1213
"else"
@@ -18,7 +19,7 @@
1819
"finally"
1920
"for"
2021
"foreach"
21-
"from"
22+
; "from"
2223
"function"
2324
"hidden"
2425
"if"
@@ -32,84 +33,86 @@
3233
"trap"
3334
"try"
3435
"until"
35-
"using"
36-
"var"
36+
; "using"
37+
; "var"
3738
"while"
3839
] @keyword
3940

4041
; Powershell Workflows
4142
[
42-
"inlinescript",
43-
"parallel",
44-
"sequence",
43+
"inlinescript"
44+
"parallel"
45+
"sequence"
4546
"workflow"
4647
] @keyword
4748

48-
"-as" @operator
49-
"-ccontains" @operator
50-
"-ceq" @operator
51-
"-cge" @operator
52-
"-cgt" @operator
53-
"-cle" @operator
54-
"-clike" @operator
55-
"-clt" @operator
56-
"-cmatch" @operator
57-
"-cne" @operator
58-
"-cnotcontains" @operator
59-
"-cnotlike" @operator
60-
"-cnotmatch" @operator
61-
"-contains" @operator
62-
"-creplace" @operator
63-
"-csplit" @operator
64-
"-eq" @operator
65-
"-ge" @operator
66-
"-gt" @operator
67-
"-icontains" @operator
68-
"-ieq" @operator
69-
"-ige" @operator
70-
"-igt" @operator
71-
"-ile" @operator
72-
"-ilike" @operator
73-
"-ilt" @operator
74-
"-imatch" @operator
75-
"-in" @operator
76-
"-ine" @operator
77-
"-inotcontains" @operator
78-
"-inotlike" @operator
79-
"-inotmatch" @operator
80-
"-ireplace" @operator
81-
"-is" @operator
82-
"-isnot" @operator
83-
"-isplit" @operator
84-
"-join" @operator
85-
"-le" @operator
86-
"-like" @operator
87-
"-lt" @operator
88-
"-match" @operator
89-
"-ne" @operator
90-
"-notcontains" @operator
91-
"-notin" @operator
92-
"-notlike" @operator
93-
"-notmatch" @operator
94-
"-replace" @operator
95-
"-shl" @operator
96-
"-shr" @operator
97-
"-split" @operator
98-
"-and" @operator
99-
"-or" @operator
100-
"-xor" @operator
101-
"-band" @operator
102-
"-bor" @operator
103-
"-bxor" @operator
104-
"+" @operator
105-
"-" @operator
106-
"/" @operator
107-
"\\" @operator
108-
"%" @operator
109-
"*" @operator
110-
".." @operator
111-
"-not" @operator
11249

50+
[
51+
"-as"
52+
"-ccontains"
53+
"-ceq"
54+
"-cge"
55+
"-cgt"
56+
"-cle"
57+
"-clike"
58+
"-clt"
59+
"-cmatch"
60+
"-cne"
61+
"-cnotcontains"
62+
"-cnotlike"
63+
"-cnotmatch"
64+
"-contains"
65+
"-creplace"
66+
"-csplit"
67+
"-eq"
68+
"-ge"
69+
"-gt"
70+
"-icontains"
71+
"-ieq"
72+
"-ige"
73+
"-igt"
74+
"-ile"
75+
"-ilike"
76+
"-ilt"
77+
"-imatch"
78+
"-in"
79+
"-ine"
80+
"-inotcontains"
81+
"-inotlike"
82+
"-inotmatch"
83+
"-ireplace"
84+
"-is"
85+
"-isnot"
86+
"-isplit"
87+
"-join"
88+
"-le"
89+
"-like"
90+
"-lt"
91+
"-match"
92+
"-ne"
93+
"-notcontains"
94+
"-notin"
95+
"-notlike"
96+
"-notmatch"
97+
"-replace"
98+
"-shl"
99+
"-shr"
100+
"-split"
101+
"-and"
102+
"-or"
103+
"-xor"
104+
"-band"
105+
"-bor"
106+
"-bxor"
107+
"+"
108+
"-"
109+
"/"
110+
"\\"
111+
"%"
112+
"*"
113+
".."
114+
"-not"
115+
] @operator
113116

114117
[
115118
","

src/language_server.rs

Lines changed: 0 additions & 39 deletions
This file was deleted.

src/powershell.rs

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
mod language_server;
2-
3-
use zed_extension_api::{self as zed, settings::LspSettings};
4-
5-
use crate::language_server::PowerShellEditorServices as EditorServices;
1+
use std::fs;
2+
use std::path;
3+
use zed_extension_api::{self as zed, Result};
64

75
struct PowerShellExtension {
86
/// The PowerShell binary, default to `pwsh`.
@@ -16,7 +14,7 @@ impl zed::Extension for PowerShellExtension {
1614
Self: Sized,
1715
{
1816
Self {
19-
powershell_bin: Some("pwsh".to_string()),
17+
powershell_bin: None,
2018
}
2119
}
2220

@@ -25,21 +23,13 @@ impl zed::Extension for PowerShellExtension {
2523
language_server_id: &zed_extension_api::LanguageServerId,
2624
worktree: &zed_extension_api::Worktree,
2725
) -> zed_extension_api::Result<zed_extension_api::Command> {
28-
let pwsh_bin = worktree
29-
.which(self.powershell_bin.clone().unwrap().as_str())
30-
.ok_or_else(|| "No PowerShell command found")?;
31-
32-
let bundle_path = LspSettings::for_worktree("powershell-es", worktree)
33-
.ok()
34-
.and_then(|lsp_settings| lsp_settings.binary)
35-
.and_then(|binary| binary.path)
36-
.unwrap();
26+
let pwsh_bin = PowerShellExtension::powershell_binary_path(self, worktree).unwrap();
3727

38-
// TODO: make remote install works.
39-
// let bundle_path = EditorServices::install(language_server_id)
40-
// .map_err(|err| format!("failed to get editor services: {}", err))?;
28+
let bundle_path = self
29+
.language_server_path(language_server_id)
30+
.map_err(|err| format!("failed to get editor services: {}", err))?;
4131

42-
let command = format!("{bundle_path}/PowerShellEditorServices/Start-EditorServices.ps1 -BundledModulesPath {bundle_path} -Stdio -SessionDetailsPath {bundle_path}/powershell-es.session.json -LogPath {bundle_path}/logs -FeatureFlags @() -AdditionalModules @() -HostName zed -HostProfileId 0 -HostVersion 1.0.0 -LogLevel Diagnostic");
32+
let command = format!("Import-Module (Join-Path '{bundle_path}' 'PowerShellEditorServices/PowerShellEditorServices.psd1'); Start-EditorServices -Stdio -SessionDetailsPath '{bundle_path}/powershell-es.session.json' -LogPath '{bundle_path}/logs' -FeatureFlags @() -AdditionalModules @() -HostName zed -HostProfileId 0 -HostVersion 1.0.0 -LogLevel Diagnostic");
4333

4434
Ok(zed::Command {
4535
command: pwsh_bin,
@@ -54,4 +44,75 @@ impl zed::Extension for PowerShellExtension {
5444
}
5545
}
5646

47+
impl PowerShellExtension {
48+
fn powershell_binary_path(&mut self, worktree: &zed::Worktree) -> Result<String> {
49+
let pwsh_path = match &self.powershell_bin {
50+
Some(path) if fs::metadata(path).map_or(false, |stat| stat.is_file()) => path.clone(),
51+
Some(path) => worktree
52+
.which(path.clone().as_str())
53+
.ok_or_else(|| "PowerShell must be installed for PowerShell Extension")?,
54+
None => worktree
55+
.which("pwsh")
56+
.ok_or_else(|| "PowerShell must be installed for PowerShell Extension")?,
57+
};
58+
self.powershell_bin = Some(pwsh_path.clone());
59+
Ok(pwsh_path)
60+
}
61+
62+
fn language_server_path(
63+
&mut self,
64+
language_server_id: &zed_extension_api::LanguageServerId,
65+
) -> Result<String> {
66+
zed::set_language_server_installation_status(
67+
language_server_id,
68+
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
69+
);
70+
71+
let release = zed::latest_github_release(
72+
"PowerShell/PowerShellEditorServices",
73+
zed::GithubReleaseOptions {
74+
require_assets: true,
75+
pre_release: false,
76+
},
77+
)?;
78+
79+
let asset = release
80+
.assets
81+
.iter()
82+
.find(|asset| asset.name == "PowerShellEditorServices.zip")
83+
.ok_or_else(|| format!("no PowerShellEditorServices.zip found"))?;
84+
85+
let version_dir = format!("powershell-es-{}", release.version);
86+
let lsp_path = format!("{version_dir}/PowerShellEditorServices/Start-EditorServices.ps1");
87+
88+
if !fs::metadata(&lsp_path).map_or(false, |stat| stat.is_file()) {
89+
// Download the asset
90+
zed::set_language_server_installation_status(
91+
&language_server_id,
92+
&zed::LanguageServerInstallationStatus::Downloading,
93+
);
94+
zed::download_file(
95+
&asset.download_url,
96+
&version_dir,
97+
zed::DownloadedFileType::Zip,
98+
)
99+
.map_err(|err| format!("download error {}", err))?;
100+
101+
// Ensure the binary exists
102+
let entries =
103+
fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
104+
for entry in entries {
105+
let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
106+
if entry.file_name().to_str() != Some(&version_dir) {
107+
fs::remove_dir_all(entry.path()).ok();
108+
}
109+
}
110+
}
111+
112+
let abs_path =
113+
path::absolute(&version_dir).map_err(|e| format!("failed to get absolute path {e}"))?;
114+
Ok(abs_path.display().to_string())
115+
}
116+
}
117+
57118
zed::register_extension!(PowerShellExtension);

0 commit comments

Comments
 (0)