pysf.vim one-liner/block 実行 script

テキスト・データ中のカーソル下の文字列を OS に実行させると便利なことが多くあります。カーソルの下の xcopy command 文字列を実行させたり、複数行に渡るが小さな python コード・ブロックを実行させたりといった類です。それらを pysf.vim sciprt で行います。以下の六つの関数を実装します。なお pysf.vim スクリプトの実行には Python 2.5 以上をインストールしている、または実行可能なことを前提としています。

  1. ;c ExecPySf_command(): コマンド実行
  2. ;a ExecPySf_1liner(): python sf 計算 one liner
  3. ;f ExecPySf_start(): コマンド start 実行
  4. ;p ExecPy_Block(): Python ブロック実行
  5. ;k ExecSf_Block(): Python sf 計算ブロック実行
  6. ;e Exec_BlockCntn(): ブロック実行andコマンド連続実行

Python がインストールされていなくて pysf.vim スクリプトを試すにはpysf_095eAll.zipをお使いください。これを解凍してできる Python265M95d ディレクトリをカレント・ディレクトリとるすることで Python が動きます。ポータブル・タイプに作ってあり環境変数やレジストリを操作していません。130M と少し大きいのですが、ベーシックな Python と scipy, sympy, vpython、matplot および pysf 計算パッケージも含んでいます。他にも流用でき便利だと思います。

Python はインストールしてあり、 scipy, sympy, vpython, matplot もインストール済みの方は、こちらの pysf_095e.zip を試してみてください。Python sf の数値計算パッケージが動くようになります。これにより vim を matlab/mathematica と同等の計算ソフトにできます。Python を扱える方ならば matlab/mathematica 以上にできるでしょう。

pysf_095eAll.zip, pysf_095e.zip どちらかを使えば Python や Python sf を必要とする pysf.vim の関数も実行できるようになります。

なお、現在のところ pysf.vim は Windows 上でしか動作確認していません。Python sf 計算パッケージが Windows 向けしかないからです。「ExecPySf_command() コマンド実行」「ExecPy_Block(): Python ブロック実行」、「Exec_BlockCntn(): ブロック実行andコマンド連続実行」の三つだけなら特殊なことをしていないので Linux でも動くと思います。でも動作確認していません。

pysf.vim のファイルをここにおいておきます。pysf.vim スクリプトを以下で実行するテキスト・ファイル principalBorrow.txt をここにおいておきます。実行させながら以下を読んで頂くことを希望します。

以下、pysf.vim スクリプトの中の六つの関数それぞれについて見ていきましょう。

;a ExecPySf_command(): コマンド実行

ExecPySf_command() はファイル上に書かれた文字列をコマンドとみなして OS に実行させる関数です。

例えば grep(または findstr) コマンドを例に考えてみましょう。grep コマンドは長くなりがちです。使う頻度が多いくせに似たようなコマンドになりがちです。grep の対象とするディレクトリは同じものを使うことが多いからです。下のような英辞郎テキスト・データ・ファイルから日本語表現に対応する英語表現を探し出すことを想定してください。英語の文章を書こうとすると下のような検索を何度も行うことになります。

6 行目と同じ文字列を cmd.exe の画面に打ち込むのは馬鹿げています。6 行目を yank してalt+tab で cmd.exe 画面に切り替えて alt+space, alt+e,p と操作して、この grep 文字列を cmd.exe 画面に paste して実行させることもありです。

でも、このよなう操作を何十回も行うのも馬鹿げています。そこで ExecPySf_command() vim sciprt に、この作業を行わせます。6 行目にカーソルを持って行き、ノーマル・モードで「;a」操作を行うと vim の system(..) 関数を使って、カーソルの下の行を OS に実行させます。上の画像は OS に実行させた後の返り値を vim がコマンド・ウィンドウに表示している状態です。

次に別の日本語表現を調べたいきは、上の 8 行目のように 6 行目を yank して編集して「;a」を行うか 6 行目を直接編集して「;a」を行うかはユーザーの選択です。

コマンド実行結果は無名レジスタに入っているので、必要があれば p でペーストできます

ExecPySf_command() script のコードは下のようになっています。



vim script

function! ExecPySf_command()
    let l:strAt = __getLineOmittingComment()

    echo l:strAt
    let @0= system(l:strAt)
    let @* = @0
    echo @0
