2012/02/09


Vimが良くも悪くも「エディタだ」と言われる要因として「画像や異なるグリフのフォントを同時に出せない」ことを上げられます。つまりVimはHTMLやマークダウン等のプレビューを確認する為にいちいちブラウザを起動して確認し、ファイルを更新した際には読み込み直すという面倒な手間が掛かる事を意味しています。
まぁ専用ブラウザを作ればいいんだけど面倒で腰が重かったんだけど、ちょいと作ってみました。

mattn/mkdpreview-vim - GitHub

MkdPreview Markdown previewer for vimmer

https://github.com/mattn/mkdpreview-vim
ファイルタイプがmarkdownなバッファで :MkdPreview!
と実行するとプレビューワが起動します。
mkdpreview-vim
プレビューワが一度起動している状態なら、以後は他のVimからでも :MkdPreview
でプレビューが表示される様になっています。MkdPreview!を実行したVimに関してはBufWritePostに 対してプレビュー更新が行われる様になっているので:wで自動更新されます。この辺は使い勝手で変えていくかも知れない。
仕組みはpythonスクリプトで書かれたウェブサーバ兼Qt4を使ったプレビューワになっていて POSTを受けたらそれをmarkdown-jsを使ってQtWebkitに反映させています。 複数起動とかうっとおしいだろうなと思ったので、ポートは固定にしています。 起動に必要な物としては
  • python (2.7 or later) http://python.org/
  • PyQt4 http://www.riverbankcomputing.co.uk/software/pyqt/download
  • curl command http://curl.haxx.se/libcurl/
  • webapi-vim http://github.com/mattn/webapi-vim
  • markdown-js https://github.com/evilstreak/markdown-js
となります。pythonスクリプトは短いのでコードを貼り付けておきます。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import json
import cgi
from threading import Thread
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
from PyQt4.QtNetwork import *
from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler

os.chdir(os.path.dirname(__file__))

port = int(os.getenv("mkdpreview_port"or "8081")

QNetworkProxyFactory.setUseSystemConfiguration(True)
app = QApplication(sys.argv)
webview = QWebView()
webview.setWindowTitle('Markdown Previewer')
webview.load(QUrl("http://localhost:8081"))
def do_eval(js):
  webview.page().mainFrame().evaluateJavaScript(
      "preview(%s)" % json.dumps(unicode(js, 'utf-8')))
QObject.connect(webview, SIGNAL("preview(QString)"), do_eval)
webview.show()

class PreviewHandler(SimpleHTTPRequestHandler):
  def do_POST(self):
    s = self.rfile.read(int(self.headers.getheader('content-length')))
    p = cgi.parse_qs(s)
    webview.emit(SIGNAL("preview(QString)"), p["data"][0])
    self.wfile.write("")

class WebServer(QThread):
  def __init__(self):
    QThread.__init__(self)
    self.server = HTTPServer(("", port), PreviewHandler)

  def run(self):
    self.server.serve_forever()

server = WebServer()
server.start()

sys.exit(app.exec_())

# vim:set et sw=2 ts=2:
良かったら使ってみて下さい。 なお、ベースとなるHTMLはstaticフォルダにあるのでスタイル等をカスタマイズしたい人はじゃんじゃんやっちゃって下さい。 こうすればもっと使い勝手が良くなるよなどあればpull requestお願いします。

追記
Windowsじゃない人はstatic/mkdpreview.pyに実行権限与えて下さい。
markdown-jsじゃなくpython側でparseする様にしました。
Posted at by



2012/02/01


こんにちわ。昨今、ウェブ開発の進化はすざましいですね。PythonやPerlやJava、色んな言語で書かれていると思います。
もちろん編集にはVimを使っているかと思います。
でも編集だけ?

違うよね!
Vim scriptはウェブアプリケーション記述言語なんだよ!

Plack::App::Vim
package Plack::App::Vim;
use strict;
use warnings;
use parent qw/Plack::Component/;
use Plack::Request;
use Encode;
use JSON::PP;

sub prepare_app {
    my $self = shift;
    $self->{vim} ||= 'vim';
    if (!$self->{server}) {
        open(my $f"vim --serverlist|");
        my $server = <$f>;
        close($f);
        chomp $server;
        $self->{server} = $server;
    }
    if (!$self->{encoding}) {
        open(my $fsprintf("%s --servername %s --remote-expr \"&encoding\"|",
            $self->{vim}$self->{server}));
        my $encoding = <$f>;
        close($f);
        chomp $encoding;
        $self->{encoding} = $encoding;
    }
    $self;
}

sub call {
    my ($self$env) = @_;
    my $req = Plack::Request->new($env);
    my $json = JSON::PP->new->ascii
        ->allow_singlequote->allow_blessed->allow_nonref;
    my $str = $json->encode({
        uri => $env->{PATH_INFO}||'',
        method => $req->method,
        headers => [split/\n/$req->headers->as_string)],
        content => $req->content,
    });
    $str =~ s!"!\\x22!g;

    my $command;
    if ($^O eq 'MSWin32') {
        $command = sprintf(
            '%s --servername %s --remote-expr "vimplack#handle("""%s""")"',
            $self->{vim}$self->{server},
            encode($self->{encoding} || 'utf8'$str));
    } else {
        $command = sprintf(
            "%s --servername %s --remote-expr 'vimplack#handle(\"%s\")'",
            $self->{vim}$self->{server},
            encode($self->{encoding} || 'utf8'$str));
    }
    open(my $f"$command|");
    binmode $f':utf8';
    my $out = <$f>;
    close $f;
    my $res = $json->decode($out);
    $res->[2][0] = encode_utf8 $res->[2][0] if $res;
    $res || [500, ['Content-Type' => 'text/plain'], ['Internal Server Error']];
}

