The Hobbit Hole

In a hole there lived a hobbit. Not a nasty, dirty, wet hole, filled with the ends of worms and an oozy smell, nor yet a dry, bare, sandy hole with nothing in it to sit down on or to eat: it was a hobbit-hole, and that means comfort.

7/26/2008

Powershell in Emacs (with tab expansion)

Filed under: Programming — bilbo @ 9:11 am

If you’re just looking for how to run Powershell in Emacs, that problem has already been solved. However, that solution doesn’t address tab expansion. Instead it relies on Emacs’ default tab expansion, which needless to say is different than Powershell’s.

I worked up this solution over a few weeks as spare time allowed. The basic gist is that whenever tab is pressed, we run a function that runs a Powershell script – TabExpansionEmacs. Why not just run the default TabExpansion function? It seems that TabExpansion doesn’t do everything. If there is no TabExpansion function, then the shell does something equivalent to a Resolve-Path command. To emulate what the shell does, I simply created the TabExpansionEmacs function to do similar if TabExpansion doesn’t return something on its own.

To use, first the TabExpansionEmacs function has to be made available. I put it in the Profile.ps1 in the WindowsPowerShell directory under my home directory.

# -------------------------------------------------------------------------
# Function called by Emacs to do tab expansions that work just like the
# built in tab expansion of powershell.exe in a console window.
# -------------------------------------------------------------------------
function TabExpansionEmacs {

    param( $line, $lastWord )

    if( $lastWord -eq "" ) {
        $lastWord = "*"
    }

    $expansion = $(TabExpansion -line $line -lastWord $lastWord)

    if( $expansion -eq $Null ) {
        return (resolve-path -path ($lastWord + "*"))
    }
}

After we have this available, next is to make Emacs use it. Here is the function that I came up with to tell Emacs to execute the TabExpansionEmacs and then to insert the results into the buffer.

;; tab expansion for powershell v 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) )

;; --tabexpansion--

I won’t bore you with the details of these functions. To make it work, I simply appended it to DotNetInterop’s powershell.el file and added the following line to the bottom of the powershell function (just before the return of the buffer):

  (local-set-key [tab] 'ps-tab-expand)

Just for convenience, I’ve included the entire file for download.

Of course, I must apply the normal disclaimers that are found in the file. This is 0.1 level software. There are several known cases where the tab expansion doesn’t work properly, but it suits my purposes so far, and I thought I would share it. I hope you find it useful too. If you improve upon it, let me (and DotNetInterop) know!

7/23/2008

Book Review: C++ Coding Standards

Filed under: Programming — bilbo @ 3:13 pm

I finished reading C++ Coding Standards a couple of weeks ago, but work has been hectic, so I’ve not been able to post the review that I wanted. Now that it’s starting to slow a bit (you know, the light at the end of the tunnel…or the train), I decided to write this review.

First things first. If you don’t have any of the Effective C++ books, you should get this book. It’s not written by the same person (Scott Meyers), but includes much of the same wisdom as those those books.

All programming languages have idioms that represent the generally correct, or at least accepted, way to do something out of all the possibilities. C++ Coding Standards gives 101 tips on the proper use of C++ idioms. All of them are good and should be adhered to in 99% of the cases. The subjects are well referenced and exceptions are identified in each tip.

The tips are quite broad in their advice. From the pedestrian Don’t use magic numbers to the more academic Prefer composition to inheritance, many of the tips could be used with any object-oriented language. Others, such as Don’t specialize function templates are very specific to C++. However, that mix is good. C++ is a huge language, and they could have easily come up with 101 C++ specific tips, but neglecting the more general but more important tips in favor of specificity would have been a disservice to the C++ community.

The book provides a broad range of topics, from general to specific. Some of the C++ specific ones to live by include:

  • Use vector by default
  • Use const proactively
  • Always provide new and delete together
  • Avoid overloading &&, ||, or , (comma)

Also, there were some C++ design tips that I found enlightening:

  • Customize intentionally and explicitly
  • Consider making virtual functions non-public, and public functions non-virtual

I think my favorite topic would be a tie between the above mentioned Consider making virtual functions non-public, and public functions non-virtual and the the topic of Design and write error-safe code. The guarantees that the latter topic talks about are thought provoking not only about what you should guarantee in the software you write, but also about knowing what is guaranteed in the libraries you use.

In short, as I mentioned above, if you have no other books on C++ style, this is probably the best one. I’ve read and recommend the Meyer’s books, but lb. for lb., this is the best single book on the subject.

7/12/2008

Dual Personalities in Coop

Filed under: Games,Programming — bilbo @ 7:22 pm

Schizoid is out! Congratulations to Jamie Fristom in particular and his colleagues at Torpex Games!

I played it for about an hour last night. I don’t know what Jamie is talking about. Uberschizoid is hard!