lsp-pwsh.el (15256B)
1 ;;; lsp-pwsh.el --- client for PowerShellEditorServices -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2019 Kien Nguyen 4 5 ;; Author: kien.n.quang at gmail.com 6 ;; Keywords: lsp 7 8 ;; This program is free software; you can redistribute it and/or modify 9 ;; it under the terms of the GNU General Public License as published by 10 ;; the Free Software Foundation, either version 3 of the License, or 11 ;; (at your option) any later version. 12 13 ;; This program is distributed in the hope that it will be useful, 14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ;; GNU General Public License for more details. 17 18 ;; You should have received a copy of the GNU General Public License 19 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 20 21 ;;; Commentary: 22 23 ;; 24 25 ;;; Code: 26 27 (require 'f) 28 (require 'dash) 29 (require 's) 30 (require 'ht) 31 32 (require 'lsp-protocol) 33 (require 'lsp-mode) 34 35 (defgroup lsp-pwsh nil 36 "LSP support for PowerShell, using the PowerShellEditorServices." 37 :group 'lsp-mode 38 :package-version '(lsp-mode . "6.2")) 39 40 ;; PowerShell vscode flags 41 (defcustom lsp-pwsh-help-completion "BlockComment" 42 "Controls the comment-based help completion behavior triggered by typing '##'. 43 Set the generated help style with 'BlockComment' or 'LineComment'. 44 Disable the feature with 'Disabled'." 45 :type 46 '(choice 47 (:tag "Disabled" "BlockComment" "LineComment")) 48 :group 'lsp-pwsh 49 :package-version '(lsp-mode . "6.2")) 50 51 (defcustom lsp-pwsh-script-analysis-enable t 52 "Enables real-time script analysis from PowerShell Script Analyzer. 53 Uses the newest installed version of the PSScriptAnalyzer module or the 54 version bundled with this extension, if it is newer." 55 :type 'boolean 56 :group 'lsp-pwsh 57 :package-version '(lsp-mode . "6.2")) 58 59 (defcustom lsp-pwsh-script-analysis-settings-path "" 60 "Specifies the path to a PowerShell Script Analyzer settings file. 61 To override the default settings for all projects, enter an absolute path, 62 or enter a path relative to your workspace." 63 :type 'string 64 :group 'lsp-pwsh 65 :package-version '(lsp-mode . "6.2")) 66 67 (defcustom lsp-pwsh-code-folding-enable t 68 "Enables syntax based code folding. 69 When disabled, the default indentation based code folding is used." 70 :type 'boolean 71 :group 'lsp-pwsh 72 :package-version '(lsp-mode . "6.2")) 73 74 (defcustom lsp-pwsh-code-folding-show-last-line t 75 "Shows the last line of a folded section. 76 Similar to the default VSCode folding style. 77 When disabled, the entire folded region is hidden." 78 :type 'boolean 79 :group 'lsp-pwsh 80 :package-version '(lsp-mode . "6.2")) 81 82 (defcustom lsp-pwsh-code-formatting-preset "Custom" 83 "Sets the codeformatting options to follow the given indent style. 84 Sets in a way that is compatible with PowerShell syntax. 85 For more information about the brace styles please refer to https://github.com/PoshCode/PowerShellPracticeAndStyle/issues/81." 86 :type 87 '(choice 88 (:tag "Custom" "Allman" "OTBS" "Stroustrup")) 89 :group 'lsp-pwsh 90 :package-version '(lsp-mode . "6.2")) 91 92 (defcustom lsp-pwsh-code-formatting-open-brace-on-same-line t 93 "Places open brace on the same line as its associated statement." 94 :type 'boolean 95 :group 'lsp-pwsh 96 :package-version '(lsp-mode . "6.2")) 97 98 (defcustom lsp-pwsh-code-formatting-new-line-after-open-brace t 99 "Adds a newline (line break) after an open brace." 100 :type 'boolean 101 :group 'lsp-pwsh 102 :package-version '(lsp-mode . "6.2")) 103 104 (defcustom lsp-pwsh-code-formatting-new-line-after-close-brace t 105 "Adds a newline (line break) after a closing brace." 106 :type 'boolean 107 :group 'lsp-pwsh 108 :package-version '(lsp-mode . "6.2")) 109 110 (defcustom lsp-pwsh-code-formatting-pipeline-indentation-style "NoIndentation" 111 "Multi-line pipeline style settings." 112 :type 113 '(choice 114 (:tag "IncreaseIndentationForFirstPipeline" "IncreaseIndentationAfterEveryPipeline" "NoIndentation")) 115 :group 'lsp-pwsh 116 :package-version '(lsp-mode . "6.2")) 117 118 (defcustom lsp-pwsh-code-formatting-whitespace-before-open-brace t 119 "Adds a space between a keyword and its associated scriptblock expression." 120 :type 'boolean 121 :group 'lsp-pwsh 122 :package-version '(lsp-mode . "6.2")) 123 124 (defcustom lsp-pwsh-code-formatting-whitespace-before-open-paren t 125 "Adds a space between a keyword (if, elseif, while, switch, etc) and its 126 associated conditional expression." 127 :type 'boolean 128 :group 'lsp-pwsh 129 :package-version '(lsp-mode . "6.2")) 130 131 (defcustom lsp-pwsh-code-formatting-whitespace-around-operator t 132 "Adds spaces before and after an operator ('=', '+', '-', etc.)." 133 :type 'boolean 134 :group 'lsp-pwsh 135 :package-version '(lsp-mode . "6.2")) 136 137 (defcustom lsp-pwsh-code-formatting-whitespace-after-separator t 138 "Adds a space after a separator (',' and ';')." 139 :type 'boolean 140 :group 'lsp-pwsh 141 :package-version '(lsp-mode . "6.2")) 142 143 (defcustom lsp-pwsh-code-formatting-whitespace-inside-brace t 144 "Adds a space after an opening brace ('{') and before a closing brace ('}')." 145 :type 'boolean 146 :group 'lsp-pwsh 147 :package-version '(lsp-mode . "6.2")) 148 149 (defcustom lsp-pwsh-code-formatting-whitespace-around-pipe t 150 "Adds a space before and after the pipeline operator ('|')." 151 :type 'boolean 152 :group 'lsp-pwsh 153 :package-version '(lsp-mode . "6.2")) 154 155 (defcustom lsp-pwsh-code-formatting-ignore-one-line-block t 156 "Does not reformat one-line code blocks, such as \"if (...) {...} else 157 {...}\"." 158 :type 'boolean 159 :group 'lsp-pwsh 160 :package-version '(lsp-mode . "6.2")) 161 162 (defcustom lsp-pwsh-code-formatting-align-property-value-pairs t 163 "Align assignment statements in a hashtable or a DSC Configuration." 164 :type 'boolean 165 :group 'lsp-pwsh 166 :package-version '(lsp-mode . "6.2")) 167 168 (defcustom lsp-pwsh-code-formatting-use-correct-casing nil 169 "Use correct casing for cmdlets." 170 :type 'boolean 171 :group 'lsp-pwsh 172 :package-version '(lsp-mode . "6.2")) 173 174 (defcustom lsp-pwsh-developer-editor-services-log-level "Normal" 175 "Sets the log level for the PowerShell Editor Services host executable. 176 Valid values are 'Diagnostic', 'Verbose', 'Normal', 'Warning', and 'Error'" 177 :type 178 '(choice 179 (:tag "Diagnostic" "Verbose" "Normal" "Warning" "Error")) 180 :group 'lsp-pwsh 181 :package-version '(lsp-mode . "6.2")) 182 183 (defcustom lsp-pwsh-developer-editor-services-wait-for-debugger nil 184 "Launches the language service with the /waitForDebugger flag to force it to 185 wait for a .NET debugger to attach before proceeding." 186 :type 'boolean 187 :group 'lsp-pwsh 188 :package-version '(lsp-mode . "6.2")) 189 190 (defcustom lsp-pwsh-developer-feature-flags nil 191 "An array of strings that enable experimental features in the PowerShell 192 extension." 193 :type 194 '(repeat string) 195 :group 'lsp-pwsh 196 :package-version '(lsp-mode . "6.2")) 197 198 (lsp-register-custom-settings 199 '(("powershell.developer.featureFlags" lsp-pwsh-developer-feature-flags) 200 ("powershell.developer.editorServicesWaitForDebugger" lsp-pwsh-developer-editor-services-wait-for-debugger t) 201 ("powershell.codeFormatting.useCorrectCasing" lsp-pwsh-code-formatting-use-correct-casing t) 202 ("powershell.codeFormatting.alignPropertyValuePairs" lsp-pwsh-code-formatting-align-property-value-pairs t) 203 ("powershell.codeFormatting.ignoreOneLineBlock" lsp-pwsh-code-formatting-ignore-one-line-block t) 204 ("powershell.codeFormatting.whitespaceAroundPipe" lsp-pwsh-code-formatting-whitespace-around-pipe t) 205 ("powershell.codeFormatting.whitespaceInsideBrace" lsp-pwsh-code-formatting-whitespace-inside-brace t) 206 ("powershell.codeFormatting.whitespaceAfterSeparator" lsp-pwsh-code-formatting-whitespace-after-separator t) 207 ("powershell.codeFormatting.whitespaceAroundOperator" lsp-pwsh-code-formatting-whitespace-around-operator t) 208 ("powershell.codeFormatting.whitespaceBeforeOpenParen" lsp-pwsh-code-formatting-whitespace-before-open-paren t) 209 ("powershell.codeFormatting.whitespaceBeforeOpenBrace" lsp-pwsh-code-formatting-whitespace-before-open-brace t) 210 ("powershell.codeFormatting.pipelineIndentationStyle" lsp-pwsh-code-formatting-pipeline-indentation-style) 211 ("powershell.codeFormatting.newLineAfterCloseBrace" lsp-pwsh-code-formatting-new-line-after-close-brace t) 212 ("powershell.codeFormatting.newLineAfterOpenBrace" lsp-pwsh-code-formatting-new-line-after-open-brace t) 213 ("powershell.codeFormatting.openBraceOnSameLine" lsp-pwsh-code-formatting-open-brace-on-same-line t) 214 ("powershell.codeFormatting.preset" lsp-pwsh-code-formatting-preset) 215 ("powershell.codeFolding.showLastLine" lsp-pwsh-code-folding-show-last-line t) 216 ("powershell.codeFolding.enable" lsp-pwsh-code-folding-enable t) 217 ("powershell.scriptAnalysis.settingsPath" lsp-pwsh-script-analysis-settings-path) 218 ("powershell.scriptAnalysis.enable" lsp-pwsh-script-analysis-enable t) 219 ("powershell.helpCompletion" lsp-pwsh-help-completion))) 220 221 ;; lsp-pwsh custom variables 222 (defcustom lsp-pwsh-ext-path (expand-file-name "pwsh" lsp-server-install-dir) 223 "The path to powershell vscode extension." 224 :type 'string 225 :group 'lsp-pwsh 226 :package-version '(lsp-mode . "6.2")) 227 228 (defcustom lsp-pwsh-exe (or (executable-find "pwsh") (executable-find "powershell")) 229 "PowerShell executable." 230 :type 'string 231 :group 'lsp-pwsh 232 :package-version '(lsp-mode . "6.2")) 233 234 (defcustom lsp-pwsh-dir lsp-pwsh-ext-path 235 "Path to PowerShellEditorServices without last slash." 236 :type 'string 237 :group 'lsp-pwsh 238 :package-version '(lsp-mode . "6.2")) 239 240 (defvar lsp-pwsh-pses-script (expand-file-name "PowerShellEditorServices/Start-EditorServices.ps1" 241 lsp-pwsh-dir) 242 "Main script to start PSES.") 243 244 (defvar lsp-pwsh-log-path (expand-file-name "logs" lsp-pwsh-ext-path) 245 "Path to directory where server will write log files. 246 Must not nil.") 247 248 (defvar lsp-pwsh--sess-id (emacs-pid)) 249 250 (defun lsp-pwsh--command () 251 "Return the command to start server." 252 `(,lsp-pwsh-exe "-NoProfile" "-NonInteractive" "-NoLogo" 253 ,@(if (eq system-type 'windows-nt) '("-ExecutionPolicy" "Bypass")) 254 "-OutputFormat" "Text" 255 "-File" 256 ,lsp-pwsh-pses-script 257 "-HostName" "\"Emacs Host\"" 258 "-HostProfileId" "'Emacs.LSP'" 259 "-HostVersion" "9.0.0" 260 "-LogPath" ,(expand-file-name "emacs-powershell.log" lsp-pwsh-log-path) 261 "-LogLevel" ,lsp-pwsh-developer-editor-services-log-level 262 "-SessionDetailsPath" ,(expand-file-name (format "PSES-VSCode-%d" lsp-pwsh--sess-id) 263 lsp-pwsh-log-path) 264 ;; "-AdditionalModules" "@('PowerShellEditorServices.VSCode')" 265 "-Stdio" 266 "-BundledModulesPath" ,lsp-pwsh-dir 267 "-FeatureFlags" "@()")) 268 269 (defun lsp-pwsh--extra-init-params () 270 "Return form describing parameters for language server.") 271 272 (lsp-defun lsp-pwsh--apply-code-action-edits ((&Command :command :arguments?)) 273 "Handle ACTION for PowerShell.ApplyCodeActionEdits." 274 (-if-let* (((&pwsh:ScriptRegion :start-line-number :end-line-number 275 :start-column-number :end-column-number :text) 276 (lsp-seq-first arguments?)) 277 (start-position (lsp-make-position :line (1- start-line-number) 278 :character (1- start-column-number))) 279 (end-position (lsp-make-position :line (1- end-line-number) 280 :character (1- end-column-number))) 281 (edits `[,(lsp-make-text-edit :range (lsp-make-range :start start-position 282 :end end-position) 283 :newText text)])) 284 (lsp--apply-text-edits edits 'code-action) 285 (lsp-send-execute-command command arguments?))) 286 287 (lsp-defun lsp-pwsh--show-code-action-document ((&Command :arguments?)) 288 "Handle ACTION for PowerShell.ShowCodeActionDocumentation." 289 (-if-let* ((rule-raw (lsp-seq-first arguments?)) 290 (rule-id (if (s-prefix-p "PS" rule-raw) (substring rule-raw 2) rule-raw))) 291 (browse-url 292 (concat "https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/rules/" 293 rule-id)) 294 (lsp-warn "Cannot show documentation for code action, no ruleName was supplied"))) 295 296 (lsp-register-client 297 (make-lsp-client 298 :new-connection (lsp-stdio-connection #'lsp-pwsh--command 299 (lambda () 300 (f-exists? lsp-pwsh-pses-script))) 301 :activation-fn (lsp-activate-on "powershell") 302 :server-id 'pwsh-ls 303 :priority -1 304 :initialization-options #'lsp-pwsh--extra-init-params 305 :notification-handlers (ht ("powerShell/executionStatusChanged" #'ignore) 306 ("output" #'ignore)) 307 :action-handlers (ht ("PowerShell.ApplyCodeActionEdits" 308 #'lsp-pwsh--apply-code-action-edits) 309 ("PowerShell.ShowCodeActionDocumentation" 310 #'lsp-pwsh--show-code-action-document)) 311 :initialized-fn (lambda (w) 312 (with-lsp-workspace w 313 (lsp--set-configuration 314 (lsp-configuration-section "powershell"))) 315 (let ((caps (lsp--workspace-server-capabilities w))) 316 (lsp:set-server-capabilities-document-range-formatting-provider? caps t) 317 (lsp:set-server-capabilities-document-formatting-provider? caps t))) 318 :download-server-fn #'lsp-pwsh-setup)) 319 320 (defcustom lsp-pwsh-github-asset-url 321 "https://github.com/%s/%s/releases/latest/download/%s" 322 "GitHub latest asset template url." 323 :type 'string 324 :group 'lsp-pwsh 325 :package-version '(lsp-mode . "6.2")) 326 327 (defun lsp-pwsh-setup (_client callback error-callback update) 328 "Downloads PowerShellEditorServices to `lsp-pwsh-dir'. 329 CALLBACK is called when the download finish successfully otherwise 330 ERROR-CALLBACK is called. 331 UPDATE is non-nil if it is already downloaded. 332 FORCED if specified with prefix argument." 333 334 (unless (and lsp-pwsh-exe (file-executable-p lsp-pwsh-exe)) 335 (user-error "Use `lsp-pwsh-exe' with the value of `%s' is not a valid powershell binary" 336 lsp-pwsh-exe)) 337 338 (let ((url (format lsp-pwsh-github-asset-url "PowerShell" 339 "PowerShellEditorServices" "PowerShellEditorServices.zip")) 340 (temp-file (make-temp-file "ext" nil ".zip"))) 341 (unless (f-exists? lsp-pwsh-log-path) 342 (mkdir lsp-pwsh-log-path 'create-parent)) 343 (unless (and (not update) (f-exists? lsp-pwsh-pses-script)) 344 ;; since we know it's installed, use powershell to download the file 345 ;; (and avoid url.el bugginess or additional libraries) 346 (when (f-exists? lsp-pwsh-dir) (delete-directory lsp-pwsh-dir 'recursive)) 347 (lsp-async-start-process 348 callback 349 error-callback 350 lsp-pwsh-exe "-noprofile" "-noninteractive" "-nologo" 351 "-ex" "bypass" "-command" 352 "Invoke-WebRequest" "-UseBasicParsing" "-uri" url "-outfile" temp-file ";" 353 "Expand-Archive" "-Path" temp-file 354 "-DestinationPath" lsp-pwsh-dir)))) 355 356 (lsp-consistency-check lsp-pwsh) 357 358 (provide 'lsp-pwsh) 359 ;;; lsp-pwsh.el ends here