endfunction         


__getLineOmittingComment() でカーソル下の文字列を取り出し vim の system(..) 関数を実行させているだけです。

__getLineOmittingComment() は、多くの場合 getline(“.”) でもかまいません。でもコマンド以外の文字列も含むときにも ExecPySf_command(..) 関数が働くようにするため、__getLineOmittingComment() 関数を設けました。下のような具合です

__getLineOmittingComment() 関数のコードは下のようにしてあります。「;;」and/or「##」の文字列が見つかったら、その間の文字列を返します。それらが見つからなかったら「最初から」and/or「最後まで」の文字列を返します。



vim script

function! __getLineOmittingComment()
    let l:strAt = getline(".")
    let l:inAt = 0

    if l:strAt =~ ";;"
        let l:inAt = match(l:strAt,";;")
        let l:strAt = l:strAt[l:inAt+2 :]
    endif

    if l:strAt =~ "##"
        " get a sub-string before ##
        let l:inAt = match(l:strAt,"##")
        let l:strAt = l:strAt[: l:inAt-1]
    endif

    return l:strAt
endfunction         


xcopy コマンドのようなファイル・バック・アップ・コマンドは誤りが許されないことが多くあります。疲れているときなど、xcopy コマンドを最初から自分で書いて「最新のファイルが古いファイルで上書きされてしまった」などのミスを経験した方も多いと思います。このような xcopy バックアップ・コマンドは特定のファイルに書いておき何度も再利用すべきです。そのとき ExecPySf_command() 関数は重宝します。

上の図の「python -c “import numpy as sc;print sc.sin(1+1j)」コマンドは Python の numpy package を利用して複素数引数値:1+i に対する sin 関数の値を計算させています。「計算させる」視点で見ると面白い使い方です。Python の numpy には数値計算のための多くの機能が実装されています。当然ながら sin,cos, exp,log などの基本関数も numpy に実装されています。これらの関数の大部分は複素数値引数に対しても計算できるように作られています。

ですから Python と numpy が install されていれば、numpy の計算機能全てを vim から利用できてしまいます。これだけでも関数電卓の機能を超えます。numpy には行列・ベクトル計算機能や fft 計算機能なども入っており、これらも vim から利用できます。

でも数学計算を日常的に多用する理系の人間にとって「python -c “import numpy as sc;print sc.sin(1+1j)」の記述は冗長すぎます。理系の人間にとって sin,cos, exp, log などの基本関数名は global 変数空間に存在すべき名前です。「python -c “import numpy as sc;…”」を常に書き足すのも馬鹿げています。「sin(1+i)」の数式だけで計算できることが望まれます。これを可能にするのが Python sf package であり、次に説明する ExecPySf_1liner() vim script 関数です。

;c ExecPySf_1liner(): : python sf 計算 one liner

