2016 年につくったやつ一覧

f:id:rhysd:20161231221520p:plain

GitHub contributions グラフを続けるのも4年目に入っていて,来年も無理しない程度に続けて行こうかなぁと思ってます.今年は中盤あたりイマイチだったけど後半結構時間ができたので例年通りぐらいになりました.

今年もぼちぼち使っているツールのためのプラグインとか,ほしいアプリとかで新しく色々つくった気がするのでまとめてみました.

また,GitHub 検索 によると,今年は 130 件の pull request を出したようです.(自身のリポジトリ除く,public リポジトリのみ)

あと去年からつくっているもののメンテや継続的な開発も引き続きやっていて,NyaoVim には結構時間を割いたと思います.

前半にソフトウェアデザインに記事を寄稿したり,システムプログラミング会で発表させてもらったりしました.

来年もぼちぼち頑張っていきたいと思います.良いお年を.

フルスクラッチからさいきょうの Vim カラースキームをつくろう!

この記事はVim Advent Calendar 2016 その2の18日目の記事です.その1のほうにも記事を書いたのですが,こっちもまだ空いてたので埋めがてら書きます. その1のほうではかなり非実用的な記事を書いてしまったので,こっちでは比較的実用的なことを書きます.

'穏やかな'カラースキーム spring-night

突然ですが,ここ数日で sprint-night という新しいカラースキームをつくりました.

github.com

  • MacVim で Vim script のコードを開いた時

Vim script on gVim (MacVim)

  • 半透明ターミナル + Vim で Go のコードを開いた時 (24bit 色)

Go on Vim (24bit color)

紺色の背景に黄色系の文字色をメインにしたコントラスト少なめのカラースキームです. 一番特徴的なのはビジュアルモードでピンクを採用している点です(桜をイメージしています).黒背景なので 'night' です.

今までスプラトゥーン感のあるカラースキームや,いくつか試しにつくってみたカラースキームもありましたが,実用には至りませんでした. ですが今回は結構満足いく感じにできて,実際に今も使って記事を書いています.

今回はどうやって,どう考えてこのカラースキームをつくったかを紹介します.

カラースキームがほしくなったら…

まずカラースキームがほしいときは,本当に自作すべきか考えるところから始まります. 優先順位的には次のような感じです.

  1. 要求に合うカラースキームが無いか試しまくる
  2. 要求に一番近いカラースキームを fork して不満点を修正する
  3. つくる

まず 1. についてはカレンダー12日目の記事のように,調査していただいている方の記事がいくつかありますので,片っ端から試してみると良いと思います.

Vim の割と新しいおすすめ colorscheme たちを紹介する - Qiita

試す時はわざわざインストールすると面倒なので,手前味噌ですが,try-colorscheme.vim を使うとインストールせずにインメモリで Vim にカラースキームを適用できます.

github.com

ジャストフィットするものがあればそれで確定ですね.無くても,いくらか修正すれば満足行くものができるのであれば fork して直接修正してしまうのが良いです. 僕は wombat256 を fork したものをずっと使い続けていました.wombat はとても良いカラースキームなのですが,一部対応していないハイライトがあったり少し気に食わないハイライトがあったりした箇所を修正して使っていました.

使いたいものが全然無い場合,もしくは作りたいコンセプトのカラースキームがある場合はようやく 3. のつくるフェーズに入ります. 僕は前述の fork も気に入っているのですが,Vim8 で端末でも RGB の24bit色(true color)が使えるようになったこともあり,24bitカラーをフルに使ったもので自分好みのものがほしくなりました.

コンセプトを決める

まず最初にやるのはカラースキームのコンセプト決めです.

カラースキームをつくっていると,どうしても今いじっているところの色合いだけが気になってしまって全体として統一感がなくなってしまいます. それを防ぐために,迷った時などに決定を助けるコンセプトを持っておくのが良いです.

