2016/04/22


コルタナさん、とても便利なのですが Vim を起動する事が出来ない。どこかで見たショートカットファイルを所定の場所に置く方法もうまく動かない。

C:\ProgramData\Microsoft\Windows\Start Menu\Programs

調べると、ボイスコマンドという API が出ているのだけど、Universal Application でないといけない。Universal Application だと System.Diagnostic からプロセスを起動できない。そこで別に立てたウェブサーバから Vim を起動させる。サーバのコードは至って簡単。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            HttpListener listener = new HttpListener();
            listener.Prefixes.Add("http://localhost:8888/");
            listener.Start();
            while (true)
            {
                HttpListenerContext context = listener.GetContext();
                HttpListenerRequest req = context.Request;
                HttpListenerResponse res = context.Response;

                if (req.RawUrl == "/vim")
                {
                    Process.Start("gvim");
                    res.StatusCode = 200;
                }
                else
                {
                    res.StatusCode = 404;
                }
                res.Close();
            }
        }
    }
}

次に空の Universal Application を作り、OnLaunched イベントでボイスコマンドAPIにコマンド定義を食わせる。

        protected async override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif
            Frame rootFrame = Window.Current.Content as Frame;

            // ウィンドウに既にコンテンツが表示されている場合は、アプリケーションの初期化を繰り返さずに、
            // ウィンドウがアクティブであることだけを確認してください
            if (rootFrame == null)
            {
                // ナビゲーション コンテキストとして動作するフレームを作成し、最初のページに移動します
                rootFrame = new Frame();

                rootFrame.NavigationFailed += OnNavigationFailed;

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    //TODO: 以前中断したアプリケーションから状態を読み込みます
                }

                // フレームを現在のウィンドウに配置します
                Window.Current.Content = rootFrame;
            }

            if (e.PrelaunchActivated == false)
            {
                if (rootFrame.Content == null)
                {
                    // ナビゲーション スタックが復元されない場合は、最初のページに移動します。
                    // このとき、必要な情報をナビゲーション パラメーターとして渡して、新しいページを
                    //構成します
                    rootFrame.Navigate(typeof(MainPage), e.Arguments);
                }
                // 現在のウィンドウがアクティブであることを確認します
                Window.Current.Activate();

                try
                {
                    // 俺コマンドの登録
                    StorageFile vcdStorageFile = await Package.Current.InstalledLocation.GetFileAsync(@"MyCommands.xml");
                    await VoiceCommandDefinitionManager.InstallCommandDefinitionsFromStorageFileAsync(vcdStorageFile);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine("ボイスコマンドの定義でエラーが発生しました", ex);
                }
            }
        }

食わせるコマンド定義は XML で記述し、以下のようにした。

<?xml version="1.0" encoding="utf-8" ?>

<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.2">
  <CommandSet xml:lang="ja-jp" Name="HayCortanaLaunchVim">
    <CommandPrefix>ねぇ</CommandPrefix>
    <Example>ビムを起動して</Example>
    <Command Name="OpenVim">
      <Example>ビムを起動して</Example>
      <ListenFor>ビムを起動して[下さい]</ListenFor>
      <Feedback>ビムを起動しています</Feedback>
      <Navigate/>
    </Command>
  </CommandSet>
</VoiceCommands>

これで「コルタナさん、ねぇ Vim を起動して」と喋るだけで http://localhost:8888/vim にリクエストが飛び、サーバを経由して Vim が起動する。

サーバ起動しておかないといけないのが面倒臭い。ちなみにちゃんと作るならクライアントサーバ間でトークンか何かをやりとりする必要があるし、サーバも localhost からのアクセスに絞るべきです。

ソースは GitHub に置いておきます。


2016/04/11


Software Design 2016年5月号|技術評論社

第1特集 コード編集の高速化からGitHub連携まで Vim[実戦]投入 Part1 :Vimとの長い付き合いのはじめかた …… 氏久 達博 Part2 :Vimだからでき...


http://gihyo.jp/magazine/SD/archive/2016/201605

