From 2a4c6969ce9ad58b5702238daf8e5899f3eaf373 Mon Sep 17 00:00:00 2001 From: John Doty Date: Mon, 8 Jul 2013 15:49:44 -0700 Subject: [PATCH] Some local changes --- .emacs.d/init.el | 2 - WindowsPowershell/Profile.ps1 | 11 + site-lisp/powershell-mode.el | 721 ++++++++++++++++++++++++++++------ 3 files changed, 605 insertions(+), 129 deletions(-) diff --git a/.emacs.d/init.el b/.emacs.d/init.el index 878c5c4..e92a285 100644 --- a/.emacs.d/init.el +++ b/.emacs.d/init.el @@ -163,8 +163,6 @@ ;; ================================================================= ;; FUN WITH KEY BINDINGS! YAAAAYYY!!! ;; ================================================================= -;; (Note: I secretly think the use of read-kbd-macro below is dorky. -;; This from the man commenting his .emacs file.) (global-set-key (read-kbd-macro "M-g") 'goto-line) (global-set-key (read-kbd-macro "C-q") 'copy-region-as-kill) (global-set-key (read-kbd-macro "C-w") 'kill-region) diff --git a/WindowsPowershell/Profile.ps1 b/WindowsPowershell/Profile.ps1 index f8291fe..a02a6b6 100644 --- a/WindowsPowershell/Profile.ps1 +++ b/WindowsPowershell/Profile.ps1 @@ -184,3 +184,14 @@ function ConvertFrom-EKB($text) return $ekb } + +function Wrap-Text ($txt) +{ + $t = [string]$txt + while($t.Length -gt 77) { + $t.Substring(0, 77) + $t = $t.Substring(77) + } + $t +} + diff --git a/site-lisp/powershell-mode.el b/site-lisp/powershell-mode.el index a73d0f4..00d8621 100644 --- a/site-lisp/powershell-mode.el +++ b/site-lisp/powershell-mode.el @@ -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 +;; Author: Frédéric Perrin ;; 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 . -;;; 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 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 \"\" powershell-eldoc-obarray) \"\") + (set (intern \"\" powershell-eldoc-obarray) \"\") +... + +Where is the name of the function to which applies. + is the string to display when point is near ." + :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 {|}`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