;; powershell.el, version 0.1 ;; ;; Author: Dino Chiesa ;; Thu, 10 Apr 2008 11:10 ;; ;; Run Windows PowerShell v1.0 as an inferior shell within emacs. Tested with emacs v22.2. ;; ;; TODO: ;; test what happens when you expand the window size beyond the maxWindowWidth for the RawUI ;; make everything configurable (Powershell exe, initial args, powershell prompt regexp) ;; implement powershell launch hooks ;; prevent backspace from deleting the powershell prompt? (do other shells do this?) ;; (require 'shell) (defun powershell-gen-window-width-string () (concat "$a = (Get-Host).UI.RawUI\n" "$b = $a.WindowSize\n" "$b.Width = " (number-to-string (window-width)) "\n" "$a.BufferSize = $b\n" "$a.WindowSize = $b") ) (defvar powershell-prompt-pattern "PS [^#$%>]+>" "Regexp for powershell prompt. This isn't really used, because I couldn't figure out how to get it to work." ) (defgroup powershell nil "Running shell from within Emacs buffers." :group 'processes ) (defcustom powershell-need-rawui-resize t "set when powershell needs to be resized" :group 'powershell ) ;;;###autoload (defun powershell (&optional buffer) "Run an inferior powershell, by invoking the shell function. See the help for shell for more details. \(Type \\[describe-mode] in the shell buffer for a list of commands.)" (interactive (list (and current-prefix-arg (read-buffer "Shell buffer: " (generate-new-buffer-name "*PowerShell*"))))) ; get a name for the buffer (setq buffer (get-buffer-create (or buffer "*PowerShell*"))) (let ( (tmp-shellfile explicit-shell-file-name) ) ; set arguments for the powershell exe. ; This needs to be tunable. (setq explicit-shell-file-name "d:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe") (setq explicit-powershell.exe-args '("-Command" "-" )) ; interactive, but no command prompt ; launch the shell (shell buffer) ; restore the original shell (if explicit-shell-file-name (setq explicit-shell-file-name tmp-shellfile) ) ) (let ( (proc (get-buffer-process buffer)) ) ; This sets up the powershell RawUI screen width. By default, ; the powershell v1.0 assumes terminal width of 80 chars. ; This means input gets wrapped at the 80th column. We reset the ; width of the PS terminal to the window width. (add-hook 'window-size-change-functions 'powershell-window-size-changed) (powershell-window-size-changed) ; ask for initial prompt (comint-simple-send proc "prompt") ) ; hook the kill-buffer action so we can kill the inferior process? (add-hook 'kill-buffer-hook 'powershell-delete-process) ; wrap the comint-input-sender with a PS version ; must do this after launching the shell! (make-local-variable 'comint-input-sender) (setq comint-input-sender 'powershell-simple-send) ; set a preoutput filter for powershell. This will trim newlines after the prompt. (add-hook 'comint-preoutput-filter-functions 'powershell-preoutput-filter-for-prompt) ;(run-hooks 'powershell-launch-hook) ; set the tab key to our tab expansion function ; THIS ASSUMES THAT TabExpansionEmacs HAS ALREADY BEEN LOADED INTO THE SHELL. (local-set-key [tab] 'ps-tab-expand) ; return the buffer created buffer ) (defun powershell-window-size-changed (&optional frame) ; do not actually resize here. instead just set a flag. (setq powershell-need-rawui-resize t) ) (defun powershell-delete-process (&optional proc) (or proc (setq proc (get-buffer-process (current-buffer)))) (and (processp proc) (delete-process proc)) ) ;; This function trims the newline from the prompt that we ;; get back from powershell. It is set into the preoutput ;; filters, so the newline is trimmed before being put into ;; the output buffer. (defun powershell-preoutput-filter-for-prompt (string) (if ; not sure why, but I have not succeeded in using a variable here??? ;(string-match powershell-prompt-pattern string) (string-match "PS [^#$%>]+>" string) (substring string 0 -1) string ) ) (defun powershell-simple-send (proc string) "Override of the comint-simple-send function, specific for powershell. This just sends STRING, plus the prompt command. Normally powershell is in noninteractive model when run as an inferior shell with stdin/stdout redirected, which is the case when running as a shell within emacs. This function insures we get and display the prompt. " ; resize if necessary. We do this by sending a resize string to the shell, ; before sending the actual command to the shell. (if powershell-need-rawui-resize (and (comint-simple-send proc (powershell-gen-window-width-string)) (setq powershell-need-rawui-resize nil) ) ) (comint-simple-send proc string) (comint-simple-send proc "prompt") ) ;; tab expansion for powershell 0.1 ;; by Jay Kint ;; Copyright 2008 Jay Kint ;; ;; This software, meaning the portion encapsulated by --tabexpansion-- comments, is licensed under the ;; Microsoft Public License ;; (found at http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx) ;; ;; THIS SOFTWARE AND ARE PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, ;; BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ;; DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ;; EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ;; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ;; ANY WAY OUT OF THE USE OF THIS SOFTWARE OR THESE INSTRUCTIONS, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH ;; DAMAGE. ;; --tabexpansion-- (defvar *original-line* nil) (defvar *last-line* "") (defvar *ps-tab-completions* nil) (defvar *ps-tab-index* 0) (defun insert-tab-completion (last-word) (let ((beginning-last (- (point) (length last-word)))) (unless beginning-last (setq beginning-last 0)) (goto-char beginning-last) (unless (eolp) (kill-line))) (insert (nth *ps-tab-index* *ps-tab-completions*)) (incf *ps-tab-index*) (if (>= *ps-tab-index* (length *ps-tab-completions*)) (setq *ps-tab-index* 0))) (defun get-last-word (line) (let ((quote-quote (string-match "\"[^\"]+\"$" line)) (quote-end (string-match "\"[^\"]+$" line)) (last-non-whitespace (string-match "[^ \t]+$" line))) (cond (quote-quote (substring line quote-quote)) (quote-end (substring line quote-end)) (last-non-whitespace (substring line last-non-whitespace)) (t "") ))) (defun ps-tab-expand () "Tab completion emulation for PowerShell in Emacs. Works by sending the TabExpansionEmacs command to Powershell and reading the output the first time tab is hit. Subsequent times just rotate through the output lines one at a time until the line is changed." (interactive) (let* ((proc (get-buffer-process (current-buffer))) (pmark (process-mark proc)) (point (point)) (line (buffer-substring-no-properties pmark point)) ) ; if they type something besides tab, then set the new line to build the tab completions from (if (not (string= line *last-line*)) (setq *original-line* line)) ; if we're at an empty completions queue or they typed something new (if (or (null *ps-tab-completions*) (not (string= line *last-line*))) (progn (let* ((old-proc-filter (process-filter proc)) (last-word (get-last-word *original-line*)) ; don't forget the newline at the end (tab-expand-cmd (concat "TabExpansionEmacs -line \"" *original-line* "\" -lastWord \"" last-word "\"\n")) (last-len 0) ) ; reset to empty (setq *ps-tab-completions* nil) (when (not (string= last-word *original-line*)) (progn (condition-case err (progn ; set the process filter to simply concatenate all the strings that we receive from powershell (set-process-filter proc (lambda (proc str) (setq *ps-tab-completions* (concat *ps-tab-completions* str)) )) ; send the tab command (process-send-string proc tab-expand-cmd) ; get the initial output (accept-process-output proc 1) ; for some reason, accept-process-output doesn't always get all the output, so ; if there is any left we loop until we get it all (while (not (= (length *ps-tab-completions*) last-len)) (setq last-len (length *ps-tab-completions*)) (accept-process-output proc 0 100) )) (error message "%s" (error-message-string err)) ) ; restore our old filter (set-process-filter proc old-proc-filter) (if (not (null *ps-tab-completions*)) (progn ; break the string into a list of lines (setq *ps-tab-completions* (cdddr (split-string *ps-tab-completions* "\n"))) ; strip the spaces at the end of each line (setq *ps-tab-completions* (mapcar (lambda (path) (if (> (length path) 0) (progn (string-match "\\(.+?\\)[ ]*$" path) (let ((new-path (replace-match "\\1" t nil path nil))) (let ((contains-spaces (not (null (string-match " " new-path))))) (if contains-spaces (setq new-path (concat "\"" new-path "\""))) new-path ))) nil )) *ps-tab-completions* )) ; remove problematic blank strings (setq *ps-tab-completions* (remove* nil *ps-tab-completions*)) ; set variables for the first tab expansion (setq *ps-tab-index* 0) (insert-tab-completion last-word) )))))) ; otherwise just put in the next one in the list (let ((last-word (get-last-word line))) (insert-tab-completion last-word) )) ; save the new insertion as the last line to compare against (setq *last-line* (buffer-substring-no-properties pmark (point))) )) (defun reset-ps-tabs () (interactive) (setq *ps-tab-completions nil) (setq *ps-tab-index* 0) (setq *last-line* "") (setq *original-line* nil) ) (get-last-word "dir") ;; --tabexpansion--