seoul256 を見てローコントラストなカラースキームが良いなと思っていたので,紺色背景 + 薄い黄色で春っぽいローコントラストなカラースキームをつくろうとなりました. なぜ紺色 + 黄色なのかは後述します.

コンセプトは明確なほど良く,一番良い例としてはバットマンゴッサム・シティをコンセプトにした vim-gotham があります.これぐらい強いコンセプトがあるとまず迷うことは無さそうです.

ブラウザで色合いを考えて最初のカラースキームを '生成' する

クラッチからといっても手で書く必要はありません.

ブラウザ上でインタラクティブに色をカラーピッカーで選びながらカラースキームがつくれるサービスがいくつかあります. 僕は Theme Creator というサービスを使って最初のプロトタイプを生成しました. Theme Creator では12箇所のハイライトをプレビューを見ながら決めます. 画像から色を抽出してみたり,Platton のような配色サービスを使ってみても良いと思います.

配色については,iceberg をつくられた cocopon さんの記事が参考になりました.

自作Vimカラースキーム「Iceberg」の配色戦略

また,コントラストが低いカラースキームでも見えにくくならないように,補色関係にある紺色と黄色をメインにすることにしました.

とりあえず気になった配色を手当たり次第に試してみます. こんな感じのがほしいなというのができあがったら,それをコードとして生成します.

カラースキーム開発環境を整える (ライブリロード)

生成されたコードをブラッシュアップしていく前にカラースキームの開発環境を簡単に整えておきます. 今回は 24bit 色および(おまけで)8bit 色にも対応するので,下記の3つの環境で色合いを見る必要があります.

  • 端末上の Vim (8bit)
  • 端末上の Vim (24bit)
  • gVim (MacVim)

毎回カラースキームを編集するたびに読み込み直すのは手間です.また,少なくとも自分がよく使う言語3つぐらいの色合いは確認しながら進めたいところです.

ウェブの開発にはライブリロードというものがあります.手元で編集したコードがブラウザに即時反映されるものです.カラースキーム開発でもこういうことがやれると楽になりそうです.

そこで,下記のような設定を .vimrc に(カラースキーム開発中だけでも)足します.

function! s:auto_update_colorscheme(...) abort
    if &ft !=# 'vim'
        echoerr 'Execute this command in colorscheme file buffer'
    endif
    setlocal autoread noswapfile
    let interval = a:0 > 0 ? a:1 : 3000
    let timer = timer_start(interval, {-> execute('checktime')}, {'repeat' : -1})
    autocmd! BufReadPost <buffer> source %
endfunction
command! -nargs=? AutoUpdateColorscheme call <SID>auto_update_colorscheme(<f-args>)

:AutoUpdateColorscheme というコマンドを定義しています. Vim には編集中のバッファが外部で変更された時に自動でリロードする autoread というオプションがあるので,それを有効にします. それだけだと autoread はすぐに変更を検知してくれないので,:checktime とタイマーを使って3秒ごとにポーリングします. これで作業用の Vim で編集するとプレビュー用の Vim が変更を検知し,バッファを更新,BufReadPost イベントが発火してカラースキームをリロードします. タイマーとラムダ式execute() を使っているので Vim8 専用です.

gVim と端末 Vim (8bit色環境) と端末 Vim (24bit色環境) を立ち上げ,ウィンドウを4分割ぐらいして自分のよく使う言語のコードを3つのウィンドウで開き,最後のウィンドウで編集予定のカラースキームファイルを開きます. 最後に :AutoUpdateColorscheme と打っておけば準備完了です.

生成されたコードを整える

それでは生成されたコードを整えていきます.

今回僕が使った Theme Creator ではこういう感じのコードが生成されていました.

" ハイライトの色の定義
let s:bg="#334152"
let s:fg="#FFFEEE"
let s:fg2="#ebeadb"
let s:fg3="#d6d5c8"
" ...

