Download
(defgroup powershell nil
"Customization of PowerShell mode."
:link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
:group 'languages )
(defcustom powershell-indent 4
"Amount of horizontal space to indent after, for instance, an
opening brace"
:type 'integer
:group 'powershell)
(defcustom powershell-continuation-indent 2
"Amount of horizontal space to indent a continuation line"
:type 'integer
:group 'powershell)
(defcustom powershell-continued-regexp ".*\\(|[\\t ]*\\|`\\)$"
"Regexp matching a continued line (ending either with an
explicit backtick, or with a pipe)."
:type 'integer
:group 'powershell)
(defun powershell-continuation-line-p ()
"Returns t is the current line is a continuation line (i.e. the
previous line is a continued line, ending with a backtick or a pipe"
(interactive)
(save-excursion
(forward-line -1)
(looking-at powershell-continued-regexp)))
(defun powershell-indent-line-amount ()
"Returns the column to which the current line ought to be indented."
(interactive)
(save-excursion
(beginning-of-line)
(if (powershell-continuation-line-p)
(progn
(while (and (not (bobp))(powershell-continuation-line-p))
(forward-line -1))
(+ (current-indentation) powershell-continuation-indent))
(let ((closing-paren (looking-at "\\s-*\\s)"))
new-indent
block-open-line)
(condition-case nil
(progn
(backward-up-list)
(setq block-open-line (line-number-at-pos))
(if (not (looking-at "\\s(\\s-*\\(#.*\\)?$"))
(if closing-paren
(setq new-indent (current-column))
(forward-char)
(skip-syntax-forward " ")
(setq new-indent (current-column)))
(if closing-paren
(setq new-indent (current-indentation))
(setq new-indent (+ powershell-indent (current-indentation))))
(when (condition-case nil
(progn
(backward-up-list)
(= block-open-line (line-number-at-pos)))
(scan-error nil))
(forward-char)
(skip-syntax-forward " ")
(if closing-paren
(setq new-indent (current-column))
(setq new-indent (+ powershell-indent (current-column))))))
new-indent)
(scan-error
0))))))
(defun powershell-indent-line ()
"Indent the current line of powershell mode, leaving the point
in place if it is inside the meat of the line"
(interactive)
(let ((savep (> (current-column) (current-indentation)))
(amount (powershell-indent-line-amount)))
(if savep
(save-excursion (indent-line-to amount))
(indent-line-to amount))))
(defun powershell-quote-selection (beg end)
"Quotes the selection with single quotes and doubles embedded single quotes"
(interactive `(,(region-beginning) ,(region-end)))
(if (not mark-active)
(error "Command requires a marked region"))
(goto-char beg)
(while (re-search-forward "'" end t)
(replace-match "''")(setq end (1+ end)))
(goto-char beg)
(insert "'")
(setq end (1+ end))
(goto-char end)
(insert "'"))
(defun powershell-unquote-selection (beg end)
"Unquotes the selected text removing doubles as we go"
(interactive `(,(region-beginning) ,(region-end)))
(if (not mark-active)
(error "Command requires a marked region"))
(goto-char beg)
(cond ((looking-at "'")
(goto-char end)
(when (looking-back "'")
(delete-char -1)
(setq end (1- end))
(goto-char beg)
(delete-char 1)
(setq end (1- end))
(while (search-forward "'" end t)
(delete-char -1)
(forward-char)
(setq end (1- end)))))
((looking-at "\"")
(goto-char end)
(when (looking-back "\"")
(delete-char -1)
(setq end (1- end))
(goto-char beg)
(delete-char 1)
(setq end (1- end))
(while (search-forward "\"" end t)
(delete-char -1)
(forward-char)
(setq end (1- end)))
(while (search-forward "`" end t)
(delete-char -1)
(forward-char)
(setq end (1- end)))))
(t (error "Must select quoted text exactly."))))
(defun powershell-escape-selection (beg end)
"Escapes variables in the selection and extends existing escapes."
(interactive `(,(region-beginning) ,(region-end)))
(if (not mark-active)
(error "Command requires a marked region"))
(goto-char beg)
(while (re-search-forward "`" end t)
(replace-match "```")(setq end (+ end 2)))
(goto-char beg)
(while (re-search-forward "\\(?:\\=\\|[^`]\\)[$]" end t)
(goto-char (car (cdr (match-data))))
(backward-char)
(insert "`")
(forward-char)
(setq end (1+ end))))
(defun powershell-doublequote-selection (beg end)
"Quotes the selection with double quotes, doubles embedded quotes"
(interactive `(,(region-beginning) ,(region-end)))
(if (not mark-active)
(error "Command requires a marked region"))
(goto-char beg)
(while (re-search-forward "\"" end t)
(replace-match "\"\"")(setq end (1+ end)))
(goto-char beg)
(while (re-search-forward "`'" end t)
(replace-match "```")(setq end (+ 2 end)))
(goto-char beg)
(insert "\"")
(setq end (1+ end))
(goto-char end)
(insert "\""))
(defun powershell-dollarparen-selection (beg end)
"Wraps the selected text with $() leaving point after closing paren."
(interactive `(,(region-beginning) ,(region-end)))
(if (not mark-active)
(error "Command requires a marked region"))
(save-excursion
(goto-char end)
(insert ")")
(goto-char beg)
(insert "$("))
(forward-char))
(defun powershell-regexp-to-regex (beg end)
"Turns the selected string (assumed to be regexp-opt output) into a regex"
(interactive `(,(region-beginning) ,(region-end)))
(if (not mark-active)
(error "Command requires a marked region"))
(save-restriction
(narrow-to-region beg end)
(goto-char (point-min))
(while (re-search-forward "\\\\(" nil t)
(replace-match "("))
(goto-char (point-min))
(while (re-search-forward "\\\\)" nil t)
(replace-match ")"))
(goto-char (point-min))
(while (re-search-forward "\\\\|" nil t)
(replace-match "|"))))
(defvar powershell-keywords
(concat "\\_<"
(regexp-opt
'("begin" "break" "catch"
"continue" "data" "do"
"default" "dynamicparam" "else"
"elseif" "end" "exit"
"filter" "finally" "for"
"foreach" "from" "function"
"if" "in" "param"
"process" "return" "switch"
"throw" "trap" "try"
"until" "where" "while" ) t)
"\\_>")
"Powershell keywords")
(defvar powershell-operators
(concat "\\_<"
(regexp-opt
'("-eq" "-ne" "-gt" "-ge" "-lt" "-le"
"-ceq" "-cne" "-cgt" "-cge" "-clt" "-cle"
"-ieq" "-ine" "-igt" "-ige" "-ilt" "-ile"
"-band" "-bor" "-bxor"
"-and" "-or" "-xor"
"-like" "-notlike" "-clike" "-cnotlike" "-ilike" "-inotlike"
"-match" "-notmatch" "-cmatch" "-cnotmatch" "-imatch" "-inotmatch"
"-contains" "-notcontains" "-ccontains" "-cnotcontains" "-icontains" "-inotcontains"
"-replace" "-creplace" "-ireplace"
"-is" "-as" "-f"
"-casesensitive" "-wildcard" "-regex" "-exact"
"-begin" "-process" "-end"
) t)
"\\_>")
"Powershell operators")
(defvar powershell-scope-names
'("global" "local" "private" "script" )
"Names of scopes in Powershell mode.")
(defvar powershell-variable-drive-names
(append '("env" "function" "variable" "alias" ) powershell-scope-names)
"Names of scopes in Powershell mode.")
(defconst powershell-variables-regexp
(concat
"\\_<$\\(?:{\\(?:" (regexp-opt powershell-variable-drive-names t) ":\\)?[^}]+}\\|"
"\\(?:" (regexp-opt powershell-variable-drive-names t) ":\\)?[a-zA-Z0-9_]+\\_>\\)")
"Identifies legal powershell variable names")
(defconst powershell-function-names-regex
(concat
"\\_<\\(?:" (regexp-opt powershell-scope-names t) ":\\)?"
"\\([A-Z][a-zA-Z0-9]*-[A-Z0-9][a-zA-Z0-9]*\\)\\_>")
"Identifies legal function & filter names")
(defconst powershell-object-types-regexp
"\\[\\(?:[a-zA-Z_][a-zA-Z0-9]*\\)\\(?:\\.[a-zA-Z_][a-zA-Z0-9]*\\)*\\]"
"Identifies object type references. I.E. [object.data.type] syntax")
(defconst powershell-function-switch-names-regexp
"\\_<-[a-zA-Z][a-zA-Z0-9]*\\_>"
"Identifies function parameter names of the form -xxxx")
(defvar powershell-builtin-variables-regexp
(regexp-opt
'("$" "?"
"^" "_"
"args" "ConsoleFileName"
"Error" "Event"
"EventSubscriber" "ExecutionContext"
"false" "Foreach"
"HOME" "Host"
"input" "LASTEXITCODE"
"Matches" "MyInvocation"
"NestedPromptLevel" "null"
"PID" "PROFILE"
"PSBoundParameters" "PSCmdlet"
"PSCulture" "PSDebugContext"
"PSHOME" "PSScriptRoot"
"PSUICulture" "PSVersionTable"
"PWD" "ReportErrorShowExceptionClass"
"ReportErrorShowInnerException" "ReportErrorShowSource"
"ReportErrorShowStackTrace" "Sender"
"ShellId" "SourceArgs"
"SourceEventArgs" "StackTrace"
"this" "true" ) t)
"Names of the built-in Powershell variables. They are hilighted
differently from the other variables.")
(defvar powershell-config-variables-regexp
(regexp-opt
'("ConfirmPreference" "DebugPreference"
"ErrorActionPreference" "ErrorView"
"FormatEnumerationLimit" "LogCommandHealthEvent"
"LogCommandLifecycleEvent" "LogEngineHealthEvent"
"LogEngineLifecycleEvent" "LogProviderHealthEvent"
"LogProviderLifecycleEvent" "MaximumAliasCount"
"MaximumDriveCount" "MaximumErrorCount"
"MaximumFunctionCount" "MaximumHistoryCount"
"MaximumVariableCount" "OFS"
"OutputEncoding" "ProgressPreference"
"PSEmailServer" "PSSessionApplicationName"
"PSSessionConfigurationName" "PSSessionOption"
"VerbosePreference" "WarningPreference"
"WhatIfPreference" ) t)
"Names of variables that configure powershell features.")
(defun powershell-find-syntactic-comments (limit)
"Finds PowerShell comment begin and comment end characters.
Returns match 1 and match 2 for <# #> comment sequences respectively.
Returns match 3 and optionally match 4 for #/eol comments.
Match 4 is returned only if eol is found before LIMIT"
(when (search-forward "#" limit t)
(cond
((looking-back "<#")
(set-match-data (list (match-beginning 0) (1+ (match-beginning 0))
(match-beginning 0) (1+ (match-beginning 0)))))
((looking-at ">")
(set-match-data (list (match-beginning 0) (match-end 0)
nil nil
(match-beginning 0) (match-end 0)))
(forward-char))
(t
(let ((start (point)))
(if (search-forward "\n" limit t)
(set-match-data (list (1- start) (match-end 0)
nil nil nil nil
(1- start) start
(match-beginning 0) (match-end 0)))
(set-match-data (list start (match-end 0)
nil nil nil nil
(1- start) start))))))
t))
(defun powershell-find-syntactic-quotes (limit)
"Finds PowerShell hear string begin and end sequences.
Returns match 1 and match 2 for @' '@ sequences respectively.
Returns match 3 and match 4 for @\" \"@ sequences respectively."
(when (search-forward "@" limit t)
(cond
((looking-at "'$")
(set-match-data (list (match-beginning 0) (1+ (match-beginning 0))
(match-beginning 0) (1+ (match-beginning 0))))
(forward-char))
((looking-back "^'@")
(set-match-data (list (1- (match-end 0)) (match-end 0)
nil nil
(1- (match-end 0)) (match-end 0))))
((looking-at "\"$")
(set-match-data (list (match-beginning 0) (1+ (match-beginning 0))
nil nil
nil nil
(match-beginning 0) (1+ (match-beginning 0))))
(forward-char))
((looking-back "^\"@")
(set-match-data (list (1- (match-end 0)) (match-end 0)
nil nil
nil nil
nil nil
(1- (match-end 0)) (match-end 0)))))
t))
(defvar powershell-font-lock-syntactic-keywords
`((powershell-find-syntactic-comments (1 "!" t t) (2 "!" t t)
(3 "<" t t) (4 ">" t t))
(powershell-find-syntactic-quotes (1 "|" t t) (2 "|" t t)
(3 "|" t t) (4 "|" t t)))
"A list of regexp's or functions. Used to add syntax-table properties to
characters that can't be set by the syntax-table alone.")
(defvar powershell-font-lock-keywords-1
`(
(,powershell-object-types-regexp . font-lock-type-face)
(,powershell-keywords . font-lock-keyword-face)
(,powershell-operators . font-lock-builtin-face)
("^#\\(REQUIRES\\)" 1 font-lock-warning-face t))
"Keywords for the first level of font-locking in Powershell mode.")
(defvar powershell-font-lock-keywords-2
(append
powershell-font-lock-keywords-1
`(
(,(concat "\\$\\(" powershell-builtin-variables-regexp "\\)\\>")
0 font-lock-builtin-face t)
(,(concat "\\$\\(" powershell-config-variables-regexp "\\)\\>")
0 font-lock-builtin-face t)))
"Keywords for the second level of font-locking in Powershell mode.")
(defvar powershell-font-lock-keywords-3
(append
powershell-font-lock-keywords-2
`(
(,powershell-variables-regexp
(0 font-lock-variable-name-face)
(1 (cons font-lock-type-face '(underline)) t t)
(2 (cons font-lock-type-face '(underline)) t t))
(,powershell-function-switch-names-regexp
(0 font-lock-reference-face)
(1 (cons font-lock-type-face '(underline)) t t)
(2 (cons font-lock-type-face '(underline)) t t))
(,powershell-function-names-regex
(0 font-lock-function-name-face)
(1 (cons font-lock-type-face '(underline)) t t))))
"Keywords for the maximum level of font-locking in Powershell mode.")
(defun powershell-setup-font-lock ()
"Sets up the buffer local value for font-lock-defaults"
(set (make-local-variable 'parse-sexp-lookup-properties) t)
(setq font-lock-defaults
'((powershell-font-lock-keywords-1
powershell-font-lock-keywords-2
powershell-font-lock-keywords-3)
nil
t
nil
nil
(font-lock-syntactic-keywords . powershell-font-lock-syntactic-keywords))))
(defvar powershell-mode-syntax-table
(let ((powershell-mode-syntax-table (make-syntax-table)))
(modify-syntax-entry ?$ "_" powershell-mode-syntax-table)
(modify-syntax-entry ?: "_" powershell-mode-syntax-table)
(modify-syntax-entry ?- "_" powershell-mode-syntax-table)
(modify-syntax-entry ?^ "_" powershell-mode-syntax-table)
(modify-syntax-entry ?\\ "_" powershell-mode-syntax-table)
(modify-syntax-entry ?{ "(}" powershell-mode-syntax-table)
(modify-syntax-entry ?} "){" powershell-mode-syntax-table)
(modify-syntax-entry ?[ "(]" powershell-mode-syntax-table)
(modify-syntax-entry ?] ")[" powershell-mode-syntax-table)
(modify-syntax-entry ?( "()" powershell-mode-syntax-table)
(modify-syntax-entry ?) ")(" powershell-mode-syntax-table)
(modify-syntax-entry ?` "\\" powershell-mode-syntax-table)
(modify-syntax-entry ?_ "w" powershell-mode-syntax-table)
(modify-syntax-entry ?= "." powershell-mode-syntax-table)
(modify-syntax-entry ?| "." powershell-mode-syntax-table)
(modify-syntax-entry ?+ "." powershell-mode-syntax-table)
(modify-syntax-entry ?* "." powershell-mode-syntax-table)
(modify-syntax-entry ?/ "." powershell-mode-syntax-table)
(modify-syntax-entry ?' "\"" powershell-mode-syntax-table)
(modify-syntax-entry ?# "<" powershell-mode-syntax-table)
powershell-mode-syntax-table)
"Syntax for PowerShell major mode")
(defvar powershell-mode-map
(let ((powershell-mode-map (make-keymap)))
(define-key powershell-mode-map "\t" 'powershell-indent-line)
(define-key powershell-mode-map (kbd "M-\"") 'powershell-doublequote-selection)
(define-key powershell-mode-map (kbd "M-'") 'powershell-quote-selection)
(define-key powershell-mode-map (kbd "C-'") 'powershell-unquote-selection)
(define-key powershell-mode-map (kbd "C-\"") 'powershell-unquote-selection)
(define-key powershell-mode-map (kbd "M-`") 'powershell-escape-selection)
(define-key powershell-mode-map (kbd "C-$") 'powershell-dollarparen-selection)
powershell-mode-map)
"Keymap for PS major mode")
(defun powershell-setup-menu ()
"Adds a menu of PowerShell specific functions to the menu bar."
(define-key (current-local-map) [menu-bar powershell-menu]
(cons "PowerShell" (make-sparse-keymap "PowerShell")))
(define-key (current-local-map) [menu-bar powershell-menu doublequote]
'(menu-item "DoubleQuote Selection" powershell-doublequote-selection
:key-sequence(kbd "M-\"")
:help "DoubleQuotes the selection escaping embedded double quotes"))
(define-key (current-local-map) [menu-bar powershell-menu quote]
'(menu-item "SingleQuote Selection" powershell-quote-selection
:key-sequence (kbd "M-'")
:help "SingleQuotes the selection escaping embedded single quotes"))
(define-key (current-local-map) [menu-bar powershell-menu unquote]
'(menu-item "UnQuote Selection" powershell-unquote-selection
:key-sequence (kbd "C-'")
:help "Un-Quotes the selection un-escaping any escaped quotes"))
(define-key (current-local-map) [menu-bar powershell-menu escape]
'(menu-item "Escape Selection" powershell-escape-selection
:key-sequence (kbd "M-`")
:help "Escapes variables in the selection and extends existing escapes."))
(define-key (current-local-map) [menu-bar powershell-menu dollarparen]
'(menu-item "DollarParen Selection" powershell-dollarparen-selection
:key-sequence (kbd "C-$")
:help "Wraps the selection in $()")))
(eval-when-compile (require 'thingatpt))
(defcustom powershell-eldoc-def-files nil
"List of files containing function help strings used by `eldoc-mode'.
These are the strings eldoc-mode displays as help for functions near point.
The format of the file must be exactly as follows or who knows what happens.
(set (intern \"<fcn-name1>\" powershell-eldoc-obarray) \"<helper string1>\")
(set (intern \"<fcn-name2>\" powershell-eldoc-obarray) \"<helper string2>\")
...
Where <fcn-name> is the name of the function to which <helper string> applies.
<helper-string> is the string to display when point is near <fcn-name>."
:type '(repeat string)
:group 'powershell)
(defvar powershell-eldoc-obarray ()
"Array into which powershell-eldoc-def-files entries are added for use by eldoc.")
(defun powershell-eldoc-function ()
"Returns a documentation string appropriate for the current context or nil"
(let ((word (thing-at-point 'symbol)))
(if word
(eval (intern-soft word powershell-eldoc-obarray)))))
(defun powershell-setup-eldoc ()
"Loads the function documentation for use with eldoc."
(when (not (null powershell-eldoc-def-files))
(set (make-local-variable 'eldoc-documentation-function)
'powershell-eldoc-function)
(unless (vectorp powershell-eldoc-obarray)
(setq powershell-eldoc-obarray (make-vector 41 0))
(condition-case var (mapc 'load powershell-eldoc-def-files)
(error (message "*** powershell-setup-eldoc ERROR *** %s" var))))))
(defvar powershell-imenu-expression
`(("Functions" ,(concat "function " powershell-function-names-regex) 2)
("Filters" ,(concat "filter " powershell-function-names-regex) 2)
("Top variables"
, (concat "^\\(" powershell-object-types-regexp "\\)?\\("
powershell-variables-regexp "\\)\\s-*=")
2))
"List of regexps matching important expressions, for speebar & imenu.")
(defun powershell-setup-imenu ()
"Installs powershell-imenu-expression."
(when (require 'imenu nil t)
(setq imenu-generic-expression powershell-imenu-expression)
(setq imenu-case-fold-search nil)
(imenu-add-menubar-index)
(when (require 'which-func nil t)
(which-function-mode t))))
(eval-when-compile (require 'speedbar))
(if (require 'speedbar nil t)
(speedbar-add-supported-extension ".ps1?"))
(require 'compile nil t)
(defvar powershell-compile-command
'(buffer-file-name)
"Default command used to invoke a powershell script")
(setq compilation-error-regexp-alist
(cons '("At \\(.*\\):\\([0-9]+\\) char:\\([0-9]+\\)" 1 2)
compilation-error-regexp-alist))
(defvar powershell-mode-hook '(imenu-add-menubar-index)
"Hook run after the initialization of Powershell mode.")
(defun powershell-mode ()
"Major mode for editing PowerShell scripts."
(interactive)
(kill-all-local-variables)
(setq major-mode 'powershell-mode)
(setq mode-name "PS")
(set-syntax-table powershell-mode-syntax-table)
(use-local-map powershell-mode-map)
(powershell-setup-font-lock)
(set (make-local-variable 'indent-line-function) 'powershell-indent-line)
(set (make-local-variable 'compile-command) powershell-compile-command)
(set (make-local-variable 'comment-start) "#")
(set (make-local-variable 'comment-start-skip) "#+\\s*")
(set (make-local-variable 'parse-sexp-ignore-comments) t)
(powershell-setup-imenu)
(powershell-setup-menu)
(powershell-setup-eldoc)
(run-hooks 'powershell-mode-hook))
(provide 'powershell-mode)