emacs/code/elpa/lsp-mode-20230823.446/lsp-pwsh.el

362 lines
15 KiB
EmacsLisp

;;; lsp-pwsh.el --- client for PowerShellEditorServices -*- lexical-binding: t; -*-
;; Copyright (C) 2019 Kien Nguyen
;; Author: kien.n.quang at gmail.com
;; Keywords: lsp
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'f)
(require 'dash)
(require 's)
(require 'ht)
(require 'lsp-protocol)
(require 'lsp-mode)
(defgroup lsp-pwsh nil
"LSP support for PowerShell, using the PowerShellEditorServices."
:group 'lsp-mode
:package-version '(lsp-mode . "6.2"))
;; PowerShell vscode flags
(defcustom lsp-pwsh-help-completion "BlockComment"
"Controls the comment-based help completion behavior triggered by typing '##'.
Set the generated help style with 'BlockComment' or 'LineComment'.
Disable the feature with 'Disabled'."
:type
'(choice
(:tag "Disabled" "BlockComment" "LineComment"))
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-script-analysis-enable t
"Enables real-time script analysis from PowerShell Script Analyzer.
Uses the newest installed version of the PSScriptAnalyzer module or the
version bundled with this extension, if it is newer."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-script-analysis-settings-path ""
"Specifies the path to a PowerShell Script Analyzer settings file.
To override the default settings for all projects, enter an absolute path,
or enter a path relative to your workspace."
:type 'string
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-folding-enable t
"Enables syntax based code folding.
When disabled, the default indentation based code folding is used."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-folding-show-last-line t
"Shows the last line of a folded section.
Similar to the default VSCode folding style.
When disabled, the entire folded region is hidden."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-preset "Custom"
"Sets the codeformatting options to follow the given indent style.
Sets in a way that is compatible with PowerShell syntax.
For more information about the brace styles please refer to https://github.com/PoshCode/PowerShellPracticeAndStyle/issues/81."
:type
'(choice
(:tag "Custom" "Allman" "OTBS" "Stroustrup"))
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-open-brace-on-same-line t
"Places open brace on the same line as its associated statement."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-new-line-after-open-brace t
"Adds a newline (line break) after an open brace."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-new-line-after-close-brace t
"Adds a newline (line break) after a closing brace."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-pipeline-indentation-style "NoIndentation"
"Multi-line pipeline style settings."
:type
'(choice
(:tag "IncreaseIndentationForFirstPipeline" "IncreaseIndentationAfterEveryPipeline" "NoIndentation"))
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-whitespace-before-open-brace t
"Adds a space between a keyword and its associated scriptblock expression."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-whitespace-before-open-paren t
"Adds a space between a keyword (if, elseif, while, switch, etc) and its
associated conditional expression."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-whitespace-around-operator t
"Adds spaces before and after an operator ('=', '+', '-', etc.)."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-whitespace-after-separator t
"Adds a space after a separator (',' and ';')."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-whitespace-inside-brace t
"Adds a space after an opening brace ('{') and before a closing brace ('}')."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-whitespace-around-pipe t
"Adds a space before and after the pipeline operator ('|')."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-ignore-one-line-block t
"Does not reformat one-line code blocks, such as \"if (...) {...} else
{...}\"."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-align-property-value-pairs t
"Align assignment statements in a hashtable or a DSC Configuration."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-code-formatting-use-correct-casing nil
"Use correct casing for cmdlets."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-developer-editor-services-log-level "Normal"
"Sets the log level for the PowerShell Editor Services host executable.
Valid values are 'Diagnostic', 'Verbose', 'Normal', 'Warning', and 'Error'"
:type
'(choice
(:tag "Diagnostic" "Verbose" "Normal" "Warning" "Error"))
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-developer-editor-services-wait-for-debugger nil
"Launches the language service with the /waitForDebugger flag to force it to
wait for a .NET debugger to attach before proceeding."
:type 'boolean
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-developer-feature-flags nil
"An array of strings that enable experimental features in the PowerShell
extension."
:type
'(repeat string)
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(lsp-register-custom-settings
'(("powershell.developer.featureFlags" lsp-pwsh-developer-feature-flags)
("powershell.developer.editorServicesWaitForDebugger" lsp-pwsh-developer-editor-services-wait-for-debugger t)
("powershell.codeFormatting.useCorrectCasing" lsp-pwsh-code-formatting-use-correct-casing t)
("powershell.codeFormatting.alignPropertyValuePairs" lsp-pwsh-code-formatting-align-property-value-pairs t)
("powershell.codeFormatting.ignoreOneLineBlock" lsp-pwsh-code-formatting-ignore-one-line-block t)
("powershell.codeFormatting.whitespaceAroundPipe" lsp-pwsh-code-formatting-whitespace-around-pipe t)
("powershell.codeFormatting.whitespaceInsideBrace" lsp-pwsh-code-formatting-whitespace-inside-brace t)
("powershell.codeFormatting.whitespaceAfterSeparator" lsp-pwsh-code-formatting-whitespace-after-separator t)
("powershell.codeFormatting.whitespaceAroundOperator" lsp-pwsh-code-formatting-whitespace-around-operator t)
("powershell.codeFormatting.whitespaceBeforeOpenParen" lsp-pwsh-code-formatting-whitespace-before-open-paren t)
("powershell.codeFormatting.whitespaceBeforeOpenBrace" lsp-pwsh-code-formatting-whitespace-before-open-brace t)
("powershell.codeFormatting.pipelineIndentationStyle" lsp-pwsh-code-formatting-pipeline-indentation-style)
("powershell.codeFormatting.newLineAfterCloseBrace" lsp-pwsh-code-formatting-new-line-after-close-brace t)
("powershell.codeFormatting.newLineAfterOpenBrace" lsp-pwsh-code-formatting-new-line-after-open-brace t)
("powershell.codeFormatting.openBraceOnSameLine" lsp-pwsh-code-formatting-open-brace-on-same-line t)
("powershell.codeFormatting.preset" lsp-pwsh-code-formatting-preset)
("powershell.codeFolding.showLastLine" lsp-pwsh-code-folding-show-last-line t)
("powershell.codeFolding.enable" lsp-pwsh-code-folding-enable t)
("powershell.scriptAnalysis.settingsPath" lsp-pwsh-script-analysis-settings-path)
("powershell.scriptAnalysis.enable" lsp-pwsh-script-analysis-enable t)
("powershell.helpCompletion" lsp-pwsh-help-completion)))
;; lsp-pwsh custom variables
(defcustom lsp-pwsh-ext-path (expand-file-name "pwsh" lsp-server-install-dir)
"The path to powershell vscode extension."
:type 'string
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-exe (or (executable-find "pwsh") (executable-find "powershell"))
"PowerShell executable."
:type 'string
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defcustom lsp-pwsh-dir lsp-pwsh-ext-path
"Path to PowerShellEditorServices without last slash."
:type 'string
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defvar lsp-pwsh-pses-script (expand-file-name "PowerShellEditorServices/Start-EditorServices.ps1"
lsp-pwsh-dir)
"Main script to start PSES.")
(defvar lsp-pwsh-log-path (expand-file-name "logs" lsp-pwsh-ext-path)
"Path to directory where server will write log files.
Must not nil.")
(defvar lsp-pwsh--sess-id (emacs-pid))
(defun lsp-pwsh--command ()
"Return the command to start server."
`(,lsp-pwsh-exe "-NoProfile" "-NonInteractive" "-NoLogo"
,@(if (eq system-type 'windows-nt) '("-ExecutionPolicy" "Bypass"))
"-OutputFormat" "Text"
"-File"
,lsp-pwsh-pses-script
"-HostName" "\"Emacs Host\""
"-HostProfileId" "'Emacs.LSP'"
"-HostVersion" "8.0.1"
"-LogPath" ,(expand-file-name "emacs-powershell.log" lsp-pwsh-log-path)
"-LogLevel" ,lsp-pwsh-developer-editor-services-log-level
"-SessionDetailsPath" ,(expand-file-name (format "PSES-VSCode-%d" lsp-pwsh--sess-id)
lsp-pwsh-log-path)
;; "-AdditionalModules" "@('PowerShellEditorServices.VSCode')"
"-Stdio"
"-BundledModulesPath" ,lsp-pwsh-dir
"-FeatureFlags" "@()"))
(defun lsp-pwsh--extra-init-params ()
"Return form describing parameters for language server.")
(lsp-defun lsp-pwsh--apply-code-action-edits ((&Command :command :arguments?))
"Handle ACTION for PowerShell.ApplyCodeActionEdits."
(-if-let* (((&pwsh:ScriptRegion :start-line-number :end-line-number
:start-column-number :end-column-number :text)
(lsp-seq-first arguments?))
(start-position (lsp-make-position :line (1- start-line-number)
:character (1- start-column-number)))
(end-position (lsp-make-position :line (1- end-line-number)
:character (1- end-column-number)))
(edits `[,(lsp-make-text-edit :range (lsp-make-range :start start-position
:end end-position)
:newText text)]))
(lsp--apply-text-edits edits 'code-action)
(lsp-send-execute-command command arguments?)))
(lsp-defun lsp-pwsh--show-code-action-document ((&Command :arguments?))
"Handle ACTION for PowerShell.ShowCodeActionDocumentation."
(-if-let* ((rule-raw (lsp-seq-first arguments?))
(rule-id (if (s-prefix-p "PS" rule-raw) (substring rule-raw 2) rule-raw)))
(browse-url
(concat "https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/rules/"
rule-id))
(lsp-warn "Cannot show documentation for code action, no ruleName was supplied")))
(defvar lsp-pwsh--major-modes '(powershell-mode))
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection #'lsp-pwsh--command
(lambda ()
(f-exists? lsp-pwsh-pses-script)))
:major-modes lsp-pwsh--major-modes
:server-id 'pwsh-ls
:priority -1
:initialization-options #'lsp-pwsh--extra-init-params
:notification-handlers (ht ("powerShell/executionStatusChanged" #'ignore)
("output" #'ignore))
:action-handlers (ht ("PowerShell.ApplyCodeActionEdits"
#'lsp-pwsh--apply-code-action-edits)
("PowerShell.ShowCodeActionDocumentation"
#'lsp-pwsh--show-code-action-document))
:initialized-fn (lambda (w)
(with-lsp-workspace w
(lsp--set-configuration
(lsp-configuration-section "powershell")))
(let ((caps (lsp--workspace-server-capabilities w)))
(lsp:set-server-capabilities-document-range-formatting-provider? caps t)
(lsp:set-server-capabilities-document-formatting-provider? caps t)))
:download-server-fn #'lsp-pwsh-setup))
(defcustom lsp-pwsh-github-asset-url
"https://github.com/%s/%s/releases/latest/download/%s"
"GitHub latest asset template url."
:type 'string
:group 'lsp-pwsh
:package-version '(lsp-mode . "6.2"))
(defun lsp-pwsh-setup (_client callback error-callback update)
"Downloads PowerShellEditorServices to `lsp-pwsh-dir'.
CALLBACK is called when the download finish successfully otherwise
ERROR-CALLBACK is called.
UPDATE is non-nil if it is already downloaded.
FORCED if specified with prefix argument."
(unless (and lsp-pwsh-exe (file-executable-p lsp-pwsh-exe))
(user-error "Use `lsp-pwsh-exe' with the value of `%s' is not a valid powershell binary"
lsp-pwsh-exe))
(let ((url (format lsp-pwsh-github-asset-url "PowerShell"
"PowerShellEditorServices" "PowerShellEditorServices.zip"))
(temp-file (make-temp-file "ext" nil ".zip")))
(unless (f-exists? lsp-pwsh-log-path)
(mkdir lsp-pwsh-log-path 'create-parent))
(unless (and (not update) (f-exists? lsp-pwsh-pses-script))
;; since we know it's installed, use powershell to download the file
;; (and avoid url.el bugginess or additional libraries)
(when (f-exists? lsp-pwsh-dir) (delete-directory lsp-pwsh-dir 'recursive))
(lsp-async-start-process
callback
error-callback
lsp-pwsh-exe "-noprofile" "-noninteractive" "-nologo"
"-ex" "bypass" "-command"
"Invoke-WebRequest" "-UseBasicParsing" "-uri" url "-outfile" temp-file ";"
"Expand-Archive" "-Path" temp-file
"-DestinationPath" lsp-pwsh-dir))))
(lsp-consistency-check lsp-pwsh)
(provide 'lsp-pwsh)
;;; lsp-pwsh.el ends here