" 各ハイライト要素へのハイライトの適用
exe 'hi Normal guifg='s:fg' guibg='s:bg 
exe 'hi Cursor guifg='s:bg' guibg='s:fg 
exe 'hi Cursorline  guibg='s:bg2 
exe 'hi CursorColumn  guibg='s:bg2 
" ...

前述の cocopon さんの記事にも書かれていましたが,カラーパレットを作成して自分が使っている色数が分かるようにするのは良い習慣だと思います. ここでは前半の変数定義がそれに当たります.ただ,同じ色を複数箇所で使いまわしたりするので,現在の変数名はあまり良くありません. 各色ごとの名前に変えておきます.

let s:green      = ['#a9dd9d', 150]
let s:gold       = ['#fedf81', 222]
let s:darkgold   = ['#685800', 58]
let s:red        = ['#fd8489', 210]
let s:mildred    = ['#ab6560', 167]
let s:mikan      = ['#fb8965', 209]
" ...

今回は 8bit 色にも対応したので,配列にして第2引数に 8bit 色を指定しました.24bit 色のみで良い場合は右辺はそのままでも良いと思います. ちなみに 24bit 色 → 8bit 色の変換は CSApprox というプラグインで変換してから微調整するのが良いです. 256色パレットは一番暗い辺りの色合いが(灰色系を除いて) 00 からいきなり 5f に飛ぶので色々妥協します(飽くまでメインは 24bit 色です.)

また,後半については 24bit 色のみ対応であればそのままでも良いですが,8bit にも対応するので s:hi という関数をつくり,引数で色を表す配列と bold などの属性を渡せるようにしました.このあたりはソースコードを見ていただいたほうが良いかもしれません.

カラースキームの基本的なことは :help hi を見たり,他のカラースキームの実装を見たり,thinca さんの解説記事 があるので割愛します.前章で用意したライブリロード環境を使って実際に値を変えてみるとよく分かると思います.

足りないハイライト要素を足す

Vim の基本的なハイライト名は :help highlight-groups に各ハイライトが実際にどこで使われているかの説明付きで載っているので,手元のコードにまだないハイライトを足します. 配色はあまり考えすぎず,まずは網羅的にハイライトを定義することを優先します.

ハイライトを調節する

それではいよいよ一番大変で楽しいところです.

色合いをひたすら調整していきます.このあたりはもっと良い方法があれば良いのですが,特にそういう方法は知らなかったのでひたすら RGB 値を調節していきます.この時考えたことについて2点紹介します.

1. ハイライトをグルーピングする

