Some tweaks

This commit is contained in:
John Doty 2012-10-06 16:49:27 -07:00
parent 79f0af91c0
commit 92130e2524
4 changed files with 230 additions and 187 deletions

View file

@ -1,213 +1,240 @@
;; powershell-mode.el, version 0.5jd
;;
;; Author: John Doty (http://friendlyhedgehog.com)
;; Based on the work by Vivek Sharma (http://www.viveksharma.com/techlog)
;; Provides: Major mode for editing PS (PowerShell) scripts
;; Last Updated: 04/29/11
;;
;; DONE
;; - Indentation support (done)
;; - support here strings (done)
;;; powershell-mode.el --- Mode for editing Powershell scripts
;; Copyright (C) 2009, 2010 Frédéric Perrin
;; Author: Frédéric Perrin <frederic (dot) perrin (arobas) resel (dot) fr>
;; Keywords: Powershell, Monad, MSH
;; This file is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published
;; by the Free Software Foundation, either version 3 of the License,
;; or (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Comment:
;; This is still WIP.
;;
;; TODO
;; - C# class support
;; - PowerShell v2 support
;;
;; CHANGES
;; 0.1 - initial version with text highlighting support and indenting
;; 0.2 - fixed file based on feedback to handle xemacs and syntax file changes
;; 0.3 - updated to reflect Monad --> PowerShell name change
;; 0.4 - added manual indentation support, not totally what I'd like, but good enough
;; 0.5 - update from Richard Bielawski (email address excluded till he grants permission to
;; include it :) - Many thanks Richard! Fixes: indenting fixed, elseif keyword added,
;; visiting file bug fixed, standard comment support added. I only tested with latest
;; EmacsWin32, YMMV
;; 0.5jd - JohnDoty started to work on this; nothing quite yet.
;; This was written from scratch, without using Vivek Sharma's code:
;; it had issues I wanted to correct, but unfortunately there were no
;; licence indication, and Vivek didn't answered my mails.
;;
;; This is still pretty basic: there is indentation, syntax
;; hilighting, speedbar/imenu support. The indentation is pretty naïve
;; but robust, and sufficient for my current needs.
;; custom hooks
(defvar powershell-mode-hook nil)
(setq debug-on-error t)
;; default mode map, really simple
(defvar powershell-mode-map
(let ((powershell-mode-map (make-keymap)))
;; (define-key powershell-mode-map "\r" 'powershell-indent-line)
(define-key powershell-mode-map "\t" 'powershell-indent-line)
;; (define-key powershell-mode-map "{" 'powershell-electric-brace)
;; (define-key powershell-mode-map "}" 'powershell-electric-brace)
powershell-mode-map)
"Keymap for PS major mode")
(defvar powershell-indent 4
"Amount of horizontal space to indent after, for instance, an
opening brace")
(defvar powershell-indent-width 4)
(defvar powershell-continuation-indent 2
"Amount of horizontal space to indent a continuation line")
;; make braces indent properly
(defun powershell-electric-brace (arg)
"Correct indentation for squigly brace"
(interactive "P")
(self-insert-command (prefix-numeric-value arg))
(unless
(save-excursion
(beginning-of-line)
(or
;; (looking-at "$\w+") ;Don't do this in a variable
(looking-at "{\s*|[^}]") ;Don't do this in an open procedure block
(looking-at "\"[^\"]*$")) ;Don't do this in a open string
)
(powershell-indent-line)
(forward-char 1)
)
)
(defvar powershell-continued-regexp ".*\\(|[\\t ]*\\|`\\)$"
"Regexp matching a continued line (ending either with an
explicit backtick, or with a pipe).")
;(add-to-list 'auto-mode-alist '("\\.ps1\\'" . powershell-mode))
(defun powershell-continuation-line-p ()
"Returns t is the current line is a continuation line (i.e. the
previous line is a continued line, ending with a backtick or a pipe"
(interactive)
(save-excursion
(forward-line -1)
(looking-at powershell-continued-regexp)))
;; Function to control indenting.
(defun powershell-indent-line ()
"Indent current PowerShell script line"
(interactive)
(defun powershell-indent-line-amount ()
"Returns the column to which the current line ought to be indented."
(interactive)
(beginning-of-line)
(let ((closing-paren (looking-at "[\t ]*[])}]")))
;; a very simple indentation method: if on a continuation line (i.e. the
;; previous line ends with a trailing backtick or pipe), we indent relative
;; to the continued line; otherwise, we indent relative to the ([{ that
;; opened the current block.
(if (powershell-continuation-line-p)
(progn
(while (powershell-continuation-line-p)
(forward-line -1))
(+ (current-indentation) powershell-continuation-indent))
(condition-case nil
(progn
(backward-up-list)
;; indentation relative to the opening paren: if there is text (no
;; comment) after the opening paren, vertically align the block
;; with this text; if we were looking at the closing paren, reset
;; the indentation; otherwise, indent the block by powershell-indent.
(cond ((not (looking-at ".[\t ]*\\(#.*\\)?$"))
(forward-char)
(skip-chars-forward " \t")
(current-column))
(closing-paren
(current-indentation))
(t
(+ (current-indentation) powershell-indent))))
(scan-error ;; most likely, we are at the top-level
0)))))
;; Set the point to beginning of line.
(beginning-of-line)
(if (bobp)
(indent-line-to 0)
(defun powershell-indent-line ()
"Indent the current line of powershell mode, leaving the point
in place if it is inside the meat of the line"
(interactive)
(let ((savep (> (current-column) (current-indentation)))
(amount (save-excursion (powershell-indent-line-amount))))
(if savep
(save-excursion (indent-line-to amount))
(indent-line-to amount))))
(let ((not-indented t) (lines-back 0) cur-indent)
(if (looking-at "^[ \t]*}") ; Check for closing brace
(progn
(save-excursion
(forward-line -1)
(setq lines-back (+ lines-back 1))
(if (looking-at "^[ \t]*{") ; If now looking at opening block
(setq cur-indent (current-indentation)) ;; duplicate indent
(setq cur-indent (- (current-indentation) powershell-indent-width)))
)
;; Taken from <http://www.manning.com/payette/AppCexcerpt.pdf> which seems the
;; closest to a grammar definition for powershell. It is not complete, and
;; contains some inaccuracies (e.g. it says that variables match \$[:alnum:]+,
;; so $_ is not a variable it seems...)
;; Safety check to make sure we don't indent negative.
(if (< cur-indent 0)
(setq cur-indent 0)))
(defvar powershell-keywords
(regexp-opt '("begin" "break" "catch" "continue" "data" "do" "dynamicparam"
"else" "elseif" "end" "exit" "filter" "finally" "for" "foreach"
"from" "function" "if" "in" "param" "process" "return"
"switch" "throw" "trap" "try" "until" "while"))
"Powershell keywords")
(save-excursion
(if (looking-at "^[ \t]*{") ; Opening block
(progn
(forward-line -1)
(setq lines-back (+ lines-back 1))
(setq cur-indent (current-indentation))
(setq not-indented nil))
(defvar powershell-operators
(regexp-opt '("and" "as" "band" "bnot" "bor" "bxor" "casesensitive"
"ccontains" "ceq" "cge" "cgt" "cle" "clike" "clt" "cmatch"
"cne" "cnotcontains" "cnotlike" "cnotmatch" "contains"
"creplace" "eq" "exact" "f" "file" "ge" "gt" "icontains"
"ieq" "ige" "igt" "ile" "ilike" "ilt" "imatch" "ine"
"inotcontains" "inotlike" "inotmatch" "ireplace" "is"
"isnot" "le" "like" "lt" "match" "ne" "not" "notcontains"
"notlike" "notmatch" "or" "replace" "wildcard"))
"Powershell operators")
(while not-indented
(forward-line -1)
(setq lines-back (+ lines-back 1))
(if (looking-at "^[ \t]*}") ; Closing block
(progn
(setq cur-indent (current-indentation))
(setq not-indented nil))
(defvar powershell-scope-names
(regexp-opt
'("env" "function" "global" "local" "private" "script" "variable"))
"Names of scopes in Powershell mode.")
(if (looking-at "^[ \t]*{") ; Opening block
(progn
(setq cur-indent (+ (current-indentation) powershell-indent-width))
(setq not-indented nil))
;; Taken from Get-Variable on a fresh shell, merged with man
;; about_automatic_variables
(defvar powershell-builtin-variables
(regexp-opt
'("^" "_" "$" "?" "Args" "ConfirmPreference" "ConsoleFileName"
"DebugPreference" "Error" "ErrorActionPreference" "ErrorView"
"ExecutionContext" "foreach" "FormatEnumerationLimit" "HOME" "Host"
"Input" "LASTEXITCODE" "MaximumAliasCount" "MaximumDriveCount"
"MaximumErrorCount" "MaximumFunctionCount" "MaximumHistoryCount"
"MaximumVariableCount" "MyInvocation" "NestedPromptLevel" "OFS"
"OutputEncoding" "PID" "PROFILE" "PSHOME" "PWD" "ProgressPreference"
"ReportErrorShowExceptionClass" "ReportErrorShowInnerException"
"ReportErrorShowSource" "ReportErrorShowStackTrace" "ShellId"
"ShouldProcessPreference" "ShouldProcessReturnPreference" "StackTrace"
"VerbosePreference" "WarningPreference" "WhatIfPreference" "false"
"input" "lastWord" "line" "null" "true" ))
"Names of the built-in Powershell variables. They are hilighted
differently from the other variables.")
(if (looking-at "^[ \t]*\\(if\\|for\\|foreach\\|function\\|filter\\|else\\|do\\|while\\)\\>")
(progn
(setq cur-indent (current-indentation))
(forward-line 1)
(setq lines-back (- lines-back 1))
(if (looking-at "^[ \t]*{") ; Has block
(setq not-indented nil)
(if (equal lines-back 0) ; No block
(progn
(setq cur-indent (+ cur-indent powershell-indent-width))
(setq not-indented nil))
(setq not-indented nil)))
)
(defvar powershell-font-lock-keywords-1
`(;; Type annotations
("\\[\\([[:word:].]+\\(\\[\\]\\)?\\)\\]" 1 font-lock-type-face)
;; syntaxic keywords
(,(concat "\\<" powershell-keywords "\\>") . font-lock-keyword-face)
;; operators
(,(concat "\\<-" powershell-operators "\\>") . font-lock-builtin-face)
;; the REQUIRES mark
("^#\\(REQUIRES\\)" 1 font-lock-warning-face t))
"Keywords for the first level of font-locking in Powershell mode.")
(if (bobp)
(setq not-indented nil)))))))))
(if cur-indent
(indent-line-to cur-indent)
(indent-line-to 0)))))
;; only defined one keyword list right now
(defvar powershell-font-lock-keywords-2
(append
powershell-font-lock-keywords-1
`(;; Built-in variables
(,(concat "\\$\\(" powershell-builtin-variables "\\)\\>")
1 font-lock-builtin-face t)))
"Keywords for the second level of font-locking in Powershell mode.")
(defconst powershell-font-lock-keywords-3
(list
'("\\<\\(d\\(?:o\\|efault\\)\\|else\\(if\\)?\\|f\\(?:oreach\\|unction\\|ilter\\)\\|if\\|switch\\|t\\(?:hrow\\|rap\\)\\|w\\(?:here\\|hile\\)\\)\\>" . font-lock-keyword-face)
'("$[a-zA-Z0-9_\\.:{}]+\\>" . font-lock-variable-name-face)
'("\\<\\w+-\\w+\\>" . font-lock-function-name-face)
'("\\<-\\w+\\>" . font-lock-builtin-face)
'("@'[A-z0-9\n\t ]+'@" . font-lock-string-face)
'("@\"[A-z0-9\n\t ]+\"@" . font-lock-string-face)
'("\\(-\\)\\([a-z][a-zA-Z0-9]+\\)" . font-lock-type-face))
"Maximum highlighting for PowerShell major mode")
(defvar powershell-font-lock-keywords-3
(append
powershell-font-lock-keywords-2
`(;; Variables in curly brackets
("\\${\\([^}]+\\)}" 1 font-lock-variable-name-face)
;; Variables, with a scope
(,(concat "\\$\\(" powershell-scope-names "\\):"
"\\([[:alnum:]_]+\\)")
(1 (cons font-lock-type-face '(underline)))
(2 font-lock-variable-name-face))
;; Variables, without a scope. XXX: unify this with the
;; previous rule?
("\\$\\([[:alnum:]_]+\\)" 1 font-lock-variable-name-face)
;; hilight properties, but not the methods (personnal preference)
("\\.\\([[:alnum:]_.]+\\)\\>\\s *[^(]" 1 font-lock-variable-name-face)))
"Keywords for the maximum level of font-locking in Powershell mode.")
(defvar powershell-mode-syntax-table (make-syntax-table)
"Syntax table for Powershell mode")
(defvar powershell-font-lock-keywords powershell-font-lock-keywords-3
"Maximum highlighting for PowerShell major mode")
(modify-syntax-entry ?# "<" powershell-mode-syntax-table)
(modify-syntax-entry ?\n ">" powershell-mode-syntax-table)
;; Powershell uses a backtick as its escape character.
(modify-syntax-entry ?` "\\" powershell-mode-syntax-table)
(modify-syntax-entry ?\\ "_" powershell-mode-syntax-table)
(modify-syntax-entry ?- "w" powershell-mode-syntax-table)
(modify-syntax-entry ?' "\"" powershell-mode-syntax-table)
(defvar powershell-imenu-expression
`(("Functions" "function \\(\\w+\\)" 1)
("Top variables" ,(concat "^\\$\\(" powershell-scope-names "\\)?:?"
"\\([[:alnum:]_]+\\)")
2))
"List of regexps matching important expressions, for speebar & imenu.")
;; is adding punctuation to word syntax appropriate??
(if (require 'speedbar nil t)
(speedbar-add-supported-extension ".ps1?"))
(defvar powershell-mode-syntax-table
(let ((powershell-mode-syntax-table (make-syntax-table)))
(modify-syntax-entry ?. "_" powershell-mode-syntax-table)
(modify-syntax-entry ?: "_" powershell-mode-syntax-table)
(modify-syntax-entry ?\\ "_" powershell-mode-syntax-table)
(modify-syntax-entry ?{ "(}" powershell-mode-syntax-table)
(modify-syntax-entry ?} "){" powershell-mode-syntax-table)
(modify-syntax-entry ?[ "(]" powershell-mode-syntax-table)
(modify-syntax-entry ?] ")[" powershell-mode-syntax-table)
(modify-syntax-entry ?( "()" powershell-mode-syntax-table)
(modify-syntax-entry ?) ")(" powershell-mode-syntax-table)
(modify-syntax-entry ?` "\\" powershell-mode-syntax-table)
(modify-syntax-entry ?_ "w" powershell-mode-syntax-table)
(modify-syntax-entry ?# "<" powershell-mode-syntax-table)
(modify-syntax-entry ?\n ">" powershell-mode-syntax-table)
(modify-syntax-entry ?' "\"" powershell-mode-syntax-table)
powershell-mode-syntax-table)
"Syntax for PowerShell major mode")
(require 'compile nil t)
;; A better command would be something like "powershell.exe -NoLogo
;; -NonInteractive -Command & (buffer-file-name)". But it will just
;; sit there waiting... The following will only work when .ps1 files
;; are associated with powershell.exe. And if they don't contain spaces.
(defvar powershell-compile-command
'(buffer-file-name)
"Default command used to invoke a powershell script")
;; The column number will be off whenever tabs are used. Since this is
;; the default in this mode, we will not capture the column number.
(setq compilation-error-regexp-alist
(cons '("At \\(.*\\):\\([0-9]+\\) char:\\([0-9]+\\)" 1 2)
compilation-error-regexp-alist))
(defvar powershell-imenu-expressions
'((nil "^\\(?:function\\|Add-Class\\)\\s-+\\([-a-z0-9A-Z_^:.]+\\)[^-a-z0-9A-Z_^:.]" 1))
"alist of regexp identifying the start of powershell definitions"
)
;; the hook is automatically run by derived-mode
(defvar powershell-mode-hook '(imenu-add-menubar-index)
"Hook run after the initialization of Powershell mode.")
(defun powershell-setup-imenu ()
"Installs powershell-imenu-expression."
(require 'imenu t)
;; imenu doc says these 3 are buffer-local by default
(setq imenu-generic-expression powershell-imenu-expressions)
(imenu-add-menubar-index)
(require 'which-func t)
(which-function-mode t)
)
(defun powershell-mode ()
"Major mode for editing PowerShell files"
(interactive)
(kill-all-local-variables)
(setq major-mode 'powershell-mode)
(setq mode-name "PS")
(set-syntax-table powershell-mode-syntax-table)
(use-local-map powershell-mode-map)
(define-derived-mode powershell-mode fundamental-mode "PS"
"A major mode for editing Powershell script files."
(set (make-local-variable 'indent-line-function) 'powershell-indent-line)
(set (make-local-variable 'font-lock-defaults)
'(powershell-font-lock-keywords))
'((powershell-font-lock-keywords-1
powershell-font-lock-keywords-2
powershell-font-lock-keywords-3)
nil t))
(set (make-local-variable 'comment-start) "# ")
(set (make-local-variable 'comment-start-skip) "#+\\s*")
;; not sure why this is not the default
(set (make-local-variable 'parse-sexp-ignore-comments) t)
(set-syntax-table powershell-mode-syntax-table)
(set (make-local-variable 'imenu-generic-expression)
powershell-imenu-expression)
(set (make-local-variable 'imenu-case-fold-search) nil)
(set (make-local-variable 'compile-command) powershell-compile-command))
(make-local-variable 'compile-command)
(if buffer-file-name
(setq compile-command (format "PowerShell -noprofile -nologo -command %s"
(expand-file-name buffer-file-name))))
; Here we register our line indentation function with Emacs. Now Emacs will
; call our function every time line indentation is required (like when the
; user calls indent-region).
;; (make-local-variable 'indent-line-function)
;; (setq indent-line-function 'powershell-indent-line)
; Set indentation defaults.
(make-local-variable 'powershell-indent-width)
(set (make-local-variable 'comment-start) "#")
(powershell-setup-imenu)
(run-hooks 'powershell-mode-hook))
(provide 'powershell-mode)
(provide 'powershell-mode)