707 lines
30 KiB
EmacsLisp
707 lines
30 KiB
EmacsLisp
;;; powershell-mode.el --- Mode for editing Powershell scripts
|
|
|
|
;; Copyright (C) 2009, 2010 Frédéric Perrin
|
|
;; Copyright (C) 2012 Richard Bielawski rbielaws-at-i1-dot-net
|
|
;; http://www.emacswiki.org/emacs/Rick_Bielawski
|
|
|
|
;; Author: Frédéric Perrin <frederic (dot) perrin (arobas) resel (dot) fr>
|
|
;; Keywords: Powershell, Monad, MSH
|
|
|
|
;; This file 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.
|
|
|
|
;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Frédéric Perrin Comments:
|
|
;; This was written from scratch, without using Vivek Sharma's code:
|
|
;; it had issues I wanted to correct, but unfortunately there were no
|
|
;; licence indication, and Vivek didn't answered my mails.
|
|
;;
|
|
;;; Rick Bielawski Comments 2012/09/28:
|
|
;; On March 31, 2012 Frédéric gave me permission to take over support.
|
|
;; I've added support for multi-line comments and here-strings as well
|
|
;; as enhancement/features such as:
|
|
;; Functions to quote, unquote and escape a selection, and one to wrap
|
|
;; a selection in $(). Meanwhile I hope I didn't break anything.
|
|
;;
|
|
;;; Updates
|
|
;; 2012/10/01 Fixed several bugs in highlighting variables and types.
|
|
;; Renamed some variables to be more descriptive.
|
|
;; 2012/10/02 Enhanced PowerShell-mode indenting & syntax table.
|
|
;; Fixed dangling parens and re-indented the elisp itself.
|
|
;; 2012/10/05 Added eldoc support. Fixed bug where indent could loop.
|
|
;; See comment below on how to generate powershell-eldoc.el
|
|
|
|
;; Variables you may want to customize.
|
|
(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)))
|
|
|
|
;; Rick added significant complexity to Frédéric's original version
|
|
(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)
|
|
;; on a continuation line (i.e. prior line ends with backtick
|
|
;; or pipe), indent relative to the continued line.
|
|
(progn
|
|
(while (and (not (bobp))(powershell-continuation-line-p))
|
|
(forward-line -1))
|
|
(+ (current-indentation) powershell-continuation-indent))
|
|
;; otherwise, indent relative to the block's opening char ([{
|
|
(let ((closing-paren (looking-at "\\s-*\\s)"))
|
|
new-indent
|
|
block-open-line)
|
|
(condition-case nil
|
|
(progn
|
|
(backward-up-list) ;when at top level, throw to no-indent
|
|
(setq block-open-line (line-number-at-pos))
|
|
;; We're in a block, calculate/return indent amount.
|
|
(if (not (looking-at "\\s(\\s-*\\(#.*\\)?$"))
|
|
;; code (not comments) follow the block open so
|
|
;; vertically align the block with the code.
|
|
(if closing-paren
|
|
;; closing indent = open
|
|
(setq new-indent (current-column))
|
|
;; block indent = first line of code
|
|
(forward-char)
|
|
(skip-syntax-forward " ")
|
|
(setq new-indent (current-column)))
|
|
;; otherwise block open is at eol so indent is relative to
|
|
;; bol or another block open on the same line.
|
|
(if closing-paren ; this sets the default indent
|
|
(setq new-indent (current-indentation))
|
|
(setq new-indent (+ powershell-indent (current-indentation))))
|
|
;; now see if the block is nested on the same line
|
|
(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 ;; most likely, we are at the top-level
|
|
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 "|"))))
|
|
|
|
|
|
;; Taken from About_Keywords
|
|
(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")
|
|
|
|
;; Taken from About_Comparison_Operators and some questionable sources :-)
|
|
(defvar powershell-operators
|
|
(concat "\\_<"
|
|
(regexp-opt
|
|
'("-eq" "-ne" "-gt" "-ge" "-lt" "-le"
|
|
"-ceq" "-cne" "-cgt" "-cge" "-clt" "-cle" ;; case sensitive versions
|
|
"-ieq" "-ine" "-igt" "-ige" "-ilt" "-ile" ;; explicitly case insensitive
|
|
"-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"
|
|
;; Questionable --> specific to certain contexts
|
|
"-casesensitive" "-wildcard" "-regex" "-exact" ;specific to case
|
|
"-begin" "-process" "-end" ;specific to scriptblock
|
|
) 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
|
|
;; There are 2 syntaxes detected: ${[scope:]name} and $[scope:]name
|
|
;; Match 0 is the entire variable name.
|
|
;; Match 1 is scope when the former syntax is found.
|
|
;; Match 2 is scope when the latter syntax is found.
|
|
(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
|
|
;; Syntax detected is [scope:]verb-noun
|
|
;; Match 0 is the entire name.
|
|
;; Match 1 is the scope if any.
|
|
;; Match 2 is the function name (which must exist)
|
|
(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
|
|
;; Syntax is \[name[.name]\] (where the escaped []s are literal)
|
|
;; Only Match 0 is returned.
|
|
"\\[\\(?:[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
|
|
;; Only Match 0 is returned.
|
|
"\\_<-[a-zA-Z][a-zA-Z0-9]*\\_>"
|
|
"Identifies function parameter names of the form -xxxx")
|
|
|
|
;; Taken from Get-Variable on a fresh shell, merged with man
|
|
;; about_automatic_variables
|
|
(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
|
|
`( ;; Type annotations
|
|
(,powershell-object-types-regexp . font-lock-type-face)
|
|
;; syntaxic keywords
|
|
(,powershell-keywords . font-lock-keyword-face)
|
|
;; operators
|
|
(,powershell-operators . font-lock-builtin-face)
|
|
;; the REQUIRES mark
|
|
("^#\\(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
|
|
`( ;; Built-in variables
|
|
(,(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
|
|
`( ;; user variables
|
|
(,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))
|
|
;; function argument names
|
|
(,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))
|
|
;; function names
|
|
(,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"
|
|
;; I use font-lock-syntactic-keywords to set some properties and I
|
|
;; don't want them ignored.
|
|
(set (make-local-variable 'parse-sexp-lookup-properties) t)
|
|
;; This is where all the font-lock stuff actually gets set up. Once
|
|
;; font-lock-defaults has its value, setting font-lock-mode true should
|
|
;; cause all your syntax highlighting dreams to come true.
|
|
(setq font-lock-defaults
|
|
;; The first value is all the keyword expressions.
|
|
'((powershell-font-lock-keywords-1
|
|
powershell-font-lock-keywords-2
|
|
powershell-font-lock-keywords-3)
|
|
;; keywords-only means no strings or comments get fontified
|
|
nil
|
|
;; case-fold (t ignores case)
|
|
t
|
|
;; syntax-alist nothing special here
|
|
nil
|
|
;; syntax-begin - no function defined to move outside syntactic block
|
|
nil
|
|
;; font-lock-syntactic-keywords
|
|
;; takes (matcher (match syntax override lexmatch) ...)...
|
|
(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 "\r" 'powershell-indent-line)
|
|
(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 $()")))
|
|
|
|
|
|
;;; Eldoc support
|
|
|
|
(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))))))
|
|
;;; Note: You can create quite a bit of help with these commands:
|
|
;;
|
|
;; function Get-Signature ($Cmd) {
|
|
;; if ($Cmd -is [Management.Automation.PSMethod]) {
|
|
;; $List = @($Cmd)}
|
|
;; elseif ($Cmd -isnot [string]) {
|
|
;; throw ("Get-Signature {<method>|<command>}`n" +
|
|
;; "'$Cmd' is not a method or command")}
|
|
;; else {$List = @(Get-Command $Cmd -ErrorAction SilentlyContinue)}
|
|
;; if (!$List[0] ) {
|
|
;; throw "Command '$Cmd' not found"}
|
|
;; foreach ($O in $List) {
|
|
;; switch -regex ($O.GetType().Name) {
|
|
;; 'AliasInfo' {
|
|
;; Get-Signature ($O.Definition)}
|
|
;; '(Cmdlet|ExternalScript)Info' {
|
|
;; $O.Definition} # not sure what to do with ExternalScript
|
|
;; 'F(unction|ilter)Info'{
|
|
;; if ($O.Definition -match '^param *\(') {
|
|
;; $t = [Management.Automation.PSParser]::tokenize($O.Definition,
|
|
;; [ref]$null)
|
|
;; $c = 1;$i = 1
|
|
;; while($c -and $i++ -lt $t.count) {
|
|
;; switch ($t[$i].Type.ToString()) {
|
|
;; GroupStart {$c++}
|
|
;; GroupEnd {$c--}}}
|
|
;; $O.Definition.substring(0,$t[$i].start + 1)} #needs parsing
|
|
;; else {$O.Name}}
|
|
;; 'PSMethod' {
|
|
;; foreach ($t in @($O.OverloadDefinitions)) {
|
|
;; while (($b=$t.IndexOf('`1[[')) -ge 0) {
|
|
;; $t=$t.remove($b,$t.IndexOf(']]')-$b+2)}
|
|
;; $t}}}}}
|
|
;; get-command|
|
|
;; ?{$_.CommandType -ne 'Alias' -and $_.Name -notlike '*:'}|
|
|
;; %{$_.Name}|
|
|
;; sort|
|
|
;; %{("(set (intern ""$($_.Replace('\','\\'))"" powershell-eldoc-obarray)" +
|
|
;; " ""$(Get-Signature $_|%{$_.Replace('\','\\').Replace('"','\"')})"")"
|
|
;; ).Replace("`r`n"")",""")")} > .\powershell-eldoc.el
|
|
|
|
|
|
(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)
|
|
;; imenu doc says these are buffer-local by default
|
|
(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)
|
|
;; A better command would be something like "powershell.exe -NoLogo
|
|
;; -NonInteractive -Command & (buffer-file-name)". But it will just
|
|
;; sit there waiting... The following will only work when .ps1 files
|
|
;; are associated with powershell.exe. And if they don't contain spaces.
|
|
(defvar powershell-compile-command
|
|
'(buffer-file-name)
|
|
"Default command used to invoke a powershell script")
|
|
|
|
;; The column number will be off whenever tabs are used. Since this is
|
|
;; the default in this mode, we will not capture the column number.
|
|
(setq compilation-error-regexp-alist
|
|
(cons '("At \\(.*\\):\\([0-9]+\\) char:\\([0-9]+\\)" 1 2)
|
|
compilation-error-regexp-alist))
|
|
|
|
|
|
;; the hook is automatically run by derived-mode
|
|
(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)
|
|
|
|
;;; end of powershell-mode.el
|