技術評論社様から「新人に響く Vim 特集が欲しい」とのご依頼頂き、色んな方にお声かけさせて頂きましたが結果として Vim 界隈でも超ドビムな人達が選べた事に安堵の思いです。

ujihisa さん

clojure の補完プラグイン neoclojure の作者。vim-users.jp でも多くの記事を執筆したコアな Vimmer です。

thinca さん

ご存じマンボウ Vim script マスターです。thinca さんの作る物は質が高く、リポジトリのスター数を見ても皆の期待度が高いのが良く分かります。

tyru さん

eskk.vimcaw.vim の作者。ここぞという時の製造力の高さと、熱いハートの持ち主。

rhysd さん

犬さん、linda_pp さん。electron で neovim のフロントエンド NyaoVim を作っちゃった人。僕の中では C++ の人。

そして僕

わりかし Vimmer だと言われがちです。

このコアな Vimmer が、新年度に入ってくるであろう新入社員に Vim への思いを熱くぶつける記事を書き上げました。この特集でしか書かれていない内容が多く含まれており、全体的に実戦的な記事に仕上がりました。技術的な話ももちろんですが昔話も含まれる(僕です)ので若い新入社員からオジサンまで色々な人達に楽しんで頂けるかと思います。

ソフトウェアデザイン 2016年 05 月号 [雑誌] ソフトウェアデザイン 2016年 05 月号 [雑誌]

技術評論社 / ¥ 1,534 (2016-04-18)
 
発送可能時間:在庫あり。



[D] Windowsはターミナルがダメだから使えないってのは過去の話?

基本的にはいい感じに見えますが、いくつか問題は発覚してます。

http://blog.drikin.com/2015/01/windows-2.html
僕は Cygwin よりも msys2 が好きです

理由は最後の方に書きます。

もちろん msys2 を POSIX 環境としても使いますが、一番の目的は

cmd.exe から Windows ネイティブなアプリケーションを使いたいから

例えば ag (the silver searcher) を Windows から使いたいなら、Windows native でビルドすべきだと考えます。

Cygwin がなぜ嫌われ始めたのかを皆もう一度考えるべきだと思います。Cygwin が不安定な理由は Windows 上に無理くり POSIX をエミュレーションしているのが原因。これに尽きます。Windows は UNIX では無いのだから Windows には出来ない事はたくさんあるのです。

drikin さんの記事でも書かれていますが、Cygwin (および msys2) には fork があり、UNIX を模擬する事が出来ます。しかし Windows で fork を実現する為にオーバーワークとも言える事をやっています。fork は現在実行しているプロセスのコピーを作るシステムコールです。Cygwin の fork はこれを実現する為に、POSIX で提供されるファイルハンドルの管理やメモリのアロケーションを全て Cygwin 配下で行い、fork の実行と共にそのメモリを複製し、スタックを疑似的に再現した上でジャンプ命令を実行しています。なのでたかがmallocなのに非常に実行が遅くなります。また完全にエミュレーション出来ていません。例えばコピーオンライトは動きません。つまり完全な fork ではありません。

例えば Windows はファイルハンドルを握られた状態だとそのファイルを削除する事は出来ません(FILE_SHARE_DELETE を指定して CreateFile した場合は別です)。よく UNIX 向けのソフトウェアでは後処理としてテンポラリディレクトリを削除するといった物がありますが、そのディレクトリ内のファイルハンドルを握ったままだとディレクトリの削除に失敗します。

もう一つ。Windows にはファイルの実行権限(許可という意味ではなく)がありません。ファイルが実行できるかどうかは拡張子で決まります。Cygwin ではコマンドを実行すると、手当たり次第にパスを検索し、拡張子が無ければファイルの中に shebang 行が無いかを調べて実行できそうならそこで初めてファイルが実行されます。この様にいろいろな違いがあるのです。必然的に Cygwin パッチが必要となります。

Cygwin が出始めた当初は、各 UNIX 向け OSS メンテナも「Windows ユーザにも自分達が作ったプロダクトを使って貰える」と喜び、こぞって Cygwin 特有のパッチを取り込んでくれました。しかし実際にリリースしてみると問題が出るわ出るわ。そしてなぜその問題が発生したのかは、パッチを書いた人じゃないと分からないという悪循環。最悪のケースだと誰も原因が分からない、もしくは解決の方法が見つからないと放置されました。