Python sf は数学計算に特化した言語です。Python に upper compatible です。できるだけメモ書きしている数式のままで計算可能なように作ってあります。

  1. プリプロセッサを使うことで、積演算子:* を省略可能にしています。これによりメモ書き数式に近づけています。
  2. numpy の array を継承・拡張した ClTensor 行列・ベクトルクラスを作り、行列やベクトルの積や内積を積演算子:* __mul__(..) 関数で計算させています。これによってもメモ書き数式に近づけています。
  3. 行列やベクトルを ~[….] と ~ を追加した square bracket で表現できるようにしています。これによってもメモ書き数式に近づけます
  4. Bool 体 Zp(N):素数有限体など一般体を行列・ベクトルを扱う ClFldTns クラスを実装してあります。一般体行列・ベクトルでも ~[…] 表記を共用できます。
  5. 一般体の係数を持つ多項式も扱えます
  6. simpy を使うことで symbolic な数式処理も可能にしています。
  7. back quote:「`」を変数名の前 and/or 後ろに追加可能にすることで数学向けに名前空間を拡張しています。例えば `i で純虚数の i を表せるようにしています。これにより sin(1+`i) とメモ書き数式に近づけています。
  8. ユーザー・カスタマイズ可能な ~+,~-,~*,~/,~%,~&,~^,~|,~== 二項演算子 ~(..) 単項演算子を用意しています。
  9. カスタマイズ・ファイルcustomize.py(全部に共用されるカスタマイズ), sfCrrnitIni.py(ディレクトリ毎に設定されるカスタマイズ) を使うことでユーザーにあわせたカスタマイズを可能にしています。これに ` による名前空間の拡張とユーザー演算子を組み合わせることでユーザーの専門分野に合わせた短いメモ書き数式を使えるようにしています。

カスタマイズを活用することで理系の人間が計算する数式の九割以上を Python sf のワンライナーで扱えると思います。というより Pythohn sf は、九割以上を Python sf one liner 数式で扱えるようにカスタマイズして使うソフトです。

Python sf のより詳細は■■ python sf マニュアル ■■ ■■ python sf 詳細マニュアル ■■ を参照願います。

このような Python sf を使えば先の 「python -c “import numpy as sc;print sc.sin(1+1j)」は 「python -u -m sfPP “sin(1+`i)”」で計算できます。以下のように 20 行目にカーソルを置いて「;a」操作をするだけで計算結果が vim のコマンド・ライン・ウィンドウに表示されます。

ここで sfPP.py はプリプロセッサであり「sin(1+`i)」文字列を「sin(1+k__bq_i___)」と Python で扱える文字列に修正します。「k__bq_i___=1j」と customize.py ファイルで python の純虚数 i に割り振ってあります。

このように「;a」操作で 「python -u -m sfPP “sin(1+`i)”」を計算させられますが、まだ冗長です。計算のたびに、決まりきった「python -u -m sfPP “…. “」文字列を書くのは馬鹿げています。こんな文字列は vim scirpt で追加できます。これを行うのが ExecPySf_1liner() vim script 関数です。

下の画像の 22 行目にカーソルを持っていき「;c」操作をするだけで OS に「python -u -m sfPP “sin(1+`i)”」を実行させます。下図のような具合です。

Python sf を使い ExecPySf_1liner() vim script 関数を使えば多くの計算を one-liner で実行できてしまいます。標準配布の Python sf でも大学の初等数学までの計算ならば、大部分を one-liner で計算できてしまうと思います。下図に初歩的な例を示します。

グラフも描かせられます。





;f ExecPySf_start(): コマンド start 実行

カーソル下の文字列を OS のコマンドとして実行させるにしても、独立したプロセスとして実行させたいことが多くあります。そのコマンドが直ぐには終わらないときです。Vim エディタと、そのコマンド実行結果を並列して実行し続けたいときです。そのとき「ExecPySf_start(): コマンド start 実行」を使います

例えば Google 検索を実行させて何らかの解説 web ページを見つけた後、その web ページを見ながら vim で文章を書いていくことが多くあります。でも「ExecPySf_command(): コマンド実行」で実行させたコマンドだと、それが終わるまで vim は停止してしまいます。Vim から実行させたプロセスと並行して元の vim も動かすためには、ウィンドウズでは “start” 付きでコマンドを実行させてやる必要があります。

付け加えるに ウィンドウズでは拡張子への関連付けにより「start ファイル名」だけで関連付けされたプログラムが別プロセスとして動き始めます。 例えば「URL 文字列 http://www.vim.org/」があったら、start 文字列を追加した「start http://www.vim.org/」文字列を OS にコマンドとして与えるだけで、勝手にブラウザを立ち上げ、その URL をアクセスします。start コマンドは単純ですが便利なコマンドです。テキスト・ファイル中に 関連付け済みの拡張子の付いたファイル名文字列があれば、そのテキストがリンクを掛けられた状態にできてしまうことになるからです。html 拡張子が付いたファイル名文字列ならば web brawser が、jpg 拡張子の付いたファイル名ならば画像ビューアが指定されたファイル名データにより立ち上がります。

文字列に start を付け加えて OS コマンドとして実行させることは vim スクリプトには簡単なことです。 pysf.vim の ExecPySf_start() 関数ははカーソルの下の文字列に start 文字列を追加して OS のコマンドとして実行させます。ただし vim の system(..) 関数を使えません。system(..) 関数は戻り値が必須なようで start 付きのコマンドを実行させるとエラーになります。それを避けるために下のように python 経由で実行させています。Vim エディタ画面上の start 実行させたい文字列の上にカーソルを持って行きノーマル・モードで ;f 操作をすることで実行させます。


function! ExecPySf_start()
    let l:strAt = __getLineOmittingComment()

    echo 'start ' . l:strAt
    exe '!python -c "import os;os.system('."'start ".l:strAt ."')"
endfunction         

ExecPySf_start() 問題点

でも、下のように vimrun.exe の終了をユーザーが手作業で行わなければならない問題が残っています。一々 vimrun.exe のウィンドウに切り替えて alt+space ->C 操作を行わねばなりません。そうしないと vim が停止したままになります。この対策をご存知の方は御教え願います。

;p ExecPy_Block(): Python ブロック実行

Python one-liner ではなく、複数行の小さなコード・ブロックを実行させたいときもあります。このときは ExecPy_Block() vim script 関数を使います。//@@ と //@@@ の間に Python コードを記述しておき、//@@ 行 と //@@@ 行の間の何処かにカーソルを置いておき「;p」でで挟まれた Python コード・ブロックを 実行させます。

ExecPy_Block() 関数は カーソル行から上側に //@@ 行を探します。//@@ 行がないときは 1 行目からとします。次に //@@@ 行を探して //@@ と //@@@ の間のテキスト・データをカレント・ディレクトリの temp.py ファイルに書き出します。そして temp.py を Python に実行させます。下のコードで実装されています。



function! ExecPy_Block()
     let l:inStartAt = __getUpperAt()
    let l:inEndAt = __getLowerAt(l:inStartAt)
    if l:inEndAt == -1
        echo "We cant find //@@@"
    else
        echo [l:inStartAt, l:inEndAt]
        call writefile(getline(l:inStartAt, l:inEndAt), "temp.py")
        let @0= system("python -u temp.py")
        let @* = @0
        echo @0
    endif
endfunction


下のように 56 行目から 74 行目の Python コードを実行します。56 行から 74 行のどこかにカーソルを置いておき、ノーマル・モードで「;p」を行えば、この Python コードが実行されます。

;k ExecSf_Block(): Python sf 計算ブロック実行

Python sf の計算式も、少し複雑になってくると one-liner では実行できません。というより可読性を犠牲にしてまで one-liner に拘るべきではありません。このとき ExecSf_Block() vim script 関数を使います。//@@ 行から //@@@ 行の範囲に Python sf 計算コードを書いておき、そこにカーソルをおいたノーマル・モード状態で「;k」と操作します。下のような具合です

pi/6, pi/4 だけ回転させた メッシュ表示の三角形を描いてみました。

;e Exec_BlockCntn(): ブロック実行andコマンド連続実行

Python, Python sf 以外の一般のコードブロックの実行 Exec_BlockCntn() で行います。//@@ と //@@@ で挟まれた複数行をカレント・ディレクトリの __temp ファイルに書き出します。また //@@@ に続く // で始まる行をコマンド行として OS に実行させていきます。



vim エディタで下の //@@ 行と //@@@ 行の間にカーソルを持って行きノーマル・モードで ;e 操作をする

//@@
main = do
    -- print  concat [[[1], []], [[2,3]]] -- error
    print $ concat [[[1], []], [[2,3]]]
    print $ concat [ [1], [],   [2,3]]
    putStrLn $show $ concat [ [1], [],   [2,3]]
//@@@
//copy __temp temp.hs /y
//C:\lng\ghc6121\bin\runghc.exe temp.hs
[[1],[],[2,3]]
[1,2,3]
[1,2,3]


どんな言語でも短いテスト・コードを書いて動作を確認してみることは頻繁に発生します。そのようなテスト・コードはユーザーの宝です。でも小さなファイルを大量に作ってしまっては後で再利用できにくくなってしまいます。

Exec_BlockCntn() を使って一つのファイルにに多くのテスト・コードを残しておけば、それが再利用可能な宝として残したことになります。「;e」操作だけで何時でも再実行可能な形式で残っているのですから。コマンド引数があったら、それも一緒に残っているのですから。コンパイル・オプションがあったら、それも一緒に残っているのですから。

最後に

以上の動作を実装した pysf.vim スクリプトを下に示します


vim script
"2010.12.28 pysf.vim version 0.1a
"coded by kVerifierLab:kenji kobayashi
function! __getLineOmittingComment()
    let l:strAt = getline(".")
    let l:inAt = 0

    if l:strAt =~ ";;"
        let l:inAt = match(l:strAt,";;")
        let l:strAt = l:strAt[l:inAt+2 :]
    endif

    if l:strAt =~ "##"
        " get a sub-string before ##
        let l:inAt = match(l:strAt,"##")
        let l:strAt = l:strAt[: l:inAt-1]
    endif

    return l:strAt
endfunction         


function! ExecPySf_1liner()
    let l:strAt = __getLineOmittingComment()

    let @0= system('python -u -m sfPP "' . l:strAt . '"')
    let @" = @0
    echo @0
endfunction         

function! ExecPySf_start()
    let l:strAt = __getLineOmittingComment()

    echo 'start ' . l:strAt
    exe '!python -c "import os;os.system('."'start ".l:strAt ."')"
endfunction         
"vim system(..) 関数を使うとエラーになる。OS コマンドの戻り値を VIo??.tmp ファイルに
"リダイレクトさせようとしてエラーになる。 start ... コマンドには戻り値がないのだろう
"python < l:inBeforeAt
            let l:inAt = 1
            break
        elseif l:strAt[:4] == "//@@@"
            let l:inBeforeAt = l:inAt-1
            continue
        elseif l:strAt[:3] == "//@@"
            let l:inAt += 1
            break
        endif

    endwhile

    "debug
    "echo l:inAt

    return l:inAt
endfunction

function! __getLowerAt(inAg)
    let l:inBeforeAt = a:inAg
    call cursor(l:inBeforeAt,0)
    while 1
        let l:inAt = search("//@@@")
        let l:strAt = getline(".")
        if l:inAt == 0
            let l:inAt = 1
            break
        elseif l:inAt < l:inBeforeAt
            let l:inAt = -1
            break
        elseif l:strAt[:4] == "//@@@"
            let l:inAt -= 1
            break
        endif

    endwhile

    "debug
    echo l:inAt

    return l:inAt
endfunction

function! ExecPy_Block()
    let l:inStartAt = __getUpperAt()
    let l:inEndAt = __getLowerAt(l:inStartAt)
    if l:inEndAt == -1
        echo "We cant find //@@@"
    else
        echo [l:inStartAt, l:inEndAt]
        call writefile(getline(l:inStartAt, l:inEndAt), "temp.py")
        let @0= system("python -u temp.py")
        let @" = @0
        echo @0
    endif
endfunction

function! ExecSf_Block()
    let l:inStartAt = __getUpperAt()
    let l:inEndAt = __getLowerAt(l:inStartAt)
    if l:inEndAt == -1
        echo "We cant find //@@@"
    else
        echo [l:inStartAt, l:inEndAt]
        call writefile(getline(l:inStartAt, l:inEndAt), "temp.py")
        let l:strAt = system("python -u -m sfPP -fs temp.py")
        let @0 = l:strAt
        if l:strAt =~ "Error"
            echo @0
            return
        endif

        let l:strAt = system("python -u __tempConverted.py")
        if @0 == 0
            let @0 = l:strAt
        else
            let @0 += l:strAt
        endif

        let @" = @0
        echo @0
    endif
endfunction

function! Exec_BlockCntn()
    let l:inStartAt = __getUpperAt()
    let l:inEndAt = __getLowerAt(l:inStartAt)
    if l:inEndAt == -1
        echo "We cant find //@@@"
    else
        echo [l:inStartAt, l:inEndAt]
        call writefile(getline(l:inStartAt, l:inEndAt), "__temp")
    endif

    let l:inEndAt += 1  " at //@@@
    let @0=""
    while 1
        let l:inEndAt += 1  " next line
        call cursor(l:inEndAt, 0)
        let l:strAt = getline(".")
        if l:strAt[:1] != "//"
            break
        endif

        let l:strAt = system(l:strAt[2:])
        if l:strAt =~ "Error"
            break
        elseif l:strAt != ""
            if @0 == 0
                let @0 = l:strAt
            else
                let @0 += l:strAt
            endif
        endif
    endwhile

    let @" = @0
    echo @0
endfunction


nnoremap  ;c     :call      ExecPySf_1liner()
nnoremap  ;j     :call      ExecPySf_1liner()

nnoremap  ;f     :call      ExecPySf_start()
nnoremap  ;a     :call      ExecPySf_command()

nnoremap  ;p     :call      ExecPy_Block()
nnoremap  ;k     :call      ExecSf_Block()
nnoremap  ;e     :call      Exec_BlockCntn()