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