dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

powershell.el (57163B)


      1 ;;; powershell.el --- Mode for editing PowerShell scripts  -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2009, 2010 Frédéric Perrin
      4 ;; Copyright (C) 2012 Richard Bielawski rbielaws-at-i1-dot-net
      5 ;;               http://www.emacswiki.org/emacs/Rick_Bielawski
      6 
      7 ;; Author: Frédéric Perrin <frederic (dot) perrin (arobas) resel (dot) fr>
      8 ;; URL: http://github.com/jschaf/powershell.el
      9 ;; Package-Version: 20220805.1712
     10 ;; Package-Commit: f2da15857e430206e215a3c65289b4058ae3c976
     11 ;; Version: 0.3
     12 ;; Package-Requires: ((emacs "24"))
     13 ;; Keywords: powershell, languages
     14 
     15 ;; This file is NOT part of GNU Emacs.
     16 
     17 ;; This file is free software: you can redistribute it and/or modify
     18 ;; it under the terms of the GNU General Public License as published
     19 ;; by the Free Software Foundation, either version 3 of the License,
     20 ;; or (at your option) any later version.
     21 
     22 ;; GNU Emacs is distributed in the hope that it will be useful, but
     23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
     24 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     25 ;; General Public License for more details.
     26 
     27 ;; You should have received a copy of the GNU General Public License
     28 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
     29 
     30 ;;; Installation:
     31 
     32 ;; Place powershell.el on your `load-path' by adding the following
     33 ;; code to your `user-init-file', which is usually ~/.emacs.d/init.el
     34 ;; or ~/.emacs.
     35 ;;
     36 ;; (add-to-list 'load-path "~/path/to/powershell")
     37 ;;
     38 
     39 ;;; Commentary:
     40 
     41 ;; powershell.el is a combination of powershell.el by Dino Chiesa
     42 ;; <dpchiesa@hotmail.com> and powershell-mode.el by Frédéric Perrin
     43 ;; and Richard Bielawski.  Joe Schafer combined the work into a single
     44 ;; file.
     45 
     46 ;;; Frédéric Perrin Comments:
     47 ;;
     48 ;; The original powershell-mode.el was written from scratch, without
     49 ;; using Vivek Sharma's code: it had issues I wanted to correct, but
     50 ;; unfortunately there were no licence indication, and Vivek didn't
     51 ;; answered my mails.
     52 ;;
     53 ;;; Rick Bielawski Comments 2012/09/28:
     54 ;;
     55 ;; On March 31, 2012 Frédéric gave me permission to take over support
     56 ;; for powershell-mode.el.  I've added support for multi-line comments
     57 ;; and here-strings as well as enhancement/features such as: Functions
     58 ;; to quote, unquote and escape a selection, and one to wrap a
     59 ;; selection in $().  Meanwhile I hope I didn't break anything.
     60 ;;
     61 ;; Joe Schafer Comments 2013-06-06:
     62 ;;
     63 ;; I combined powershell.el and powershell-mode.el.  Since
     64 ;; powershell.el was licensed with the new BSD license I combined the
     65 ;; two files using the more restrictive license, the GPL.  I also
     66 ;; cleaned up the documentation and reorganized some of the code.
     67 
     68 ;;; Updates:
     69 
     70 ;; 2012/10/01 Fixed several bugs in highlighting variables and types.
     71 ;;            Renamed some variables to be more descriptive.
     72 ;; 2012/10/02 Enhanced PowerShell-mode indenting & syntax table.
     73 ;;            Fixed dangling parens and re-indented the elisp itself.
     74 ;; 2012/10/05 Added eldoc support.  Fixed bug where indent could loop.
     75 ;;            See comment below on how to generate powershell-eldoc.el
     76 ;; 2013/06/06 Merged powershell.el and powershell-mode.el
     77 
     78 ;;; Code:
     79 
     80 (eval-when-compile (require 'thingatpt))
     81 (require 'shell)
     82 (require 'compile)
     83 
     84 ;;;###autoload
     85 (add-to-list 'auto-mode-alist '("\\.ps[dm]?1\\'" . powershell-mode))
     86 
     87 
     88 ;; User Variables
     89 
     90 (defgroup powershell nil
     91   "Customization of PowerShell mode."
     92   :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
     93   :group 'languages)
     94 
     95 (defcustom powershell-indent 4
     96   "Amount of horizontal space to indent.
     97 After, for instance, an opening brace"
     98   :type 'integer
     99   :group 'powershell)
    100 
    101 (defcustom powershell-continuation-indent 2
    102   "Amount of horizontal space to indent a continuation line."
    103   :type 'integer
    104   :group 'powershell)
    105 
    106 (defcustom powershell-continued-regexp  ".*\\(|[\\t ]*\\|`\\)$"
    107   "Regexp matching a continued line.
    108 Ending either with an explicit backtick, or with a pipe."
    109   :type 'integer
    110   :group 'powershell)
    111 
    112 ;; Note: There are no explicit references to the variable
    113 ;; `explicit-powershell.exe-args'.  It is used implicitly by M-x shell
    114 ;; when the shell is `powershell.exe'.  See
    115 ;; http://blogs.msdn.com/b/dotnetinterop/archive/2008/04/10/run-powershell-as-a-shell-within-emacs.aspx
    116 ;; for details.
    117 (defcustom explicit-powershell.exe-args '("-Command" "-" )
    118   "Args passed to inferior shell by \\[shell], if the shell is powershell.exe.
    119 Value is a list of strings, which may be nil."
    120   :type '(repeat (string :tag "Argument"))
    121   :group 'powershell)
    122 
    123 (defun powershell-continuation-line-p ()
    124   "Return t is the current line is a continuation line.
    125 The current line is a continued line when the previous line ends
    126 with a backtick or a pipe"
    127   (interactive)
    128   (save-excursion
    129     (forward-line -1)
    130     (looking-at powershell-continued-regexp)))
    131 
    132 ;; Rick added significant complexity to Frédéric's original version
    133 (defun powershell-indent-line-amount ()
    134   "Return the column to which the current line ought to be indented."
    135   (interactive)
    136   (save-excursion
    137     (beginning-of-line)
    138     (if (powershell-continuation-line-p)
    139         ;; on a continuation line (i.e. prior line ends with backtick
    140         ;; or pipe), indent relative to the continued line.
    141         (progn
    142           (while (and (not (bobp))(powershell-continuation-line-p))
    143             (forward-line -1))
    144           (+ (current-indentation) powershell-continuation-indent))
    145       ;; otherwise, indent relative to the block's opening char ([{
    146       ;; \\s- includes newline, which make the line right before closing paren not indented
    147       (let ((closing-paren (looking-at "[ \t]*\\s)"))
    148             new-indent
    149             block-open-line)
    150         (condition-case nil
    151             (progn
    152               (backward-up-list)   ;when at top level, throw to no-indent
    153               (setq block-open-line (line-number-at-pos))
    154               ;; We're in a block, calculate/return indent amount.
    155               (if (not (looking-at "\\s(\\s-*\\(#.*\\)?$"))
    156                   ;; code (not comments) follow the block open so
    157                   ;; vertically align the block with the code.
    158                   (if closing-paren
    159                       ;; closing indent = open
    160                       (setq new-indent (current-column))
    161                     ;; block indent = first line of code
    162                     (forward-char)
    163                     (skip-syntax-forward " ")
    164                     (setq new-indent (current-column)))
    165                 ;; otherwise block open is at eol so indent is relative to
    166                 ;; bol or another block open on the same line.
    167                 (if closing-paren       ; this sets the default indent
    168                     (setq new-indent (current-indentation))
    169                   (setq new-indent (+ powershell-indent (current-indentation))))
    170                 ;; now see if the block is nested on the same line
    171                 (when (condition-case nil
    172                           (progn
    173                             (backward-up-list)
    174                             (= block-open-line (line-number-at-pos)))
    175                         (scan-error nil))
    176                   (forward-char)
    177                   (skip-syntax-forward " ")
    178                   (if closing-paren
    179                       (setq new-indent (current-column))
    180                     (setq new-indent (+ powershell-indent (current-column))))))
    181               new-indent)
    182           (scan-error ;; most likely, we are at the top-level
    183            0))))))
    184 
    185 (defun powershell-indent-line ()
    186   "Indent the current line of powershell mode.
    187 Leave the point in place if it is inside the meat of the line"
    188   (interactive)
    189   (let ((savep (> (current-column) (current-indentation)))
    190         (amount (powershell-indent-line-amount)))
    191     (if savep
    192         (save-excursion (indent-line-to amount))
    193       (indent-line-to amount))))
    194 
    195 (defun powershell-quote-selection (beg end)
    196   "Quotes the selection between BEG and END.
    197 Quotes with single quotes and doubles embedded single quotes."
    198   (interactive `(,(region-beginning) ,(region-end)))
    199   (if (not mark-active)
    200       (error "Command requires a marked region"))
    201   (goto-char beg)
    202   (while (re-search-forward "'" end t)
    203     (replace-match "''")(setq end (1+ end)))
    204   (goto-char beg)
    205   (insert "'")
    206   (setq end (1+ end))
    207   (goto-char end)
    208   (insert "'"))
    209 
    210 (defun powershell-unquote-selection (beg end)
    211   "Unquotes the selected text between BEG and END.
    212 Remove doubled single quotes as we go."
    213   (interactive `(,(region-beginning) ,(region-end)))
    214   (if (not mark-active)
    215       (error "Command requires a marked region"))
    216   (goto-char beg)
    217   (cond ((looking-at "'")
    218          (goto-char end)
    219          (when (looking-back "'" nil)
    220            (delete-char -1)
    221            (setq end (1- end))
    222            (goto-char beg)
    223            (delete-char 1)
    224            (setq end (1- end))
    225            (while (search-forward "'" end t)
    226              (delete-char -1)
    227              (forward-char)
    228              (setq end (1- end)))))
    229         ((looking-at "\"")
    230          (goto-char end)
    231          (when (looking-back "\"" nil)
    232            (delete-char -1)
    233            (setq end (1- end))
    234            (goto-char beg)
    235            (delete-char 1)
    236            (setq end (1- end))
    237            (while (search-forward "\"" end t)
    238              (delete-char -1)
    239              (forward-char)
    240              (setq end (1- end)))
    241            (while (search-forward "`" end t)
    242              (delete-char -1)
    243              (forward-char)
    244              (setq end (1- end)))))
    245         (t (error "Must select quoted text exactly"))))
    246 
    247 (defun powershell-escape-selection (beg end)
    248   "Escape variables between BEG and END.
    249 Also extend existing escapes."
    250   (interactive `(,(region-beginning) ,(region-end)))
    251   (if (not mark-active)
    252       (error "Command requires a marked region"))
    253   (goto-char beg)
    254   (while (re-search-forward "`" end t)
    255     (replace-match "```")(setq end (+ end 2)))
    256   (goto-char beg)
    257   (while (re-search-forward "\\(?:\\=\\|[^`]\\)[$]" end t)
    258     (goto-char (car (cdr (match-data))))
    259     (backward-char)
    260     (insert "`")
    261     (forward-char)
    262     (setq end (1+ end))))
    263 
    264 (defun powershell-doublequote-selection (beg end)
    265   "Quotes the text between BEG and END with double quotes.
    266 Embedded quotes are doubled."
    267   (interactive `(,(region-beginning) ,(region-end)))
    268   (if (not mark-active)
    269       (error "Command requires a marked region"))
    270   (goto-char beg)
    271   (while (re-search-forward "\"" end t)
    272     (replace-match "\"\"")(setq end (1+ end)))
    273   (goto-char beg)
    274   (while (re-search-forward "`'" end t)
    275     (replace-match "```")(setq end (+ 2 end)))
    276   (goto-char beg)
    277   (insert "\"")
    278   (setq end (1+ end))
    279   (goto-char end)
    280   (insert "\""))
    281 
    282 (defun powershell-dollarparen-selection (beg end)
    283   "Wraps the text between BEG and END with $().
    284 The point is moved to the closing paren."
    285   (interactive `(,(region-beginning) ,(region-end)))
    286   (if (not mark-active)
    287       (error "Command requires a marked region"))
    288   (save-excursion
    289     (goto-char end)
    290     (insert ")")
    291     (goto-char beg)
    292     (insert "$("))
    293   (forward-char))
    294 
    295 (defun powershell-regexp-to-regex (beg end)
    296   "Turn the text between BEG and END into a regex.
    297 The text is assumed to be `regexp-opt' output."
    298   (interactive `(,(region-beginning) ,(region-end)))
    299   (if (not mark-active)
    300       (error "Command requires a marked region"))
    301   (save-restriction
    302     (narrow-to-region beg end)
    303     (goto-char (point-min))
    304     (while (re-search-forward "\\\\(" nil t)
    305       (replace-match "("))
    306     (goto-char (point-min))
    307     (while (re-search-forward "\\\\)" nil t)
    308       (replace-match ")"))
    309     (goto-char (point-min))
    310     (while (re-search-forward "\\\\|" nil t)
    311       (replace-match "|"))))
    312 
    313 
    314 ;; Taken from About_Keywords
    315 (defvar powershell-keywords
    316   (concat "\\_<"
    317           (regexp-opt
    318            '("begin" "break" "catch" "class" "continue" "data" "define" "do" "default"
    319              "dynamicparam" "else" "elseif" "end" "enum" "exit" "filter" "finally"
    320              "for" "foreach" "from" "function" "hidden" "if" "in" "param" "process"
    321              "return" "static" "switch" "throw" "trap" "try" "until" "using" "var" "where" "while"
    322              ;; Questionable, specific to workflow sessions
    323              "inlinescript")
    324            t)
    325           "\\_>")
    326   "PowerShell keywords.")
    327 
    328 ;; Taken from About_Comparison_Operators and some questionable sources :-)
    329 (defvar powershell-operators
    330   (concat "\\_<"
    331           (regexp-opt
    332            '("-eq" "-ne" "-gt" "-ge" "-lt" "-le"
    333              ;; case sensitive versions
    334              "-ceq" "-cne" "-cgt" "-cge" "-clt" "-cle"
    335              ;; explicitly case insensitive
    336              "-ieq" "-ine" "-igt" "-ige" "-ilt" "-ile"
    337              "-band" "-bor" "-bxor" "-bnot"
    338              "-and" "-or" "-xor" "-not" "!"
    339              "-like" "-notlike" "-clike" "-cnotlike" "-ilike" "-inotlike"
    340              "-match" "-notmatch" "-cmatch" "-cnotmatch" "-imatch" "-inotmatch"
    341              "-contains" "-notcontains" "-ccontains" "-cnotcontains"
    342              "-icontains" "-inotcontains"
    343              "-replace" "-creplace" "-ireplace"
    344              "-is" "-isnot" "-as" "-f"
    345              "-in" "-cin" "-iin" "-notin" "-cnotin" "-inotin"
    346              "-split" "-csplit" "-isplit"
    347              "-join"
    348              "-shl" "-shr"
    349              ;; Questionable --> specific to certain contexts
    350              "-casesensitive" "-wildcard" "-regex" "-exact" ;specific to case
    351              "-begin" "-process" "-end" ;specific to scriptblock
    352              ) t)
    353           "\\_>")
    354   "PowerShell operators.")
    355 
    356 (defvar powershell-scope-names
    357   '("global"   "local"    "private"  "script"   )
    358   "Names of scopes in PowerShell mode.")
    359 
    360 (defvar powershell-variable-drive-names
    361   (append '("env" "function" "variable" "alias" "hklm" "hkcu" "wsman") powershell-scope-names)
    362   "Names of scopes in PowerShell mode.")
    363 
    364 (defconst powershell-variables-regexp
    365   ;; There are 2 syntaxes detected: ${[scope:]name} and $[scope:]name
    366   ;; Match 0 is the entire variable name.
    367   ;; Match 1 is scope when the former syntax is found.
    368   ;; Match 2 is scope when the latter syntax is found.
    369   (concat
    370    "\\_<$\\(?:{\\(?:" (regexp-opt powershell-variable-drive-names t)
    371    ":\\)?[^}]+}\\|"
    372    "\\(?:" (regexp-opt powershell-variable-drive-names t)
    373    ":\\)?[a-zA-Z0-9_]+\\_>\\)")
    374   "Identifies legal powershell variable names.")
    375 
    376 (defconst powershell-function-names-regex
    377   ;; Syntax detected is [scope:]verb-noun
    378   ;; Match 0 is the entire name.
    379   ;; Match 1 is the scope if any.
    380   ;; Match 2 is the function name (which must exist)
    381   (concat
    382    "\\_<\\(?:" (regexp-opt powershell-scope-names t) ":\\)?"
    383    "\\([A-Z][a-zA-Z0-9]*-[A-Z0-9][a-zA-Z0-9]*\\)\\_>")
    384   "Identifies legal function & filter names.")
    385 
    386 (defconst powershell-object-types-regexp
    387   ;; Syntax is \[name[.name]\] (where the escaped []s are literal)
    388   ;; Only Match 0 is returned.
    389   "\\[\\(?:[a-zA-Z_][a-zA-Z0-9]*\\)\\(?:\\.[a-zA-Z_][a-zA-Z0-9]*\\)*\\]"
    390   "Identifies object type references.  I.E. [object.data.type] syntax.")
    391 
    392 (defconst powershell-function-switch-names-regexp
    393   ;; Only Match 0 is returned.
    394   "\\_<-[a-zA-Z][a-zA-Z0-9]*\\_>"
    395   "Identifies function parameter names of the form -xxxx.")
    396 
    397 ;; Taken from Get-Variable on a fresh shell, merged with man
    398 ;; about_automatic_variables
    399 (defvar powershell-builtin-variables-regexp
    400   (regexp-opt
    401    '("$"                              "?"
    402      "^"                              "_"
    403      "args"                           "ConsoleFileName"
    404      "Error"                          "Event"
    405      "EventArgs"
    406      "EventSubscriber"                "ExecutionContext"
    407      "false"                          "Foreach"
    408      "HOME"                           "Host"
    409      "input"                          "lsCoreCLR"
    410      "lsLinux"                        "lsMacOS"
    411      "lsWindows"                      "LASTEXITCODE"
    412      "Matches"                        "MyInvocation"
    413      "NestedPromptLevel"              "null"
    414      "PID"                            "PROFILE"
    415      "PSBoundParameters"              "PSCmdlet"
    416      "PSCommandPath"
    417      "PSCulture"                      "PSDebugContext"
    418      "PSHOME"                         "PSITEM"
    419      "PSScriptRoot"                   "PSSenderInfo"
    420      "PSUICulture"                    "PSVersionTable"
    421      "PWD"                            "ReportErrorShowExceptionClass"
    422      "ReportErrorShowInnerException"  "ReportErrorShowSource"
    423      "ReportErrorShowStackTrace"      "Sender"
    424      "ShellId"                        "SourceArgs"
    425      "SourceEventArgs"                "StackTrace"
    426      "this"                           "true"                           ) t)
    427   "The names of the built-in PowerShell variables.
    428 They are highlighted differently from the other variables.")
    429 
    430 (defvar powershell-config-variables-regexp
    431   (regexp-opt
    432    '("ConfirmPreference"           "DebugPreference"
    433      "ErrorActionPreference"       "ErrorView"
    434      "FormatEnumerationLimit"      "InformationPreference"
    435      "LogCommandHealthEvent"
    436      "LogCommandLifecycleEvent"    "LogEngineHealthEvent"
    437      "LogEngineLifecycleEvent"     "LogProviderHealthEvent"
    438      "LogProviderLifecycleEvent"   "MaximumAliasCount"
    439      "MaximumDriveCount"           "MaximumErrorCount"
    440      "MaximumFunctionCount"        "MaximumHistoryCount"
    441      "MaximumVariableCount"        "OFS"
    442      "OutputEncoding"              "ProgressPreference"
    443      "PSDefaultParameterValues"    "PSEmailServer"
    444      "PSModuleAutoLoadingPreference" "PSSessionApplicationName"
    445      "PSSessionConfigurationName"  "PSSessionOption"
    446      "VerbosePreference"           "WarningPreference"
    447      "WhatIfPreference"            ) t)
    448   "Names of variables that configure powershell features.")
    449 
    450 
    451 (defun powershell-find-syntactic-comments (limit)
    452   "Find PowerShell comment begin and comment end characters.
    453 Returns match 1 and match 2 for <# #> comment sequences respectively.
    454 Returns match 3 and optionally match 4 for #/eol comments.
    455 Match 4 is returned only if eol is found before LIMIT"
    456   (when (search-forward "#" limit t)
    457     (cond
    458      ((looking-back "<#" nil)
    459       (set-match-data (list (match-beginning 0) (1+ (match-beginning 0))
    460                             (match-beginning 0) (1+ (match-beginning 0)))))
    461      ((looking-at ">")
    462       (set-match-data (list (match-beginning 0) (match-end 0)
    463                             nil nil
    464                             (match-beginning 0) (match-end 0)))
    465       (forward-char))
    466      (t
    467       (let ((start (point)))
    468         (if (search-forward "\n" limit t)
    469             (set-match-data (list (1- start) (match-end 0)
    470                                   nil nil nil nil
    471                                   (1- start) start
    472                                   (match-beginning 0) (match-end 0)))
    473           (set-match-data (list start (match-end 0)
    474                                 nil nil nil nil
    475                                 (1- start) start))))))
    476     t))
    477 
    478 (defun powershell-find-syntactic-quotes (limit)
    479   "Find PowerShell hear string begin and end sequences upto LIMIT.
    480 Returns match 1 and match 2 for @' '@ sequences respectively.
    481 Returns match 3 and match 4 for @\" \"@ sequences respectively."
    482   (when (search-forward "@" limit t)
    483     (cond
    484      ((looking-at "'$")
    485       (set-match-data (list (match-beginning 0) (1+ (match-beginning 0))
    486                             (match-beginning 0) (1+ (match-beginning 0))))
    487       (forward-char))
    488      ((looking-back "^'@" nil)
    489       (set-match-data (list (1- (match-end 0)) (match-end 0)
    490                             nil nil
    491                             (1- (match-end 0)) (match-end 0))))
    492      ((looking-at "\"$")
    493       (set-match-data (list (match-beginning 0) (1+ (match-beginning 0))
    494                             nil nil
    495                             nil nil
    496                             (match-beginning 0) (1+ (match-beginning 0))))
    497       (forward-char))
    498      ((looking-back "^\"@" nil)
    499       (set-match-data (list (1- (match-end 0)) (match-end 0)
    500                             nil nil
    501                             nil nil
    502                             nil nil
    503                             (1- (match-end 0)) (match-end 0)))))
    504     t))
    505 (defvar powershell-font-lock-syntactic-keywords
    506   `((powershell-find-syntactic-comments (1 "!" t t) (2 "!" t t)
    507                                         (3 "<" t t) (4 ">" t t))
    508     (powershell-find-syntactic-quotes (1 "|" t t) (2 "|" t t)
    509                                       (3 "|" t t) (4 "|" t t)))
    510   "A list of regexp's or functions.
    511 Used to add `syntax-table' properties to
    512 characters that can't be set by the `syntax-table' alone.")
    513 
    514 
    515 (defvar powershell-font-lock-keywords-1
    516   `( ;; Type annotations
    517     (,powershell-object-types-regexp . font-lock-type-face)
    518     ;; syntaxic keywords
    519     (,powershell-keywords . font-lock-keyword-face)
    520     ;; operators
    521     (,powershell-operators . font-lock-builtin-face)
    522     ;; the REQUIRES mark
    523     ("^#\\(REQUIRES\\)" 1 font-lock-warning-face t))
    524   "Keywords for the first level of font-locking in PowerShell mode.")
    525 
    526 (defvar powershell-font-lock-keywords-2
    527   (append
    528    powershell-font-lock-keywords-1
    529    `( ;; Built-in variables
    530      (,(concat "\\$\\(" powershell-builtin-variables-regexp "\\)\\>")
    531       0 font-lock-builtin-face t)
    532      (,(concat "\\$\\(" powershell-config-variables-regexp "\\)\\>")
    533       0 font-lock-builtin-face t)))
    534   "Keywords for the second level of font-locking in PowerShell mode.")
    535 
    536 (defvar powershell-font-lock-keywords-3
    537   (append
    538    powershell-font-lock-keywords-2
    539    `( ;; user variables
    540      (,powershell-variables-regexp
    541       (0 font-lock-variable-name-face)
    542       (1 (cons font-lock-type-face '(underline)) t t)
    543       (2 (cons font-lock-type-face '(underline)) t t))
    544      ;; function argument names
    545      (,powershell-function-switch-names-regexp
    546       (0 font-lock-constant-face)
    547       (1 (cons font-lock-type-face '(underline)) t t)
    548       (2 (cons font-lock-type-face '(underline)) t t))
    549      ;; function names
    550      (,powershell-function-names-regex
    551       (0 font-lock-function-name-face)
    552       (1 (cons font-lock-type-face '(underline)) t t))))
    553   "Keywords for the maximum level of font-locking in PowerShell mode.")
    554 
    555 
    556 (defun powershell-setup-font-lock ()
    557   "Set up the buffer local value for `font-lock-defaults'."
    558   ;; I use font-lock-syntactic-keywords to set some properties and I
    559   ;; don't want them ignored.
    560   (set (make-local-variable 'parse-sexp-lookup-properties) t)
    561   ;; This is where all the font-lock stuff actually gets set up.  Once
    562   ;; font-lock-defaults has its value, setting font-lock-mode true should
    563   ;; cause all your syntax highlighting dreams to come true.
    564   (setq font-lock-defaults
    565         ;; The first value is all the keyword expressions.
    566         '((powershell-font-lock-keywords-1
    567            powershell-font-lock-keywords-2
    568            powershell-font-lock-keywords-3)
    569           ;; keywords-only means no strings or comments get fontified
    570           nil
    571           ;; case-fold (t ignores case)
    572           t
    573           ;; syntax-alist nothing special here
    574           nil
    575           ;; syntax-begin - no function defined to move outside syntactic block
    576           nil
    577           ;; font-lock-syntactic-keywords
    578           ;; takes (matcher (match syntax override lexmatch) ...)...
    579           (font-lock-syntactic-keywords
    580            . powershell-font-lock-syntactic-keywords))))
    581 
    582 (defvar powershell-mode-syntax-table
    583   (let ((powershell-mode-syntax-table (make-syntax-table)))
    584     (modify-syntax-entry ?$  "_" powershell-mode-syntax-table)
    585     (modify-syntax-entry ?:  "_" powershell-mode-syntax-table)
    586     (modify-syntax-entry ?-  "_" powershell-mode-syntax-table)
    587     (modify-syntax-entry ?^  "_" powershell-mode-syntax-table)
    588     (modify-syntax-entry ?\\ "_" powershell-mode-syntax-table)
    589     (modify-syntax-entry ?\{ "(}" powershell-mode-syntax-table)
    590     (modify-syntax-entry ?\} "){" powershell-mode-syntax-table)
    591     (modify-syntax-entry ?\[ "(]" powershell-mode-syntax-table)
    592     (modify-syntax-entry ?\] ")[" powershell-mode-syntax-table)
    593     (modify-syntax-entry ?\( "()" powershell-mode-syntax-table)
    594     (modify-syntax-entry ?\) ")(" powershell-mode-syntax-table)
    595     (modify-syntax-entry ?` "\\" powershell-mode-syntax-table)
    596     (modify-syntax-entry ?_  "w" powershell-mode-syntax-table)
    597     (modify-syntax-entry ?=  "." powershell-mode-syntax-table)
    598     (modify-syntax-entry ?|  "." powershell-mode-syntax-table)
    599     (modify-syntax-entry ?+  "." powershell-mode-syntax-table)
    600     (modify-syntax-entry ?*  "." powershell-mode-syntax-table)
    601     (modify-syntax-entry ?/  "." powershell-mode-syntax-table)
    602     (modify-syntax-entry ?' "\"" powershell-mode-syntax-table)
    603     (modify-syntax-entry ?#  "<" powershell-mode-syntax-table)
    604     powershell-mode-syntax-table)
    605   "Syntax for PowerShell major mode.")
    606 
    607 (defvar powershell-mode-map
    608   (let ((powershell-mode-map (make-keymap)))
    609     ;;    (define-key powershell-mode-map "\r" 'powershell-indent-line)
    610     (define-key powershell-mode-map (kbd "M-\"")
    611       'powershell-doublequote-selection)
    612     (define-key powershell-mode-map (kbd "M-'") 'powershell-quote-selection)
    613     (define-key powershell-mode-map (kbd "C-'") 'powershell-unquote-selection)
    614     (define-key powershell-mode-map (kbd "C-\"") 'powershell-unquote-selection)
    615     (define-key powershell-mode-map (kbd "M-`") 'powershell-escape-selection)
    616     (define-key powershell-mode-map (kbd "C-$")
    617       'powershell-dollarparen-selection)
    618     powershell-mode-map)
    619   "Keymap for PS major mode.")
    620 
    621 (defun powershell-setup-menu ()
    622   "Add a menu of PowerShell specific functions to the menu bar."
    623   (define-key (current-local-map) [menu-bar powershell-menu]
    624     (cons "PowerShell" (make-sparse-keymap "PowerShell")))
    625   (define-key (current-local-map) [menu-bar powershell-menu doublequote]
    626     '(menu-item "DoubleQuote Selection" powershell-doublequote-selection
    627                 :key-sequence(kbd "M-\"")
    628                 :help
    629                 "DoubleQuotes the selection escaping embedded double quotes"))
    630   (define-key (current-local-map) [menu-bar powershell-menu quote]
    631     '(menu-item "SingleQuote Selection" powershell-quote-selection
    632                 :key-sequence (kbd "M-'")
    633                 :help
    634                 "SingleQuotes the selection escaping embedded single quotes"))
    635   (define-key (current-local-map) [menu-bar powershell-menu unquote]
    636     '(menu-item "UnQuote Selection" powershell-unquote-selection
    637                 :key-sequence (kbd "C-'")
    638                 :help "Un-Quotes the selection un-escaping any escaped quotes"))
    639   (define-key (current-local-map) [menu-bar powershell-menu escape]
    640     '(menu-item "Escape Selection" powershell-escape-selection
    641                 :key-sequence (kbd "M-`")
    642                 :help (concat "Escapes variables in the selection"
    643                               " and extends existing escapes.")))
    644   (define-key (current-local-map) [menu-bar powershell-menu dollarparen]
    645     '(menu-item "DollarParen Selection" powershell-dollarparen-selection
    646                 :key-sequence (kbd "C-$")
    647                 :help "Wraps the selection in $()")))
    648 
    649 
    650 ;;; Eldoc support
    651 
    652 (defcustom powershell-eldoc-def-files nil
    653   "List of files containing function help strings used by function `eldoc-mode'.
    654 These are the strings function `eldoc-mode' displays as help for
    655 functions near point.  The format of the file must be exactly as
    656 follows or who knows what happens.
    657 
    658    (set (intern \"<fcn-name1>\" powershell-eldoc-obarray) \"<helper string1>\")
    659    (set (intern \"<fcn-name2>\" powershell-eldoc-obarray) \"<helper string2>\")
    660 ...
    661 
    662 Where <fcn-name> is the name of the function to which <helper string> applies.
    663       <helper-string> is the string to display when point is near <fcn-name>."
    664   :type '(repeat string)
    665   :group 'powershell)
    666 
    667 (defvar powershell-eldoc-obarray ()
    668   "Array for file entries by the function `eldoc'.
    669 `powershell-eldoc-def-files' entries are added into this array.")
    670 
    671 (defun powershell-eldoc-function ()
    672   "Return a documentation string appropriate for the current context or nil."
    673   (let ((word (thing-at-point 'symbol)))
    674     (if word
    675         (eval (intern-soft word powershell-eldoc-obarray)))))
    676 
    677 (defun powershell-setup-eldoc ()
    678   "Load the function documentation for use with eldoc."
    679   (when (not (null powershell-eldoc-def-files))
    680     (set (make-local-variable 'eldoc-documentation-function)
    681          'powershell-eldoc-function)
    682     (unless (vectorp powershell-eldoc-obarray)
    683       (setq powershell-eldoc-obarray (make-vector 41 0))
    684       (condition-case var (mapc 'load powershell-eldoc-def-files)
    685         (error (message "*** powershell-setup-eldoc ERROR *** %s" var))))))
    686 ;;; Note: You can create quite a bit of help with these commands:
    687 ;;
    688 ;; function Get-Signature ($Cmd) {
    689 ;;   if ($Cmd -is [Management.Automation.PSMethod]) {
    690 ;;     $List = @($Cmd)}
    691 ;;   elseif ($Cmd -isnot [string]) {
    692 ;;     throw ("Get-Signature {<method>|<command>}`n" +
    693 ;;            "'$Cmd' is not a method or command")}
    694 ;;     else {$List = @(Get-Command $Cmd -ErrorAction SilentlyContinue)}
    695 ;;   if (!$List[0] ) {
    696 ;;     throw "Command '$Cmd' not found"}
    697 ;;   foreach ($O in $List) {
    698 ;;     switch -regex ($O.GetType().Name) {
    699 ;;       'AliasInfo' {
    700 ;;         Get-Signature ($O.Definition)}
    701 ;;       '(Cmdlet|ExternalScript)Info' {
    702 ;;         $O.Definition}          # not sure what to do with ExternalScript
    703 ;;       'F(unction|ilter)Info'{
    704 ;;         if ($O.Definition -match '^param *\(') {
    705 ;;           $t = [Management.Automation.PSParser]::tokenize($O.Definition,
    706 ;;                                                           [ref]$null)
    707 ;;           $c = 1;$i = 1
    708 ;;           while($c -and $i++ -lt $t.count) {
    709 ;;             switch ($t[$i].Type.ToString()) {
    710 ;;               GroupStart {$c++}
    711 ;;               GroupEnd   {$c--}}}
    712 ;;           $O.Definition.substring(0,$t[$i].start + 1)} #needs parsing
    713 ;;         else {$O.Name}}
    714 ;;       'PSMethod' {
    715 ;;         foreach ($t in @($O.OverloadDefinitions)) {
    716 ;;           while (($b=$t.IndexOf('`1[[')) -ge 0) {
    717 ;;             $t=$t.remove($b,$t.IndexOf(']]')-$b+2)}
    718 ;;             $t}}}}}
    719 ;; get-command|
    720 ;;   ?{$_.CommandType -ne 'Alias' -and $_.Name -notlike '*:'}|
    721 ;;   %{$_.Name}|
    722 ;;   sort|
    723 ;;   %{("(set (intern ""$($_.Replace('\','\\'))"" powershell-eldoc-obarray)" +
    724 ;;      " ""$(Get-Signature $_|%{$_.Replace('\','\\').Replace('"','\"')})"")"
    725 ;;     ).Replace("`r`n"")",""")")} > .\powershell-eldoc.el
    726 
    727 
    728 (defvar powershell-imenu-expression
    729   `(("Functions" ,(concat "function " powershell-function-names-regex) 2)
    730     ("Filters" ,(concat "filter " powershell-function-names-regex) 2)
    731     ("Top variables"
    732      , (concat "^\\(" powershell-object-types-regexp "\\)?\\("
    733                powershell-variables-regexp "\\)\\s-*=")
    734      2))
    735   "List of regexps matching important expressions, for speebar & imenu.")
    736 
    737 (defun powershell-setup-imenu ()
    738   "Install `powershell-imenu-expression'."
    739   (when (require 'imenu nil t)
    740     ;; imenu doc says these are buffer-local by default
    741     (setq imenu-generic-expression powershell-imenu-expression)
    742     (setq imenu-case-fold-search nil)
    743     (imenu-add-menubar-index)))
    744 
    745 (defun powershell-setup-speedbar ()
    746   "Install `speedbar-add-supported-extension'."
    747   (when (require 'speedbar nil t)
    748     (speedbar-add-supported-extension ".ps1?")))
    749 
    750 ;; A better command would be something like "powershell.exe -NoLogo
    751 ;; -NonInteractive -Command & (buffer-file-name)". But it will just
    752 ;; sit there waiting...  The following will only work when .ps1 files
    753 ;; are associated with powershell.exe. And if they don't contain spaces.
    754 (defvar powershell-compile-command
    755   '(buffer-file-name)
    756   "Default command used to invoke a powershell script.")
    757 
    758 ;; The column number will be off whenever tabs are used. Since this is
    759 ;; the default in this mode, we will not capture the column number.
    760 (setq compilation-error-regexp-alist
    761       (cons '("At \\(.*\\):\\([0-9]+\\) char:\\([0-9]+\\)" 1 2)
    762             compilation-error-regexp-alist))
    763 
    764 
    765 (add-hook 'powershell-mode-hook #'imenu-add-menubar-index)
    766 
    767 ;;;###autoload
    768 (define-derived-mode powershell-mode prog-mode "PS"
    769   "Major mode for editing PowerShell scripts.
    770 
    771 \\{powershell-mode-map}
    772 Entry to this mode calls the value of `powershell-mode-hook' if
    773 that value is non-nil."
    774   (powershell-setup-font-lock)
    775   (setq-local indent-line-function 'powershell-indent-line)
    776   (setq-local compile-command powershell-compile-command)
    777   (setq-local comment-start "#")
    778   (setq-local comment-start-skip "#+\\s*")
    779   (setq-local parse-sexp-ignore-comments t)
    780   ;; Support electric-pair-mode
    781   (setq-local electric-indent-chars
    782               (append "{}():;," electric-indent-chars))
    783   (powershell-setup-imenu)
    784   (powershell-setup-speedbar)
    785   (powershell-setup-menu)
    786   (powershell-setup-eldoc))
    787 
    788 ;;; PowerShell inferior mode
    789 
    790 ;;; Code:
    791 (defcustom powershell-location-of-exe
    792    (or (executable-find "pwsh") (executable-find "powershell"))
    793   "A string providing the location of the powershell executable. Since
    794 the newer PowerShell Core (pwsh.exe) does not replace the older Windows
    795 PowerShell (powershell.exe) when installed, this attempts to find the
    796 former first, and only if it doesn't exist, falls back to the latter."
    797   :group 'powershell
    798   :type 'string)
    799 
    800 (defcustom powershell-log-level 3
    801   "The current log level for powershell internal operations.
    802 0 = NONE, 1 = Info, 2 = VERBOSE, 3 = DEBUG."
    803   :group 'powershell
    804   :type 'integer)
    805 
    806 (defcustom powershell-squish-results-of-silent-commands t
    807   "The function `powershell-invoke-command-silently' returns the results
    808 of a command in a string.  PowerShell by default, inserts newlines when
    809 the output exceeds the configured width of the powershell virtual
    810 window. In some cases callers might want to get the results with the
    811 newlines and formatting removed. Set this to true, to do that."
    812   :group 'powershell
    813   :type 'boolean)
    814 
    815 (defvar powershell-prompt-regex  "PS [^#$%>]+> "
    816   "Regexp to match the powershell prompt.
    817 powershell.el uses this regex to determine when a command has
    818 completed.  Therefore, you need to set this appropriately if you
    819 explicitly change the prompt function in powershell.  Any value
    820 should include a trailing space, if the powershell prompt uses a
    821 trailing space, but should not include a trailing newline.
    822 
    823 The default value will match the default PowerShell prompt.")
    824 
    825 (defvar powershell-command-reply nil
    826   "The reply of powershell commands.
    827 This is retained for housekeeping purposes.")
    828 
    829 (defvar powershell--max-window-width  0
    830   "The maximum width of a powershell window.
    831 You shouldn't need to ever set this.  It gets set automatically,
    832 once, when the powershell starts up.")
    833 
    834 (defvar powershell-command-timeout-seconds 12
    835   "The timeout for a powershell command.
    836 powershell.el will wait this long before giving up.")
    837 
    838 (defvar powershell--need-rawui-resize t
    839   "No need to fuss with this.  It's intended for internal use
    840 only.  It gets set when powershell needs to be informed that
    841 emacs has resized its window.")
    842 
    843 (defconst powershell--find-max-window-width-command
    844   (concat
    845   "function _Emacs_GetMaxPhsWindowSize"
    846   " {"
    847   " $rawui = (Get-Host).UI.RawUI;"
    848   " $mpws_exists = ($rawui | Get-Member | Where-Object"
    849   " {$_.Name -eq \"MaxPhysicalWindowSize\"});"
    850   " if ($mpws_exists -eq $null) {"
    851   " 210"
    852   " } else {"
    853   " $rawui.MaxPhysicalWindowSize.Width"
    854   " }"
    855   " };"
    856   " _Emacs_GetMaxPhsWindowSize")
    857   "The powershell logic to determine the max physical window width.")
    858 
    859 (defconst powershell--set-window-width-fn-name  "_Emacs_SetWindowWidth"
    860   "The name of the function this mode defines in PowerShell to
    861 set the window width. Intended for internal use only.")
    862 
    863 (defconst powershell--text-of-set-window-width-ps-function
    864   ;; see
    865   ;; http://blogs.msdn.com/lior/archive/2009/05/27/ResizePowerShellConsoleWindow.aspx
    866   ;;
    867   ;; When making the console window narrower, you mus set the window
    868   ;; size first. When making the console window wider, you must set the
    869   ;; buffer size first.
    870 
    871     (concat  "function " powershell--set-window-width-fn-name
    872              "([string] $pswidth)"
    873              " {"
    874              " $rawui = (Get-Host).UI.RawUI;"
    875              " $bufsize = $rawui.BufferSize;"
    876              " $winsize = $rawui.WindowSize;"
    877              " $cwidth = $winsize.Width;"
    878              " $winsize.Width = $pswidth;"
    879              " $bufsize.Width = $pswidth;"
    880              " if ($cwidth -lt $pswidth) {"
    881              " $rawui.BufferSize = $bufsize;"
    882              " $rawui.WindowSize = $winsize;"
    883              " }"
    884              " elseif ($cwidth -gt $pswidth) {"
    885              " $rawui.WindowSize = $winsize;"
    886              " $rawui.BufferSize = $bufsize;"
    887              " };"
    888              " Set-Variable -name rawui -value $null;"
    889              " Set-Variable -name winsize -value $null;"
    890              " Set-Variable -name bufsize -value $null;"
    891              " Set-Variable -name cwidth -value $null;"
    892              " }")
    893 
    894     "The text of the powershell function that will be used at runtime to
    895 set the width of the virtual Window in PowerShell, as the Emacs window
    896 gets resized.")
    897 
    898 (defun powershell-log (level text &rest args)
    899   "Log a message at level LEVEL.
    900 If LEVEL is higher than `powershell-log-level', the message is
    901 ignored.  Otherwise, it is printed using `message'.
    902 TEXT is a format control string, and the remaining arguments ARGS
    903 are the string substitutions (see `format')."
    904   (if (<= level powershell-log-level)
    905       (let* ((msg (apply 'format text args)))
    906         (message "%s" msg))))
    907 
    908 ;; (defun dino-powershell-complete (arg)
    909 ;; "do powershell completion on the given STRING. Pop up a buffer
    910 ;; with the completion list."
    911 ;;   (interactive
    912 ;;    (list (read-no-blanks-input "\
    913 ;; Stub to complete: ")))
    914 
    915 ;;   (let ((proc
    916 ;;          (get-buffer-process (current-buffer))))
    917 ;;    (comint-proc-query proc (concat "Get-Command " arg "*\n"))
    918 ;;    )
    919 ;; )
    920 
    921 ;; (defun dino-powershell-cmd-complete ()
    922 ;;   "try to get powershell completion to work."
    923 ;;   (interactive)
    924 ;;   (let ((proc
    925 ;;          (get-buffer-process (current-buffer))))
    926 ;; ;;   (comint-proc-query proc "Get-a\t")
    927 ;; ;;   (comint-simple-send proc "Get-a\t")
    928 ;;        (comint-send-string proc "Get-a\t\n")
    929 ;; ;;   (process-send-eof)
    930 ;;    )
    931 ;; )
    932 
    933 (defun powershell--define-set-window-width-function (proc)
    934   "Sends a function definition to the PowerShell instance
    935 identified by PROC.  The function sets the window width of the
    936 PowerShell virtual window.  Later, the function will be called
    937 when the width of the emacs window changes."
    938     (if proc
    939         (progn
    940           ;;process-send-string
    941           (comint-simple-send
    942            proc
    943            powershell--text-of-set-window-width-ps-function))))
    944 
    945 (defun powershell--get-max-window-width  (buffer-name)
    946   "Gets the maximum width of the virtual window for PowerShell running
    947 in the buffer with name BUFFER-NAME.
    948 
    949 In PowerShell 1.0, the maximum WindowSize.Width for
    950 PowerShell is 210, hardcoded, I believe. In PowerShell 2.0, the max
    951 windowsize.Width is provided in the RawUI.MaxPhysicalWindowSize
    952 property.
    953 
    954 This function does the right thing, and sets the buffer-local
    955 `powershell--max-window-width' variable with the correct value."
    956   (let ((proc (get-buffer-process buffer-name)))
    957 
    958     (if proc
    959         (with-current-buffer buffer-name
    960           (powershell-invoke-command-silently
    961            proc
    962            powershell--find-max-window-width-command
    963            0.90)
    964 
    965           ;; store the retrieved width
    966           (setq powershell--max-window-width
    967                 (if (and (not (null powershell-command-reply))
    968                          (string-match
    969                           "\\([1-9][0-9]*\\)[ \t\f\v\n]+"
    970                           powershell-command-reply))
    971                     (string-to-number (match-string 1 powershell-command-reply))
    972                   200)))))) ;; could go to 210, but let's use 200 to be safe
    973 
    974 (defun powershell--set-window-width (proc)
    975   "Run the PowerShell function that sets the RawUI width
    976 appropriately for a PowerShell shell.
    977 
    978 This is necessary to get powershell to do the right thing, as far
    979 as text formatting, when the emacs window gets resized.
    980 
    981 The function gets defined in powershell upon powershell startup."
    982   (let ((ps-width
    983          (number-to-string (min powershell--max-window-width (window-width)))))
    984     (progn
    985       ;;(process-send-string
    986       (comint-simple-send
    987        proc
    988        (concat powershell--set-window-width-fn-name
    989                "('" ps-width "')")))))
    990 
    991 ;;;###autoload
    992 (defun powershell (&optional buffer prompt-string)
    993   "Run an inferior PowerShell.
    994 If BUFFER is non-nil, use it to hold the powershell
    995 process.  Defaults to *PowerShell*.
    996 
    997 Interactively, a prefix arg means to prompt for BUFFER.
    998 
    999 If BUFFER exists but the shell process is not running, it makes a
   1000 new shell.
   1001 
   1002 If BUFFER exists and the shell process is running, just switch to
   1003 BUFFER.
   1004 
   1005 If PROMPT-STRING is non-nil, sets the prompt to the given value.
   1006 
   1007 See the help for `shell' for more details.  \(Type
   1008 \\[describe-mode] in the shell buffer for a list of commands.)"
   1009   (interactive
   1010    (list
   1011     (and current-prefix-arg
   1012          (read-buffer "Shell buffer: "
   1013                       (generate-new-buffer-name "*PowerShell*")))))
   1014 
   1015   (setq buffer (get-buffer-create (or buffer "*PowerShell*")))
   1016   (powershell-log 1 "powershell starting up...in buffer %s" (buffer-name buffer))
   1017   (let ((explicit-shell-file-name (if (and (eq system-type 'cygwin)
   1018                                            (fboundp 'cygwin-convert-file-name-from-windows))
   1019 				      (cygwin-convert-file-name-from-windows powershell-location-of-exe)
   1020 				    powershell-location-of-exe)))
   1021     ;; set arguments for the powershell exe.
   1022     ;; Does this need to be tunable?
   1023 
   1024     (shell buffer))
   1025 
   1026   ;; (powershell--get-max-window-width "*PowerShell*")
   1027   ;; (powershell-invoke-command-silently (get-buffer-process "*csdeshell*")
   1028   ;; "[Ionic.Csde.Utilities]::Version()" 2.9)
   1029 
   1030   ;;  (comint-simple-send (get-buffer-process "*csdeshell*") "prompt\n")
   1031 
   1032   (let ((proc (get-buffer-process buffer)))
   1033 
   1034     (make-local-variable 'powershell-prompt-regex)
   1035     (make-local-variable 'powershell-command-reply)
   1036     (make-local-variable 'powershell--max-window-width)
   1037     (make-local-variable 'powershell-command-timeout-seconds)
   1038     (make-local-variable 'powershell-squish-results-of-silent-commands)
   1039     (make-local-variable 'powershell--need-rawui-resize)
   1040     (make-local-variable 'comint-prompt-read-only)
   1041 
   1042     ;; disallow backspace over the prompt:
   1043     (setq comint-prompt-read-only t)
   1044 
   1045     ;; We need to tell powershell how wide the emacs window is, because
   1046     ;; powershell pads its output to the width it thinks its window is.
   1047     ;;
   1048     ;; The way it's done: every time the width of the emacs window changes, we
   1049     ;; set a flag. Then, before sending a powershell command that is
   1050     ;; typed into the buffer, to the actual powershell process, we check
   1051     ;; that flag.  If it is set, we  resize the powershell window appropriately,
   1052     ;; before sending the command.
   1053 
   1054     ;; If we didn't do this, powershell output would get wrapped at a
   1055     ;; column width that would be different than the emacs buffer width,
   1056     ;; and everything would look ugly.
   1057 
   1058     ;; get the maximum width for powershell - can't go beyond this
   1059     (powershell--get-max-window-width buffer)
   1060 
   1061     ;; define the function for use within powershell to resize the window
   1062     (powershell--define-set-window-width-function proc)
   1063 
   1064     ;; add the hook that sets the flag
   1065     (add-hook 'window-size-change-functions
   1066               #'(lambda (&rest _)
   1067                   (setq powershell--need-rawui-resize t)))
   1068 
   1069     ;; set the flag so we resize properly the first time.
   1070     (setq powershell--need-rawui-resize t)
   1071 
   1072     (if prompt-string
   1073         (progn
   1074           ;; This sets up a prompt for the PowerShell.  The prompt is
   1075           ;; important because later, after sending a command to the
   1076           ;; shell, the scanning logic that grabs the output looks for
   1077           ;; the prompt string to determine that the output is complete.
   1078           (comint-simple-send
   1079            proc
   1080            (concat "function prompt { '" prompt-string "' }"))
   1081 
   1082           (setq powershell-prompt-regex prompt-string)))
   1083 
   1084     ;; hook the kill-buffer action so we can kill the inferior process?
   1085     (add-hook 'kill-buffer-hook 'powershell-delete-process)
   1086 
   1087     ;; wrap the comint-input-sender with a PS version
   1088     ;; must do this after launching the shell!
   1089     (make-local-variable 'comint-input-sender)
   1090     (setq comint-input-sender 'powershell-simple-send)
   1091 
   1092     ;; set a preoutput filter for powershell.  This will trim newlines
   1093     ;; after the prompt.
   1094     (add-hook 'comint-preoutput-filter-functions
   1095               'powershell-preoutput-filter-for-prompt)
   1096 
   1097     ;; send a carriage-return  (get the prompt)
   1098     (comint-send-input)
   1099     (accept-process-output proc))
   1100 
   1101   ;; The launch hooks for powershell has not (yet?) been implemented
   1102   ;;(run-hooks 'powershell-launch-hook)
   1103 
   1104   ;; return the buffer created
   1105   buffer)
   1106 
   1107 ;; +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
   1108 ;; Using powershell on emacs23, I get an error:
   1109 ;;
   1110 ;;    ansi-color-process-output: Marker does not point anywhere
   1111 ;;
   1112 ;; Here's what's happening.
   1113 ;;
   1114 ;; In order to be able to read the output from powershell, this shell
   1115 ;; starts powershell.exe in "interactive mode", using the -i
   1116 ;; option. This which has the curious side-effect of turning off the
   1117 ;; prompt in powershell. Normally powershell will return its results,
   1118 ;; then emit a prompt to indicate that it is ready for more input.  In
   1119 ;; interactive mode it doesn't emit the prompt.  To work around this,
   1120 ;; this code (powershell.el) sends an explicit `prompt` command after
   1121 ;; sending any user-entered command to powershell. This tells powershell
   1122 ;; to explicitly return the prompt, after the results of the prior
   1123 ;; command. The prompt then shows up in the powershell buffer.  Lovely.
   1124 ;;
   1125 ;; But, `ansi-color-apply-on-region` gets called after every command
   1126 ;; gets sent to powershell. It gets called with args `(begin end)`,
   1127 ;; which are both markers. Turns out the very first time this fn is
   1128 ;; called, the position for the begin marker is nil.
   1129 ;;
   1130 ;; `ansi-color-apply-on-region` calls `(goto-char begin)` (effectively),
   1131 ;; and when the position on the marker is nil, the call errors with
   1132 ;; "Marker does not point anywhere."
   1133 ;;
   1134 ;; The following advice suppresses the call to
   1135 ;; `ansi-color-apply-on-region` when the begin marker points
   1136 ;; nowhere.
   1137 (defadvice ansi-color-apply-on-region (around
   1138                                        powershell-throttle-ansi-colorizing
   1139                                        (begin end)
   1140                                        compile)
   1141   (progn
   1142     (let ((start-pos (marker-position begin)))
   1143     (cond
   1144      (start-pos
   1145       (progn
   1146         ad-do-it))))))
   1147 
   1148 (defun powershell--silent-cmd-filter (process result)
   1149 "A process filter that captures output from a shell and stores it
   1150 to `powershell-command-reply', rather than allowing the output to
   1151 be displayed in the shell buffer.
   1152 
   1153 This function is intended for internal use only."
   1154   (let ((end-of-result
   1155          (string-match (concat ".*\n\\(" powershell-prompt-regex "\\)[ \n]*\\'")
   1156                        result)))
   1157     (if (and end-of-result (numberp end-of-result))
   1158 
   1159         (progn
   1160           ;; Store everything except the follow-on prompt.
   1161           ;; The result probably includes a final newline!
   1162           (setq result (substring result 0 (match-beginning 1)))
   1163 
   1164           (if powershell-squish-results-of-silent-commands
   1165               (setq result
   1166                     (replace-regexp-in-string "\n" "" result)))
   1167 
   1168           (setq powershell-command-reply
   1169                 (concat powershell-command-reply result)))
   1170 
   1171       (progn
   1172         (if powershell-squish-results-of-silent-commands
   1173               (setq result
   1174                     (replace-regexp-in-string "\n" "" result)))
   1175 
   1176         (setq powershell-command-reply
   1177               (concat powershell-command-reply result))
   1178 
   1179         ;; recurse.  For very very long output, the recursion can
   1180         ;; cause stack overflow. Careful!
   1181         (accept-process-output process powershell-command-timeout-seconds)))))
   1182 
   1183 (defun powershell-invoke-command-silently (proc command
   1184                                                 &optional timeout-seconds)
   1185   "In the PowerShell instance PROC, invoke COMMAND silently.
   1186 Neither the COMMAND is echoed nor the results to the associated
   1187 buffer.  Use TIMEOUT-SECONDS as the timeout, waiting for a
   1188 response.  The COMMAND should be a string, and need not be
   1189 terminated with a newline.
   1190 
   1191 This is helpful when, for example, doing setup work. Or other sneaky
   1192 stuff, such as resetting the size of the PowerShell virtual window.
   1193 
   1194 Returns the result of the command, a string, without the follow-on
   1195 command prompt.  The result will probably end in a newline. This result
   1196 is also stored in the buffer-local variable `powershell-command-reply'.
   1197 
   1198 In some cases the result can be prepended with the command prompt, as
   1199 when, for example, several commands have been send in succession and the
   1200 results of the prior command were not fully processed by the application.
   1201 
   1202 If a PowerShell buffer is not the current buffer, this function
   1203 should be invoked within a call to `with-current-buffer' or
   1204 similar in order to insure that the buffer-local values of
   1205 `powershell-command-reply', `powershell-prompt-regex', and
   1206 `powershell-command-timeout-seconds' are used.
   1207 
   1208 Example:
   1209 
   1210     (with-current-buffer powershell-buffer-name
   1211       (powershell-invoke-command-silently
   1212        proc
   1213        command-string
   1214        1.90))"
   1215 
   1216   (let ((old-timeout powershell-command-timeout-seconds)
   1217         (original-filter (process-filter proc)))
   1218 
   1219     (setq powershell-command-reply nil)
   1220 
   1221     (if timeout-seconds
   1222         (setq powershell-command-timeout-seconds timeout-seconds))
   1223 
   1224     (set-process-filter proc 'powershell--silent-cmd-filter)
   1225 
   1226     ;; Send the command plus the "prompt" command.  The filter
   1227     ;; will know the command is finished when it sees the command
   1228     ;; prompt.
   1229     ;;
   1230     (process-send-string proc (concat command "\nprompt\n"))
   1231 
   1232     (accept-process-output proc powershell-command-timeout-seconds)
   1233 
   1234     ;; output of the command is now available in powershell-command-reply
   1235 
   1236     ;; Trim prompt from the beginning of the output.
   1237     ;; this can happen for the first command through
   1238     ;; the shell.  I think there's a race condition.
   1239     (if (and powershell-command-reply
   1240              (string-match (concat "^" powershell-prompt-regex "\\(.*\\)\\'")
   1241                            powershell-command-reply))
   1242         (setq powershell-command-reply
   1243               (substring powershell-command-reply
   1244                          (match-beginning 1)
   1245                          (match-end 1))))
   1246 
   1247     ;; restore the original filter
   1248     (set-process-filter proc original-filter)
   1249 
   1250     ;; restore the original timeout
   1251     (if timeout-seconds
   1252         (setq powershell-command-timeout-seconds old-timeout))
   1253 
   1254     ;; the result:
   1255     powershell-command-reply))
   1256 
   1257 (defun powershell-delete-process (&optional proc)
   1258   "Delete the current buffer process or PROC."
   1259   (or proc
   1260       (setq proc (get-buffer-process (current-buffer))))
   1261   (and (processp proc)
   1262        (delete-process proc)))
   1263 
   1264 (defun powershell-preoutput-filter-for-prompt (string)
   1265   "Trim the newline from STRING, the prompt that we get back from
   1266 powershell.  This fn is set into the preoutput filters, so the
   1267 newline is trimmed before being put into the output buffer."
   1268    (if (string-match (concat powershell-prompt-regex "\n\\'") string)
   1269        (substring string 0 -1) ;; remove newline
   1270      string))
   1271 
   1272 (defun powershell-simple-send (proc string)
   1273   "Override of the comint-simple-send function, with logic
   1274 specifically designed for powershell.  This just sends STRING,
   1275 plus the prompt command.
   1276 
   1277 When running as an inferior shell with stdin/stdout redirected,
   1278 powershell is in noninteractive mode. This means no prompts get
   1279 emitted when a PS command completes. This makes it difficult for
   1280 a comint mode to determine when the command has completed.
   1281 Therefore, we send an explicit request for the prompt, after
   1282 sending the actual (primary) command. When the primary command
   1283 completes, PowerShell then responds to the \"prompt\" command,
   1284 and emits the prompt.
   1285 
   1286 This insures we get and display the prompt."
   1287   ;; Tell PowerShell to resize its virtual window, if necessary. We do
   1288   ;; this by calling a resize function in the PowerShell, before sending
   1289   ;; the user-entered command to the shell.
   1290   ;;
   1291   ;; PowerShell keeps track of its \"console\", and formats its output
   1292   ;; according to the width it thinks it is using.  This is true even when
   1293   ;; powershell is invoked with the - argument, which tells it to use
   1294   ;; stdin as input.
   1295 
   1296   ;; Therefore, if the user has resized the emacs window since the last
   1297   ;; PowerShell command, we need to tell PowerShell to change the size
   1298   ;; of its virtual window. Calling that function does not change the
   1299   ;; size of a window that is visible on screen - it only changes the
   1300   ;; size of the virtual window that PowerShell thinks it is using.  We
   1301   ;; do that by invoking the PowerShell function that this module
   1302   ;; defined for that purpose.
   1303   ;;
   1304   (if powershell--need-rawui-resize
   1305       (progn
   1306         (powershell--set-window-width proc)
   1307         (setq powershell--need-rawui-resize nil)))
   1308   (comint-simple-send proc (concat string "\n"))
   1309   (comint-simple-send proc "prompt\n"))
   1310 
   1311 ;; Notes on TAB for completion.
   1312 ;; -------------------------------------------------------
   1313 ;; Emacs calls comint-dynamic-complete when the TAB key is pressed in a shell.
   1314 ;; This is set up in shell-mode-map.
   1315 ;;
   1316 ;; comint-dynamic-complete calls the functions in
   1317 ;; comint-dynamic-complete-functions, until one of them returns
   1318 ;; non-nil.
   1319 ;;
   1320 ;; comint-dynamic-complete-functions is a good thing to set in the mode hook.
   1321 ;;
   1322 ;; The default value for that var in a powershell shell is:
   1323 ;; (comint-replace-by-expanded-history
   1324 ;;    shell-dynamic-complete-environment-variable
   1325 ;;    shell-dynamic-complete-command
   1326 ;;    shell-replace-by-expanded-directory
   1327 ;;    comint-dynamic-complete-filename)
   1328 
   1329 ;; (defun powershell-dynamic-complete-command ()
   1330 ;;   "Dynamically complete the command at point.  This function is
   1331 ;; similar to `comint-dynamic-complete-filename', except that it
   1332 ;; searches the commands from powershell and then the `exec-path'
   1333 ;; (minus the trailing Emacs library path) for completion candidates.
   1334 
   1335 ;; Completion is dependent on the value of
   1336 ;; `shell-completion-execonly', plus those that effect file
   1337 ;; completion.  See `powershell-dynamic-complete-as-command'.
   1338 
   1339 ;; Returns t if successful."
   1340 ;;   (interactive)
   1341 ;;   (let ((filename (comint-match-partial-filename)))
   1342 ;;     (if (and filename
   1343 ;;              (save-match-data (not (string-match "[~/]" filename)))
   1344 ;;              (eq (match-beginning 0)
   1345 ;;                  (save-excursion (shell-backward-command 1) (point))))
   1346 ;;         (prog2 (message "Completing command name...")
   1347 ;;             (powershell-dynamic-complete-as-command)))))
   1348 
   1349 ;; (defun powershell-dynamic-complete-as-command ()
   1350 ;;   "Dynamically complete at point as a command.
   1351 ;; See `shell-dynamic-complete-filename'.  Returns t if successful."
   1352 ;;   (let* ((filename (or (comint-match-partial-filename) ""))
   1353 ;;          (filenondir (file-name-nondirectory filename))
   1354 ;;          (path-dirs (cdr (reverse exec-path)))
   1355 ;;          (cwd (file-name-as-directory (expand-file-name default-directory)))
   1356 ;;          (ignored-extensions
   1357 ;;           (and comint-completion-fignore
   1358 ;;                (mapconcat (function (lambda (x) (concat (regexp-quote x) "$")))
   1359 ;;                           comint-completion-fignore "\\|")))
   1360 ;;          (dir "") (comps-in-dir ())
   1361 ;;          (file "") (abs-file-name "") (completions ()))
   1362 
   1363 ;;     ;; Go thru each cmd in powershell's lexicon, finding completions.
   1364 
   1365 ;;     ;; Go thru each dir in the search path, finding completions.
   1366 ;;     (while path-dirs
   1367 ;;       (setq dir (file-name-as-directory (comint-directory (or (car path-dirs) ".")))
   1368 ;;             comps-in-dir (and (file-accessible-directory-p dir)
   1369 ;;                               (file-name-all-completions filenondir dir)))
   1370 ;;       ;; Go thru each completion found, to see whether it should be used.
   1371 ;;       (while comps-in-dir
   1372 ;;         (setq file (car comps-in-dir)
   1373 ;;               abs-file-name (concat dir file))
   1374 ;;         (if (and (not (member file completions))
   1375 ;;                  (not (and ignored-extensions
   1376 ;;                            (string-match ignored-extensions file)))
   1377 ;;                  (or (string-equal dir cwd)
   1378 ;;                      (not (file-directory-p abs-file-name)))
   1379 ;;                  (or (null shell-completion-execonly)
   1380 ;;                      (file-executable-p abs-file-name)))
   1381 ;;             (setq completions (cons file completions)))
   1382 ;;         (setq comps-in-dir (cdr comps-in-dir)))
   1383 ;;       (setq path-dirs (cdr path-dirs)))
   1384 ;;     ;; OK, we've got a list of completions.
   1385 ;;     (let ((success (let ((comint-completion-addsuffix nil))
   1386 ;;                      (comint-dynamic-simple-complete filenondir completions))))
   1387 ;;       (if (and (memq success '(sole shortest)) comint-completion-addsuffix
   1388 ;;                (not (file-directory-p (comint-match-partial-filename))))
   1389 ;;           (insert " "))
   1390 ;;       success)))
   1391 
   1392 (provide 'powershell)
   1393 
   1394 ;;; powershell.el ends here