1;

__END__

=head1 NAME

Plack::App::Vim - The Vim App in Plack

=head1 SYNOPSIS

  use Plack::Builder;
  use Plack::App::Vim;

  builder {
    mount "/" => Plack::App::Vim->new(server => 'VIM');
  };

=head1 DESCRIPTION

Plack::App::Vim allows you to write web application with Vim script.

=head1 AUTHOR

Yasuhiro Matsumoto

=head1 SEE ALSO

L<Plack>

=cut
Plack::Appのアプリケーションハンドラを書いたよ。これを起動するpsgiファイルを用意するよ!

app.psgi
#!perl
use lib qw/lib/;
use Plack::Builder;
use Plack::App::Vim;

builder {
    mount "/" => Plack::App::Vim->new(server => 'VIM');
};
引数のserverにはclientserver機能が使えるVimを立ち上げ、そのサーバIDを指定しておく必要があるよ!
そしてVim側にハンドラを書くよ!

autoload/vimplack.vim
scriptencoding utf-8

function! vimplack#handle(req)
  let req = json#decode(a:req)
  let res = [200, {}, ["hello world"]]
  return json#encode(res)
endfunction
PSGIプロトコルそのままですね!便利!

起動しよう!
# plackup app.psgi
HTTP::Server::PSGI: Accepting connections at http://0:5000/
ブラウザでhttp://localhost:5000を開こう!
Vim on PSGI
やたー!
あとはアプリケーション書き放題ですね!
試しに掲示板書いてみるよ!

autoload/vimplack.vim
scriptencoding utf-8

let s:comments = get(s:, 'comments', [])