次第に Cygwin は OSS プロダクトから嫌われ始め、Cygwin 向けパッチを送ると「また Cygwin か」と言われる事もチラホラありました。

そうなんです。Windows 上で完全な POSIX エミュレーションは VM 等を使わない限り無理なのです。

Big Sky :: Windowsへの移植も視野にいれたプログラムを書くなら読んでおいて欲しい事

絶対パスの先頭に / が来る事を期待してはいけない しかしながら絶対パスの先頭にドライブレターが来る事を期待してはいけない UNCパスのホスト名やシェア名はディレクトリではないのでファイルシステムAP...

http://mattn.kaoriya.net/software/20120507131015.htm

以前書いた記事を読んで貰えると Windows で UNIX を模倣するのは到底ムリゲーなのがよく分かりますね。先日 Microsoft から発表された Bash On Windows もおそらくこういった問題があると思います。(追記: 例えばファイルシステムが NTFS 上なので ext4 と同等のファイル属性が持てません。)

だからといって有益なソフトウェアを Windows 上で使えないのは勿体ない。幾らか機能制限があったとしても使いたいものです。なので僕は Windows 向けのパッチを書くときは殆ど、Windows ネイティブな環境で動作させる為のパッチしか書きません。(中には msys2 専用の物もありますが)

例えば上記で説明した ag。ag を Windows ポートしたのは僕ですが、Cygwin のシェルから使わせる為のパッチではなく cmd.exe から使わせる為のパッチとして pull-request しました。

Porting to win32 by mattn · Pull Request #158 · ggreer/the_silver_searcher · GitHub
https://github.com/ggreer/the_silver_searcher/pull/158

他の物を見ても、おおよそ Windows ネイティブ環境の為の pull-request です。

完全に UNIX 環境じゃないと実行できない物は、VM を使えばいいんです。無理して Cygwin を使う必要は無いんです。そして Windows ユーザならば cmd.exe を使うべきなんです。(ここちょっと宗教くさいですが)

しかし cmd.exe で生きるには多くの苦行が待ち伏せています。実は Windows は環境変数に指定できる文字列の長さが4096文字しか設定できません。これはシステム環境変数もユーザ環境変数も足した状態の長さになります。通常インストールした後の状態だと600文字から700文字くらいなのですが、必要な物を色々インストールすると勝手に PATH 環境変数が足されて行きます。気付いたら2000文字や3000文字になっているなんてことはザラです。その上で各種コンパイラやスクリプト言語の処理系をインストールするとどうでしょう。4096文字なんて意外と一瞬で使い切ってしまいます。ただでさえ、Windows は UNIX の様に実行モジュールを一か所に集める習慣が無いのですから、膨れ上がって当然です。さらには mysql のライブラリをリンクさせる為に mysql-config へのパスを通す必要がある、であったりそのパスを通したおかげで zlib1.dll のシンボルの異なるバージョンの dll に先にパスが通ってしまい、他のツール群が動かなくなったなんて事、日常茶飯事です。時には気付かず4096文字を超えてしまっていて、いくらインストールし直しても動かない、であったり誤動作するなんて事もあります。

なので僕は基本的に Windows 上では不必要に PATH を通しません。例えば Java の開発をする場合、ant、maven、その他の開発に必要なツール群はオフィシャルサイトからダウンロードし、特定のフォルダに解凍した上で PATH を通して使っておられると思いますが、僕は Windows のシステム設定で PATH を通しません。

僕は基本的に、そのツールを使いたい時に初めて PATH を通します。そしてそれ専用のバッチファイルを用意しています。例えばコマンドラインから groovy を使いたい場合、groovy のオフィシャルサイトからダウンロードして解凍したディレクトリには PATH は通さず、以下のバッチファイルを書きます。

@echo off
set PATH=c:\Program Files\groovy-2.4.5\bin;%PATH%

