Some local changes

This commit is contained in:
John Doty 2013-07-08 15:49:44 -07:00
parent 85d3c38b00
commit 2a4c6969ce
3 changed files with 605 additions and 129 deletions

View file

@ -1,8 +1,10 @@
;;; powershell-mode.el --- Mode for editing Powershell scripts
;; Copyright (C) 2009, 2010 Frédéric Perrin
;; 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>
;; 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
@ -18,29 +20,48 @@
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Comment:
;; This is still WIP.
;;
;;; 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.
;;
;; This is still pretty basic: there is indentation, syntax
;; hilighting, speedbar/imenu support. The indentation is pretty naïve
;; but robust, and sufficient for my current needs.
;;; 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
(setq debug-on-error t)
;; 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 )
(defvar powershell-indent 4
(defcustom powershell-indent 4
"Amount of horizontal space to indent after, for instance, an
opening brace")
opening brace"
:type 'integer
:group 'powershell)
(defvar powershell-continuation-indent 2
"Amount of horizontal space to indent a continuation line")
(defcustom powershell-continuation-indent 2
"Amount of horizontal space to indent a continuation line"
:type 'integer
:group 'powershell)
(defvar powershell-continued-regexp ".*\\(|[\\t ]*\\|`\\)$"
(defcustom powershell-continued-regexp ".*\\(|[\\t ]*\\|`\\)$"
"Regexp matching a continued line (ending either with an
explicit backtick, or with a pipe).")
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
@ -50,103 +71,375 @@ previous line is a continued line, ending with a backtick or a pipe"
(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)
(beginning-of-line)
(let ((closing-paren (looking-at "[\t ]*[])}]")))
;; a very simple indentation method: if on a continuation line (i.e. the
;; previous line ends with a trailing backtick or pipe), we indent relative
;; to the continued line; otherwise, we indent relative to the ([{ that
;; opened the current block.
(save-excursion
(beginning-of-line)
(if (powershell-continuation-line-p)
(progn
(while (powershell-continuation-line-p)
(forward-line -1))
(+ (current-indentation) powershell-continuation-indent))
(condition-case nil
(progn
(backward-up-list)
;; indentation relative to the opening paren: if there is text (no
;; comment) after the opening paren, vertically align the block
;; with this text; if we were looking at the closing paren, reset
;; the indentation; otherwise, indent the block by powershell-indent.
(cond ((not (looking-at ".[\t ]*\\(#.*\\)?$"))
(forward-char)
(skip-chars-forward " \t")
(current-column))
(closing-paren
(current-indentation))
(t
(+ (current-indentation) powershell-indent))))
(scan-error ;; most likely, we are at the top-level
0)))))
;; 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 (save-excursion (powershell-indent-line-amount))))
(amount (powershell-indent-line-amount)))
(if savep
(save-excursion (indent-line-to amount))
(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 "'"))
;; Taken from <http://www.manning.com/payette/AppCexcerpt.pdf> which seems the
;; closest to a grammar definition for powershell. It is not complete, and
;; contains some inaccuracies (e.g. it says that variables match \$[:alnum:]+,
;; so $_ is not a variable it seems...)
(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
(regexp-opt '("begin" "break" "catch" "continue" "data" "do" "dynamicparam"
"else" "elseif" "end" "exit" "filter" "finally" "for" "foreach"
"from" "function" "if" "in" "param" "process" "return"
"switch" "throw" "trap" "try" "until" "while"))
(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
(regexp-opt '("and" "as" "band" "bnot" "bor" "bxor" "casesensitive"
"ccontains" "ceq" "cge" "cgt" "cle" "clike" "clt" "cmatch"
"cne" "cnotcontains" "cnotlike" "cnotmatch" "contains"
"creplace" "eq" "exact" "f" "file" "ge" "gt" "icontains"
"ieq" "ige" "igt" "ile" "ilike" "ilt" "imatch" "ine"
"inotcontains" "inotlike" "inotmatch" "ireplace" "is"
"isnot" "le" "like" "lt" "match" "ne" "not" "notcontains"
"notlike" "notmatch" "or" "replace" "wildcard"))
(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
(regexp-opt
'("env" "function" "global" "local" "private" "script" "variable"))
'("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
(defvar powershell-builtin-variables-regexp
(regexp-opt
'("^" "_" "$" "?" "Args" "ConfirmPreference" "ConsoleFileName"
"DebugPreference" "Error" "ErrorActionPreference" "ErrorView"
"ExecutionContext" "foreach" "FormatEnumerationLimit" "HOME" "Host"
"Input" "LASTEXITCODE" "MaximumAliasCount" "MaximumDriveCount"
"MaximumErrorCount" "MaximumFunctionCount" "MaximumHistoryCount"
"MaximumVariableCount" "MyInvocation" "NestedPromptLevel" "OFS"
"OutputEncoding" "PID" "PROFILE" "PSHOME" "PWD" "ProgressPreference"
"ReportErrorShowExceptionClass" "ReportErrorShowInnerException"
"ReportErrorShowSource" "ReportErrorShowStackTrace" "ShellId"
"ShouldProcessPreference" "ShouldProcessReturnPreference" "StackTrace"
"VerbosePreference" "WarningPreference" "WhatIfPreference" "false"
"input" "lastWord" "line" "null" "true" ))
'("$" "?"
"^" "_"
"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
("\\[\\([[:word:].]+\\(\\[\\]\\)?\\)\\]" 1 font-lock-type-face)
`( ;; Type annotations
(,powershell-object-types-regexp . font-lock-type-face)
;; syntaxic keywords
(,(concat "\\<" powershell-keywords "\\>") . font-lock-keyword-face)
(,powershell-keywords . font-lock-keyword-face)
;; operators
(,(concat "\\<-" powershell-operators "\\>") . font-lock-builtin-face)
(,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.")
@ -154,48 +447,219 @@ differently from the other variables.")
(defvar powershell-font-lock-keywords-2
(append
powershell-font-lock-keywords-1
`(;; Built-in variables
(,(concat "\\$\\(" powershell-builtin-variables "\\)\\>")
1 font-lock-builtin-face t)))
`( ;; 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
`(;; Variables in curly brackets
("\\${\\([^}]+\\)}" 1 font-lock-variable-name-face)
;; Variables, with a scope
(,(concat "\\$\\(" powershell-scope-names "\\):"
"\\([[:alnum:]_]+\\)")
(1 (cons font-lock-type-face '(underline)))
(2 font-lock-variable-name-face))
;; Variables, without a scope. XXX: unify this with the
;; previous rule?
("\\$\\([[:alnum:]_]+\\)" 1 font-lock-variable-name-face)
;; hilight properties, but not the methods (personnal preference)
("\\.\\([[:alnum:]_.]+\\)\\>\\s *[^(]" 1 font-lock-variable-name-face)))
`( ;; 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.")
(defvar powershell-mode-syntax-table (make-syntax-table)
"Syntax table for 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))))
(modify-syntax-entry ?# "<" powershell-mode-syntax-table)
(modify-syntax-entry ?\n ">" powershell-mode-syntax-table)
;; Powershell uses a backtick as its escape character.
(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)
(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" "function \\(\\w+\\)" 1)
("Top variables" ,(concat "^\\$\\(" powershell-scope-names "\\)?:?"
"\\([[:alnum:]_]+\\)")
`(("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?"))
@ -212,29 +676,32 @@ differently from the other variables.")
;; 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))
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.")
(define-derived-mode powershell-mode fundamental-mode "PS"
"A major mode for editing Powershell script files."
(set (make-local-variable 'indent-line-function) 'powershell-indent-line)
(set (make-local-variable 'font-lock-defaults)
'((powershell-font-lock-keywords-1
powershell-font-lock-keywords-2
powershell-font-lock-keywords-3)
nil t))
(set (make-local-variable 'comment-start) "# ")
(set (make-local-variable 'comment-start-skip) "#+\\s*")
;; not sure why this is not the default
(set (make-local-variable 'parse-sexp-ignore-comments) t)
(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)
(set (make-local-variable 'imenu-generic-expression)
powershell-imenu-expression)
(set (make-local-variable 'imenu-case-fold-search) nil)
(set (make-local-variable 'compile-command) powershell-compile-command))
(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