すべてのハイライトに別の色を割り当ててしまうとカラフルになりすぎるので,ハイライトのグループに分けます.今回は大まかにプログラムの要素ごとに色を分けてみました.

  • 文字列
  • 文字列以外の定数値
  • 制御構文(ループ,分岐)
  • 定義(変数,関数,型,構造体)
  • 演算子(特に論理演算子

例えば制御構文であれば ifswitch といった条件分岐と forwhile などのループを含みます.

また,定数値はマジックナンバーとして目立たせるために強めの色を使います.特に論理値は真偽を間違えやすいので気をつけて配色します.今回は赤を採用しました.文字列を別にしているのは,文字列は数値などに比べ範囲が広いので強い色を使うと目がつらいためです.

このように,プログラムの各要素ごとに色を分けることで,離れてみた時やぱっと見た時にプログラムの構造をつかみやすくなるのではないかと考えてます.

2. コントラストを変えられるようにする

Tomorrow-Night や一部の有名カラースキームではコントラストを調整できるものが結構あります.

これはカラースキームの色合いがウィンドウの半透明・不透明やディスプレイの明るさなどにも影響されるからだと思います.実際,最初は不透明ウィンドウのみを見て配色していたのですが,後から半透明ウィンドウで見るとコメントが見づらすぎて調整し直したということがありました.

CI する

とりあえずこんなもんか というところまできたら,カラースキームも CI すべきだと思います. 単体テストを書くまではいきませんが,下記の観点でチェックしておくと良いと思います.

  • ハイライトが重複していないかチェック
  • Vim がハイライトを読み込んでエラーを出さないかチェック
  • linter によるスタイルチェック

1つめはハイライトを足したり引いたりしているとついついやってしまうミスです. また,2つめは最低限エラーが出ないことを smoke test 的にチェックしています. もっと色々なチェックを足すこともできると思いますが,費用対効果的にこんなものだと思います.

テストは Travis CI を使っていて,設定は下記のようになりました.linter は vint を使っています.

language: python
install:
    - pip install vim-vint
before_script:
    - uname -a
    - vim --version
script:
    - vint --warning $(git ls-files | grep -e '\.vim$' | grep -v vital) --color
    - vim -E -c 'set t_Co=256 rtp+=.' -c 'try | colorscheme spring-night | catch | cquit | endtry' -c 'quit'
    - ruby .ci/check_duplicate.rb

.ci/check_duplicate.rb はカラースキームのファイルを雑にパースしてハイライト名が重複していないかチェックするスクリプトです. script: の2行目で実行している vim コマンドは,カラースキームを読み込んで問題なければ 0 で exit し,エラーが出れば 1 で exit するワンライナーです.

ブラッシュアップする

ここまできたら実戦投入します.自分のデフォルトのカラースキームを作成したものにし,日々使って使用感を確かめます.気になる箇所が大量に出てくると思うのでちまちま直していきます.

1. 各ファイルタイプごとの最適化

ハイライトはファイルタイプの構文ハイライトに依存するため,ファイルタイプによっては見栄えがイマイチなことがあります. かといってそのたびにグローバルにハイライトを変更すると,ある箇所をなおしたら別のファイルタイプで色合いが壊れるといったことになりかねません.

そこで,各ファイルタイプごとの構文ハイライト名を調べて,そのハイライトを追加します. 例えば diff ファイルタイプの追加行の色合いは diffAdded,削除行の色合いは diffRemoved で決まるので,そこだけ色を変えたい場合はそれらを直接指定してハイライトを定義します.

ハイライト名を知るには :hi コマンドや :GetHighlightingGroup を定義して使って調べています.

2. 独自ハイライトを使うプラグイン対応

プラグインの中には独自のハイライトをもっているものがあります.

例えば vim-gitgutter は変更行の印(sign)のハイライトがデフォルトでかなり原色に近い赤・緑・黄色なので,今回のコントラストを抑えたカラースキームには合いませんでした. ちゃんと変更できるようになっているので,GitGutterAdd などのハイライトを追加することで対処できます.

3. ステータスラインプラグイン対応

僕はステータスラインに vim-airline を使っているので,airline 用テーマを作成しました.本体側のパレットにある色を極力使うようにすると統一感が出て良いと思います. また,ビジュアルモードの選択色とステータスラインの色を合わせるとさらに統一感が出て良かったです.

まとめ

今回,実用のために結構がんばってカラースキームをつくってみて,(まだまだ改善箇所はありますが)それなりに満足のいくものができました. 配色含めデザインは大変だなぁと感じつつ,なかなか楽しく開発できたと思います.なんといってもフルスクラッチで自分でつくったカラースキームでコードを書くのはテンションが上がって良いですね.

どうしても合うカラースキームが見つからない方や,楽しそうだから作ってみたいという方がカラースキームをつくる際の参考になれば幸いです.

Electron アプリをつくる時に便利なパッケージ

この記事は Electron アドベントカレンダー2016 の13日目の記事です.

本記事では,僕が Electron アプリをつくる上で便利だったり,ほしかったのでつくったりしたパッケージを7つほど紹介します.

  1. electron-about-window
  2. electron-dl
  3. electron-in-page-search
  4. electron-window-state
  5. menubar
  6. node-auto-launch
  7. electron-mocha

electron-about-window

electron-about-window は 'このアプリについて' ウィンドウを簡単にクロスプラットフォームにつくるためのパッケージです.下記のように関数を1つインポートして呼び出すだけで「このアプリについて」ウィンドウを生成することができます.(example

import openAboutWindow from 'about-window';
openAboutWindow({ icon_path: 'path/to/icon.png' });

アイコンファイルへのパスはどうしても必要になってしまうため手で指定する必要がありますが,その他のバージョンやデスクリプションは package.json から引っ張ってきてくれるので特に指定する必要はありません.

例えば Shiba だと下記のようなウィンドウが生成されます.

f:id:rhysd:20161211191411p:plain

ウィンドウのライフサイクルなどはすべてライブラリ側でハンドルされるので,特にライブラリユーザ側が気にする必要はありません.

TypeScript も対応済みです.

electron-dl

Electron アプリ中で何かのファイルをローカルにダウンロードしたい時,やり方は2通りあります.

  1. Chrome のネイティブなダウンロード機能を Electron が提供する API で利用する
  2. ダイアログのみネイティブな API (openSaveDialog) を使って,ダウンロードや保存は Node.js の API で頑張る

できれば 1. でやりたいところですが,Electron のダウンロード関連の API は色々なところに API が分散していて使いにくいです.例えばダウンロードを開始する downloadURLWebContents に,ダウンロードの再開は Session に,ダウンロード後の Dock の挙動(macOS)は app.dock にあったりします.また,Chrome のダウンロードはダイアログを出すかどうかの制御や一時中断・再開,など一通りのことができるので複雑です.これは Electron が ChromiumC++ API を割とそのまま JavaScriptAPI として見せているためです.

electron-dl はこれらの使いづらい API を裏に隠し,簡単にファイルのダウンロードを行えるようにしてくれます.

import {download} from 'electron-dl';

const win = new BrowserWindow();

const opts = {
    // 保存先ディレクトリ.ダイアログを開く場合はこのディレクトリが初期値になる
    directory: '/path/to/some/direcotry',

    // 'save as' ダイアログを出すかどうか.出さない場合は上記の 'directory' プロパティ
    // で指定する.何も指定しなければデフォルトのダウンロードディレクトリ(~/Downloads など)
    // が使われる.
    saveAs: true
};

// https://example.com/some/file をダウンロードする(opts は省略可)
download(win, 'https://example.com/some/file', opts)
    .then(dl => {
        console.log('Download successfully completed', dl.getSavePath());
    })
    .catch(err => {
        console.error('Download failed', err.message);
    });

これだけでダウンロードが中断された場合の面倒などを含めたファイルのダウンロードをすべて行ってくれます.また,macOS のダウンロード中の挙動(Dock アイコンへのプログレスバーの表示や完了時の Dock アイコンのバウンス)もやってくれます.なおダウンロード完了後に return される値(上記コードの dl)は DownloadItem インスタンス です.

electron-in-page-search

Electron には Chrome のページ内検索を実行する API があり,WebContents インスタンス.findInPage() メソッドや found-in-page イベントを組み合わせて使えます.ですが,検索窓が別のネイティブなウィンドウで実装されていること前提になっていたり,BrowserWindow<webview> で挙動が違ったりと色々落とし穴があります.これはダウンロード周りと同様に Electron が ChromiumC++ API を割とそのまま JavaScriptAPI として見せているためです.この辺は以前メモしたりしました.

Electron で Chrome のページ内検索機能を使う

そこで,それらの落とし穴や検索の状態を気にせずページ内検索をアプリに組み込むためのモジュールとしてつくられたのが electron-in-page-search です.

これは exampleスクリーンショットです.ボタンを押すと検索窓が現れ,そこに検索ワードを入れて Enter キーや 次/前 検索ボタンで検索できます.検索窓のスタイルは CSS などで細かく制御できます.

import searchInPage from 'electron-in-page-search';
import {remote} from 'electron';

const inPageSearch = searchInPage(remote.getCurrentWebContents());

document.getElementById('some-button').addEventListener('click', () => {
    inPageSearch.openSearchWindow();
});

前日の joe-re さんの記事にもあったように,Electron のネイティブな API のテストは色々面倒ですが,electron-in-page-search はすでに各プラットフォーム(OS X, Linux, Windows)でテスト済みなので安心できます.

TypeScript も対応済みです.

electron-window-state

アプリがウィンドウを生成する時に,毎回同じサイズ・位置で生成するのではなく以前ユーザが作成したウィンドウの位置を覚えておいてほしい事があります.ブラウザウィンドウのサイズはレンダラプロセス作成前に知る必要があるので,アプリディレクトリに JSON ファイルなどで保存しておく必要があります.

Electron アプリのウィンドウサイズ&ポジションを復元する

electron-window-state はその辺りをやってくれるパッケージです.

const windowStateKeeper = require('electron-window-state');
let win;

app.on('ready', () => {
  // Load the previous state with fallback to defaults
  const state = windowStateKeeper({
    defaultWidth: 1000,
    defaultHeight: 800
  });

  win = new BrowserWindow({
    x: state.x,
    y: state.y,
    width: state.width,
    height: state.height
  });

  state.manage(win);
});

ウィンドウの状態を保存・復帰したり,初期値をハンドルしてくれるので,気にする必要がなくなります.

menubar

menubar はメニューバーに常駐して,メニューアイテムがクリックされた時にミニウィンドウを表示するようなアプリをつくるための BrowserWindow の wrapper です.

menubar の様子

メニューアイテムの表示やウィンドウ位置の制御,表示のトグルなどを管理してくれるので,十数行でメニューウィンドウを扱うアプリが作れます.

import * as menubar from 'menubar';

const mb = menubar({
    index: '/path/to/index.html',
    icon: '/path/to/icon.png'
});

mb.on('ready', () => {
    // app.on('ready') 相当.アプリが立ち上がった後の処理
    mb.showWindow();
});
mb.on('after-create-window', () => {
    // ウィンドウが作成された後の処理
});

OS X, Linux, Windows に対応していますが,LinuxUbuntu などのメジャーなディストリビューション向けです.また,Windows でタスクバーの位置を左にするなどの設定をしている場合は対応していないようです.

node-auto-launch

上記のメニューバーに常駐するアプリなどを実装すると,OS 起動時にアプリも一緒に起動してほしいと思います.起動時に自動でアプリをスタートするにはそれぞれの OS ごとに設定する(OS X なら Launch Agent, Windows ならレジストリ登録など)必要がありますが,node-auto-launch ではこれらの処理を wrap して,クロスプラットフォームに OS 起動時のアプリ自動実行を行ってくれます.

const AutoLaunch = require('auto-launch');

const launcher = new AutoLaunch({
    name: 'Your App Name',
    path: '/path/to/YourApp',
});

launcher.isEnabled().then(enabled => {
    if (enabled) {
        // すでに自動起動アプリとして登録済み
        return;
    }
    launcher.enable();
});

GitHub の通知を流してくれるメニューバーアプリ gitify などが利用しています.

electron-mocha

Electron アプリの単体テストを書く時,テスト対象が Electron のモジュールに依存していると Node.js では単体テストが実行できません.

そこで,Electron 上で mocha単体テストを実行できるのが electron-mocha です.メインプロセス側でもレンダラプロセス側でもテストを実行できます.

Electron アプリだけでなく,DOM API に依存した単体テストなんかもレンダラプロセス側では実行できるので,DOM API に依存したテストを行いたい時一般にも使えます.

describe('', function () {
    before(function () {
        this.elem = document.querySelector('.target');
    });

    it('', function () {
        this.elem.click();
        assert.equal(this.elem.innerText, 'Clicked');
    });
});

のようなテストが特に jsdom などを使わずに普通に動きます.

まとめ

Electron 1.0 が出てからだいぶ時間が経ち,それなりにエコシステムも育ってきました.今回は私が実際に導入してみて便利だったものを紹介しましたが,awesome-electron にはさらにたくさんのツールやライブラリが掲載されています.