コマンドインから groovy を使った開発を行いたい場合は、まずこのバッチファイル(groovyenv.bat という名前にしています)を実行します。その後で、groovy コマンドを使ったスクリプトを書いたり実行したりします。手間と思うかもしれませんが、これが Windows の誤動作を防ぐ一番簡単な方法なのです。システムのプロパティから設定する環境変数 PATH は、自分が思っている以上に他のプログラムに影響を与えてしまいます。先に述べた様に zlib1.dll などは色々なプロダクトで参照されており、dll に含まれるシンボルが1つ違っただけで Windows では実行時エラーが起きてしまいます。

Fuck DLL Hells

僕が上記で Cygwin よりも msys2 が好きだと言ったのは、msys2 で提供される mingw64(ネイティブ) 環境は、実行モジュールが1か所 C:\msys64\mingw64\bin に置かれており、この環境変数の問題も起きえないという所にあります。さらに適度な頻度でパッケージも更新されるので使っていても安心できるという点もあります。

どうしても groovyenv.bat の様なバッチファイルを叩きたくない場合の解決方法をご紹介しておきます。

それは bash でいう所の .bashrc を書く事です。Windows の cmd.exe には、起動時に自動実行されるコマンドを設定する為のレジストリが存在します。

レジストリエディタを実行し、以下のキーを開きます。

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Command Processor
そこに「AutoRun」という名前の文字列値を作り、以下の値を設定します。
%USERPROFILE%\init.cmd
init.cmd

そしてお好きなテキストエディタで %USERPROFILE% (僕だと C:\Users\mattn)に init.cmd というファイルを作り、以下の様に書きます。

@echo off

doskey ls=ls --color=auto --show-control-chars -N $*
doskey grep=grep --color=auto $*
doskey rm=rm -i $*
doskey cp=cp -i $*
doskey mv=mv -i $*
doskey vi=vim $*
doskey find=c:/msys64/usr/bin/find.exe $*
doskey ag=ag --nocolor $*

if "%CMD_INIT_SCRIPT_LOADED%" neq "" goto :eof
set CMD_INIT_SCRIPT_LOADED=1

set GIT_EDITOR=c:/msys64/usr/bin/vim.exe
set SBCL_HOME=C:\Program Files\Steel Bank Common Lisp\1.3.3
set PATH=%SBCL_HOME%;%PATH%;c:/msys64/usr/local/bin;c:\dev\instantclient_12_1
set ORACLE_HOME=c:\dev\instantclient_12_1

cls

ここで注意して欲しいのは、CMD_INIT_SCRIPT_LOADED という環境変数を使ってガードしている部分です。cmd.exe は cmd.exe から start コマンドを使って新しい cmd.exe を起動した場合、環境変数は引き継がれるのですが doskey は引き継がれません。なので doskey のみ毎回実行される必要があるのです。また doskey を使う事で、cp や rm 等の上書き削除確認や、無理する事無く find の動作を UNIX 風に書き換える事が出来るのです。ls で日本語も出ますよ。しかもシステムのプロパティから PATH を書き換えずに実現しているので、他のプログラムに与える影響が小さく、かつ設定したレジストリの項目がユーザ専用なので同じ PC を使う他のユーザにも影響を与えません。

僕も始めは「レジストリを弄るのか...」と思っていましたが、これが一番 dll 問題を回避できているし、使い勝手も良いのです。

ちなみに msys2 のシェルも使うと書きましたが、その場合は逆に native 側への alias を作っています。

alias ag='/c/msys64/mingw64/bin/ag.exe'
alias vagrant='winpty /c/dev/vagrant/bin/vagrant'

winpty を使えばコンソールネイティブな物も実行できます。

Windows のコマンドラインでうまく設定できずに悩んでいる方、一度この方法を試してみられてはどうでしょうか?

追記: 幾らか意図が伝わっていない部分があるので説明を足すと、僕は Cygwin がサブシステムを使わない、DLL で実装されたアプリケーションである事を前提に話していて、もし Cygwin が POSIX サブシステムで実装されていたら違う意見を持っていたと思います。言いたいのはサブシステムを使わないアプリケーションである以上、完全な UNIX には慣れないのだから「期待してはいけない」という事です。