function! vimplack#handle(req)
  let req = json#decode(a:req)
  if req.uri == "/"
    let res = [200{"Content-Type""text/html; charset=utf-8"}, [""
\."<html>"
\."<link rel='shortcut icon' href='/static/favicon.ico'>"
\."<title>comment board</title>"
\."<body>"
\."<form action='/regist' method='post'>"
\."コメント:<input type='text' name='comment' value='' /><br />"
\."<input type='submit' value='登録' />"
\."</form>"
\.join(map(copy(s:comments)'html#encodeEntityReference(v:val)')'<br />')
\."</body>"
\."</html>"
\]]
  elseif req.uri == '/regist' && req.method == 'POST'
    let params = {}
    for _ in map(split(req.content, '&')'split(v:val,"=")')
      let params[_[0]] = iconv(http#decodeURI(_[1])'utf-8', &encoding)
    endfor
    if has_key(params, 'comment')
      call add(s:comments, params['comment'])
    endif
    let res = [302{"Location""/"}, [""]]
  else
    let res = [404{}, ["404 Dan Not Found"]]
  endif
  return json#encode(res)
endfunction
アプリケーションの更新はVimを再起動するかautoload/vimplack.vimを開いている常態なら :so %
で行けるよ!
Vim on PSGI

知らんかったー
Vim scriptはウェブアプリケーション記述言語やったんやー
mattn/p5-Plack-App-Vim - GitHub

Vim Application Handler for PSGI

https://github.com/mattn/p5-Plack-App-Vim
Posted at by



2012/01/04


まず中平さんの vim-paint をインストール。
ynkdir/vim-paint - GitHub
https://github.com/ynkdir/vim-paint
このままだと日本語出せないのでパッチを当てる。 diff --git a/autoload/paint/bdf.vim b/autoload/paint/bdf.vim
index 8485995..e8323b8 100644
--- a/autoload/paint/bdf.vim
+++ b/autoload/paint/bdf.vim
@@ -139,6 +139,98 @@ let s:font.hexbits = [
       \ [1, 1, 1, 1],
       \ ]
 
+let s:utf8len = [
+\ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+\ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+\ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+\ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+\ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+\ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+\ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+\ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0,
+\]
+
+function! s:dec2bin(v)
+  let v = a:v
+  if v == 0 | return 0 | endif
+  let ret = ""
+  while v > 0
+    let i = v % 2
+    let ret = i . ret
+    let v = v / 2
+  endwhile
+  return ret
+endfunction
+
+function! s:bin2dec(v)
+  let v = a:v
+  if len(v) == 0 | return 0 | endif
+  let i = 1
+  let ret = ""
+  for n in reverse(split(v, '\zs'))
+    if n == 1
+      let ret = ret + i
+    endif
+    let i = i * 2
+  endfor
+  return ret
+endfunction
+
+function! s:bitshift(a,b)
+  let a = s:dec2bin(a:a)
+  let a = repeat('0', 32-len(a)) . a
+  if a:b < 0
+    let a = (repeat('0', -a:b) . a[: a:b-1])[-32:]
+  elseif a:b > 0
+    let a = (a . repeat('0', a:b))[-32:]
+  endif
+  return s:bin2dec(a)
+endfunction
+
+function! s:bitand(a,b)
+  let a = s:dec2bin(a:a)
+  let b = s:dec2bin(a:b)
+  return s:bin2dec(tr((a + b), '21', '10'))
+endfunction
+
+function! s:byte2code(byte)
+  let p = a:byte
+  let n0 = char2nr(p[0])
+  if n0 < 0x80
+    return n0
+  endif
+  let l = s:utf8len[n0]
+  let n1 = char2nr(p[1])
+  if l > 1 && s:bitand(n1, 0xc0) == 0x80
+    if l == 2
+      return s:bitshift(s:bitand(n0, 0x1f), 6) + s:bitand(n1, 0x3f)
+    endif
+    let n2 = char2nr(p[2])
+    if s:bitand(n2, 0xc0) == 0x80
+      if l == 3
+        return s:bitshift(s:bitand(n0, 0x0f), 12) + s:bitshift(s:bitand(n1, 0x3f), 6) + s:bitand(n2, 0x3f)
+      endif
+      let n3 = char2nr(p[3])
+      if s:bitand(n3, 0xc0) == 0x80
+        if l == 4
+          return s:bitshift(s:bitand(n0, 0x07), 18) + s:bitshift(s:bitand(n1, 0x3f), 12) + s:bitshift(s:bitand(n2, 0x3f), 6) + s:bitand(n3, 0x3f)
+        endif
+        let n4 = char2nr(p[4])
+        if s:bitand(n4, 0xc0) == 0x80
+          if (l == 5)
+            return s:bitshift(s:bitand(n0, 0x03), 24) + s:bitshift(s:bitand(n1, 0x3f), 18) + s:bitshift(s:bitand(n2, 0x3f), 12) + s:bitshift(s:bitand(n3 & 0x3f), 6) + s:bitand(n4, 0x3f)
+          endif
+          let n5 = char2nr(p[5])
+          if s:bitand(n5, 0xc0) == 0x80 && l == 6
+            return s:bitshift(s:bitand(n0, 0x01), 30) + s:bitshift(s:bitand(n1, 0x3f), 24) + s:bitshift(s:bitand(n2, 0x3f), 18) + s:bitshift(s:bitand(n3, 0x3f), 12) + s:bitshift(s:bitand(n4, 0x3f), 6) + s:bitand(n5, 0x3f)
+          endif
+        endif
+      endif
+    endif
+  endif
+  return n0
+endfunction
+
 "
 " |hello, world
 " +------------
@@ -146,7 +238,8 @@ let s:font.hexbits = [
 " origin
 function s:font.draw_text(canvas, text, org, color)
   let [ox, oy] = a:org
-  for c in map(split(a:text, '\zs'), 'char2nr(v:val)')
+  for c in map(split(a:text, '\zs'), 's:byte2code(iconv(v:val, &encoding, "utf-8"))')
+    call garbagecollect()
     if !has_key(self.glyphs, c)
       let c = self.default_char
     endif
ビットシフト関数は結構適当。最新のvimならbitwize関数が入ってるはずなので、そっちを使った方が良い。さらにunifontからbdfフォントを貰ってくる。
GNU Unifont Glyphs

This page contains the latest release of the GNU Unifont, with glyphs for every printable code point...

http://unifoundry.com/unifont.html
そしてコード scriptencoding utf-8
let canvas = paint#canvas#new(10050)
let font = paint#bdf#loadfile(globpath(&rtp, 'font/unifont/unifont-5.1.20080820.bdf'))
let text = "あけまして"
call canvas.draw_text(text, [1020], font, [000])
let text = "おめでとう"
call canvas.draw_text(text, [1040], font, [000])

call canvas.save('kakizome.bmp')
出来上がり
vim-de-kakizome
ちなみに画像ファイル出力までに5分くらいかかります。
Posted at by