xyzzy を Squirrel エディタにする方法

いつまでも書く書く詐欺してるわけにもいかんのでいい加減書きますね (現在 04:35)

完全な初心者のためのエントリにもなるように,ある程度 0 から書いてみます.っつっても Squirrel 使いたい!と思う程度の人が xyzzy の設定程度で難儀しているとも考えにくいですが…

xyzzy は,UNIX 系 OS で二大テキストエディタの一つとして存在する Emacs (もう一つは vi) を Windows で真似して作られたものです.

これが慣れると手放せなくなるんです.

サクラエディタや MK エディタなど,Windows でも様々な高性能のテキストエディタは存在します.しかし,そのカスタマイズ性は Emacs に比べれば微々たるものです.Emacs は,Emacs Lisp というプログラミング言語によってカスタマイズが行われます.で,その設定項目の多さが異常です.あと,Emacs Lispプログラミング言語なので,もう本当に何でも出来ちゃうらしいです *1

簡単な紹介はこれくらいにしておくとして.

インストール方法は,XyzzyWiki/QuickTour/intro2 を参照して下さい.初期設定 (環境変数の設定) は,XyzzyWiki/QuickTour/置き場所を決める を参照して下さい.*2 *3

xyzzy がインストールされたディレクトリを (例えば C:\Program Files\xyzzy を) %XYZZYPATH% とします.

%XYZZYPATH%\lisp というディレクトリに,cc-mode.l というファイルがあります.xyzzyC++ソースコードを編集すると C++予約語が色付きで表示されたり (シンタクスハイライト機能 (syntax hilighting)),インデントを自動で行ってくれたり (オートインデント機能) しますが,それらの設定はこのファイルに書かれています.SquirrelC++ と文法が似ているので,これを改造すれば Squirrel の編集モードを作ることが出来ます.早速 cc-mode.l をコピーしましょう.

さて,コピー先ですが,%XYZZYPATH%lisp にコピーするのはあまり作法的によろしくありません.基本的に独自の拡張や設定は,%XYZZYPATH%\site-lisp ディレクトリに置くものです.分かりやすいように squirrel-mode.l という名前でコピーします.

次に,予約語一覧を設定します.%XYZZYPATH%\etc に C++ というファイル (拡張子等無し) があります.これを Squirrel という名前でコピーします.これは場所は etc ディレクトリのままでいいです.いや,良くないのかもしれませんが,他に置くべき場所もありませんし,あったとしても今の私は知りません.

ここまでが出来たら,いよいよ改造です.

まずは予約語の方から修正しましょう.Squirrel予約語は,Squirrel 公式ページの Document, Squirrel 2.x, HTML Online ページの,2. The language, Lexical structures, Keywords *4 を参考にします.

そして Squirrel ファイルの中身を書き換えます.私は以下のようになっています.

break
case
catch
class
clone
const
constructor
continue
default
delegate
delete
else
enum
extends
false
finally
for
foreach
function
if
in
instanceof
local
null
parent
resume
return
static
switch
this
throw
true
try
typeof
vargc
vargv
while
yield

これで予約語は OK.次は,いよいよ本番とも言える,Xyzzy Lisp の改造です.

…と言ってもすることは超幼稚です.C++Squirrel に,cc または c++squirrel に一括置換するだけです! で,エラーが出るなら C++ あるいは c++ に戻しちゃう,あるいは適当にコメントアウトする! こんな適当でも何とかなります.何とかしてます.

で,エラーが出るかどうかの判断ですが,実際にこの設定を読み込んで貰わなくてはなりません.そのための設定を,%XYZZYPATH%\site-lisp にある site-init.l というファイルに書きましょう.また,ここに同時に .nut ファイルが読み込まれたら自動的に Squirrel モードにするようにする設定も書いておきます.具体的には,以下の 2 行をどこかに追加するだけです.

(load-library "squirrel-mode")
(push '("\\.nut$" . squirrel-mode) *auto-mode-alist*)

次に,squirrel-mode.l と site-init.l をコンパイルします.

M-x byte-compile-file<Enter>
%XYZZYPATH%\site-lisp\squirrel-mode.l<Enter>

上記のようにコマンドを打ち込みます *5.M-x というのは Alt + X のことです *6コンパイルが出来たなら,.l ファイルと同じ階層に同名の .lc ファイルが出来ているはずです.これがコンパイルされて出来たものです.

あとは,xyzzy.exe を,Ctrl と Shift を押しながら Enter を押して実行します.こうすると,設定を一新してくれます *7

で,先述した通りエラーが出なくなるまで squirrelSquirrelc++C++ に戻したら,無事完成です.やったねたえちゃん!Squirrelソースコードが書き易いよ!

後はおまけ的な設定として,インデントを Tab で行うようにしましょう.Squirrel 公式が推薦しているエディタである Eclipse でも,インデントは Tab が基本ですしね.

やり方は,編集モード - QandA Wiki の,「c-mode でのインデントの設定方法がわからないのですが…。」と「c-mode のインデントは,タブにしたいのですが…。」を参考にします.

で,ここで少し厄介になるのが,これらの設定値を変えただけでは,一般的にはインデントが Tab で行われるようにはなりません.ここの数値と,xyzzy の ツール > 共通設定 > 表示 > タブの幅 の数値を同じにしておく必要があるのです.表示設定の方のタブの幅は,デフォルトで 8 になっているので,Wiki の通りの数値にすれば初期状態ならばインデントが Tab で行われるようになります.しかしここを先に弄ったりしていると,「言う通りに設定変えたのにやっぱり Tab にならねーじゃねーかよ!」と逆ギレを起こすハメになります (はい,私です.).

以上の設定を終えた,私が使っている squirrel-mode.l は以下の通りです.

;;; -*- Mode: Lisp; Package: EDITOR -*-
;;;
;;; This file is part of xyzzy.
;;;

(provide "squirrel-mode")

(in-package "editor")

(export '(squirrel-mode *default-c-mode* *squirrel-comment-column*
      *squirrel-mode-hook* *squirrel-keyword-file* *squirrel-indent-tabs-mode*
      squirrel-indent-level squirrel-continued-statement-offset
      squirrel-argdecl-indent squirrel-brace-offset squirrel-brace-imaginary-offset
      squirrel-label-offset squirrel-comment-indent
      detect-squirrel-mode))

(defvar *squirrel-mode-hook* nil)

(unless (boundp 'squirrel-indent-level)
  (setq squirrel-indent-level 8)
  (setq squirrel-continued-statement-offset 8)
  (setq squirrel-argdecl-indent 8)
  (setq squirrel-brace-offset -8)
  (setq squirrel-brace-imaginary-offset 0) ;これは何の設定?
  (setq squirrel-label-offset -0)
  (setq squirrel-comment-indent 2))
;以上の値を http://xyzzy.s53.xrea.com/qanda/wiki.cgi?w=%3Csection%3E%CA%D4%BD%B8%A5%E2%A1%BC%A5%C9%3C%2Fsection%3E&a=show を参考に変更

(defvar *squirrel-keyword-hash-table* nil)
(defvar *squirrel-keyword-file* "Squirrel")

(defvar *squirrel-indent-tabs-mode* t) ;nil -> t に変更
(defvar *squirrel-comment-column* nil)

(defvar *squirrel-mode-syntax-table* nil)
(unless *squirrel-mode-syntax-table*
  (setq *squirrel-mode-syntax-table* (make-syntax-table))
  (do ((x #x21 (1+ x)))((>= x #x7f))
    (let ((c (code-char x)))
      (unless (alphanumericp c)
    (set-syntax-punctuation *squirrel-mode-syntax-table* c))))
  (set-syntax-option *squirrel-mode-syntax-table*
             (+ *syntax-option-c-preprocessor*
            *syntax-option-indent-c++*))
  (set-syntax-string *squirrel-mode-syntax-table* #\")
  (set-syntax-string *squirrel-mode-syntax-table* #\')
  (set-syntax-escape *squirrel-mode-syntax-table* #\\)
  (set-syntax-symbol *squirrel-mode-syntax-table* #\_)
  (set-syntax-symbol *squirrel-mode-syntax-table* #\#)
  (set-syntax-match *squirrel-mode-syntax-table* #\( #\))
  (set-syntax-match *squirrel-mode-syntax-table* #\{ #\})
  (set-syntax-match *squirrel-mode-syntax-table* #\[ #\])
  (set-syntax-start-multi-comment *squirrel-mode-syntax-table* "/*")
  (set-syntax-end-multi-comment *squirrel-mode-syntax-table* "*/")
  (set-syntax-start-c++-comment *squirrel-mode-syntax-table* #\/)
  (set-syntax-end-c++-comment *squirrel-mode-syntax-table* #\LFD))

(defvar *squirrel-mode-map* nil)
(unless *squirrel-mode-map*
  (setq *squirrel-mode-map* (make-sparse-keymap))
  (define-key *squirrel-mode-map* #\{ 'c-electric-insert)
  (define-key *squirrel-mode-map* #\: 'c-electric-insert)
  (define-key *squirrel-mode-map* #\# 'c-electric-insert)
  (define-key *squirrel-mode-map* #\} 'c-electric-close)
  (define-key *squirrel-mode-map* #\C-h 'backward-delete-char-untabify-or-selection)
  (define-key *squirrel-mode-map* #\TAB 'c-indent-line)
  (define-key *squirrel-mode-map* #\C-M-q 'indent-sexp)
  (define-key *squirrel-mode-map* #\RET 'c-newline-and-indent))

(defvar *squirrel-mode-abbrev-table* nil)
(unless *squirrel-mode-abbrev-table*
  (define-abbrev-table '*squirrel-mode-abbrev-table*))

(autoload 'c-build-summary-of-functions "cfns" nil)

(defun squirrel-mode ()
  (interactive)
  (kill-all-local-variables)
  (setq mode-name "Squirrel")
  (setq buffer-mode 'squirrel-mode)
  (use-syntax-table *squirrel-mode-syntax-table*)
  (use-keymap *squirrel-mode-map*)
  (make-local-variable 'mode-specific-indent-command)
  (setq mode-specific-indent-command 'c-indent-line)
  (make-local-variable 'c-comment-indent-variable)
  (setq c-comment-indent-variable 'squirrel-comment-indent)
  (make-local-variable 'paragraph-start)
  (setq paragraph-start "^$\\|\f")
  (make-local-variable 'paragraph-separate)
  (setq paragraph-separate paragraph-start)
  (make-local-variable 'indent-tabs-mode)
  (setq indent-tabs-mode *squirrel-indent-tabs-mode*)
  (make-local-variable 'tags-find-target)
  (setq tags-find-target #'c-tags-find-target)
  (make-local-variable 'tags-find-point)
  (setq tags-find-point #'c-tags-find-point)
  (make-local-variable 'build-summary-function)
  (setq build-summary-function 'c-build-summary-of-functions)
  (and *squirrel-keyword-file*
       (null *squirrel-keyword-hash-table*)
       (setq *squirrel-keyword-hash-table*
         (load-keyword-file *squirrel-keyword-file*)))
  (when *squirrel-keyword-hash-table*
    (make-local-variable 'keyword-hash-table)
    (setq keyword-hash-table *squirrel-keyword-hash-table*))
  (setq *local-abbrev-table* *squirrel-mode-abbrev-table*)
  (setq comment-start "// ")
  (setq comment-end "")
  (setq comment-start-skip "/\\(\\*+\\|/\\)[ \t]*")
  (setq comment-indent-function 'c-comment-indent)
  (when *squirrel-comment-column*
    (setq comment-column *squirrel-comment-column*))
  (run-hooks '*squirrel-mode-hook*))

;(defvar *default-c-mode* 'c-mode)

(defun detect-squirrel-mode ()
  (interactive)
  (let ((mode (save-excursion
        (goto-char (point-min))
        (cond ((or (scan-buffer "//" :limit 3000)
               (scan-buffer "\\(^\\|[^A-Za-z0-9_]\\)class\\($\\|[^A-Za-z0-9_]\\)"
                    :regexp t :limit 3000)
               (scan-buffer "\\(^\\|[^A-Za-z0-9_]\\)\\(public\\|private\\|protected\\)[ \t\n\f]*:"
                    :regexp t :limit 3000))
               'squirrel-mode)
              ((bufferp *auto-mode-last-buffer*)
               (set-buffer *auto-mode-last-buffer*)
               (and (boundp 'buffer-mode)
                (or (eq buffer-mode 'c-mode)
                (eq buffer-mode 'squirrel-mode))
                buffer-mode))))))
    (funcall (or mode *default-c-mode*))))

(export 'decode-c-mode)
(setf (symbol-function 'decode-c-mode) #'detect-squirrel-mode)

とりあえず,シンタクスハイライトとオートインデントだけは出来るようになりました.

改造の手順は,自分でメモ代わりに残した意味合いも強いので,メンド臭い人はこれらをそのままコピペして下さればいいかとw

ただ,問題は当然結構あります.

何気に,文字列の色変えがそもそも C++ 編集モードにも無いようで,黒いままです.Eclipse だと文字列部分もきちんと色変えが行われていました.この機能も当然欲しいです.

あと,式の最後にセミコロンが抜けたまま改行すると,インデントが 1 段深くなってしまいます.これも C++ は必ず式の最後にセミコロンが必要な性質を律儀に受け継いだためです.まぁセミコロンをわざわざ省略する理由もないので,とりあえずは気にしていませんが一応 Squirrel モードとして正しい状態では無いので修正したいです.

とは言え修正のためには Xyzzy Lisp を理解する必要があるのかなぁ… などと考えると,別に今のままでいいやー,みたいな感じにも.誰か詳しい人,この続きやって下さい ^q^

追記

xyzzy で使われているのは Emacs Lisp ではなく xyzzy Lisp なので,xyzzy Lisp と書くべきところを修正.

*1:らしい,と書いたのは自分はまだそこまでの域には達しておらずプログラマブルなカスタマイズはしたことがないため.

*2:XyzzyWiki に 2010-09-20 の時点で移設の予定が書かれていました.もしかしたら後日この URI は無効になっているかもしれません.

*3:環境変数って言われてもよく分からんな…って人は,ググってもよく分からないと思うので「Windowsxyzzy の設定ファイルの場所を知るために必要な情報」くらいに覚えておくと幸せかもしれません.なんでいちいちそんなものが必要なのかは気にしない.もちろん何の理由も無いわけではなくちゃんと理由はある.

*4:ここも Squirrel サイトの構成が変わったりするとリンク切れになるかもしれません.

*5:site-init.l をコンパイルするときはもちろん squirrel-mode.l を site-init.l に変えるだけ

*6:M は Meta key の意味

*7:詳細に述べると,xyzzy.wxp という設定ファイルを更新する.ちなみに xyzzy.wxp は Windows XP の場合に作られるファイル.Windows 2000 だと xyzzy.w2k になるそうです.Vista 以降は分かりません.ただ Windows 7xyzzy.wxp が生成される,という記載を何処かで見かけました.