Cmderを使ってWindowsのcmd.exeを快適にカッコよく使えるようにしてみました。

この写真で実行しているのはcmd.exeです。
cmd.exeでもCmder上で実行すると、ここまでオシャレになります。

Table of Contents

はじめに

経緯

今仕事ではWindows中心の開発をしています。
しかしWindowsはターミナルがイケていません… コマンドプロンプトを開くのは辛い。

Ubuntuではターミナル中心の快適な生活をしていました。
Windowsでも同様のことがしたい… それを目標に色々試しもしていました。

過去にチャレンジしたもの

『無理してcmd.exeを使う必要はあるのか?』と思われるかもしれません。
cmd.exeを脱却するために過去試したものを紹介します。

簡素な説明ですが、一度でも利用したことのある方なら伝わると思っています。

WSL

最近有名なWindows Subsystem for Linuxです。

Linux環境が提供されているのはありがたいことですが、Windows上のシステムと連携すると複雑になります。
今どちらのOSに対して作業をしているのか..を意識する必要があるため好きになれませんでした。

PowerShell

Windows公式でcmd.exeとも互換性ある…らしいですが色々思うように動かなかったのでやめました。
そこまで色々試していないので、単に私の勉強不足かもしれませんが..。

git bash

Gitをインストールすると標準で搭載されているアレです。
以前にgit bashを快適に使うための記事を書きました。

WSLよりはWindowsシステム寄りではありますが、動いているのはbashであるのでどちらつかずな部分があります。
例えばcd $(fzf)みたいなコマンドを実行できません。

Cmderとの出会い

Cmderについては次章で説明します。
試してみようと思ったのは以下の記事を目にしたからです。

決めては以下の2点です。

  • 記事の文書に共感を覚えた (勝手ながらシンパシーを感じました 😄)
  • 更新日時が最近

特に以下の点が心に刺さりました。

  • Powerlineも使える環境にする
  • カッコよくて見やすいターミナル、コンソールを目指す

前提条件

以下を知っている前提で進めます。

Cmderのインストール

公式サイトは以下です。

Chocolateyでインストールする場合は、C:\tools\Cmderにインストールされます。

$ choco install cmder

Powerlineの導入

デフォルトもそこそこオシャレですが、情報を増やしたいのでPowerlineを導入します。

Powerline対応フォントのインストール

Powerline対応フォントをインストールします。
私はSource Code Pro for Powerlineを使用しています。

フォントのインストール方法が分からない...
otfファイルをダウンロードして以下手順を参考にしてください。
フォントのインストール(Windows 10/8/7/Vista)

Cmder-powerline-promptの導入

CmderでPowerlineを有効にできるプロジェクトをCloneしてきます。

プロジェクトディレクトリ内のLuaファイルをCmderのconfigディレクトリ配下に移動します。

$ cp *.lua %cmder_root%\config\

設定をPowerline対応フォントに変更

フォントをPowerline対応フォントにします。

Boldは個人的な好みです。
Monospaceは日本語によるレイアウト崩れを防ぐために外しています。

コマンドの強化

cmd.exeやCmderに内包されるgit-for-windowsのコマンドでは不十分のため色々インストールします。
Scoopで以下をインストールしました。

インストールコマンド
$ scoop install ^
    sudo ^
    bat ^
    fd ^
    7zip ^
    fzf ^
    less ^
    ripgrep ^
    make
インストールしたけど消してしまったもの

以下のツールは見送りました。
Windows上ではexeファイル実行時のラグが大きく、我慢できなかったからです。

  • diff-so-fancy

Cmderの設定変更

代表的な設定をいくつか晒します。

背景の設定

記事の冒頭にあるスクリーンショットで移っていたフクロウの画像を背景に設定しています。

フクロウの画像はWallpaperCaveのものを使わせて頂きました。

cmd.exe直後のディレクトリをホームディレクトリに

起動直後は%USERPROFILE%にいるのが便利だと思うので変更します。

赤枠下の部分にあるStartup dir...ボタンを押してホームディレクトリを選ぶと、自動で入力されます。

なお、Cmderでは%home%もホームディレクトリとして認識されます。

プロンプトの改善

デフォルトのMonokaiテーマ + Powerlineだけでもオシャレですが更に格好良くしましょう。

本節で紹介されているソースコードについて
ここから紹介のあるコードおよびdiffは、2018/11/18時点のものです。
Cmder-powerline-promptプロジェクトが更新された場合、正常に動作することを保証しません。

プロンプトのλを$にする

λだとLinuxユーザには馴染みが無く、半角文字でないことが仇となることがあるため変更します。

%cmder_root%\config\powerline_core.luaを以下のように変更しましょう。

  if not plc_prompt_lambSymbol then
-     plc_prompt_lambSymbol = "λ"
+     plc_prompt_lambSymbol = "$"
  end

本記事執筆時は115行目でした。

\vendor\clink.lua は変更しなくてもいいの...?

変更の必要はありません。
Google検索で調べるとclink.luaを変更せよという記事が沢山出てきますが、それらはPowerlineを導入していない場合です。

Cmder-powerline-promptを導入している場合、プロンプトの設定はpowerline_core.luaで上書きされるためclink.luaを変更しても何も変わりません。

コマンドをプロンプト1行目に入力する

デフォルトだとコマンド入力はプロンプトに情報が表示された次の行(2行目)になります。
これをLinuxやコマンドプロンプトのように1行目で入力できるようにします。

%cmder_root%\config\powerline_core.luaを以下のように変更しましょう。

  -- Symbols
- newLineSymbol = "\n"
+ newLineSymbol = ""

本記事執筆時は100行目付近でした。

newLineSymbolを空にするのは違和感ありますが、挙動は意図したとおりになったため目を瞑ります。

Gitリポジトリの情報を更に詳しく表示する

2018/11/27: より新しく優れた情報が展開されています

デフォルトだとブランチ名、変更や競合の有無が表示されます。
追加で以下の情報も表示できるようにしてみました。

  • 追加/削除/変更の件数
  • リモートリポジトリとの差 (ahead/behind)

改修後のイメージです。

%cmder_root%\config\powerline_git.luaにいくつか改修を加えます。

色設定の追加/変更

aheadとbehindを追加して、全体の色設定を微調整します。

powerline_git.luaのsegmentColors
local segmentColors = {
    clean = {
        fill = colorGreen,
        text = colorWhite
    },
    dirty = {
        fill = colorRed,
        text = colorWhite
    },
    conflict = {
        fill = colorYellow,
        text = colorWhite
    },
    ahead = {
        fill = colorBlue,
        text = colorWhite
    },
    behind = {
        fill = colorMagenta,
        text = colorWhite
    }
}

ステータス取得関数の変更

追加、編集、削除、管理対象外のファイル数を取得できるように変更します。

powerline_git.luaのget_git_status()
function get_git_status()
    local file = io.popen("git --no-optional-locks status --porcelain | awk '{print $1\"\\n\"}' | sort | uniq -c 2>nul")
    local add, modify, delete, unknown = 0, 0, 0, 0
    for line in file:lines() do
        num, kind = string.match(line, ".+(%d+) (.+)")
        if kind == "A" then
          add = num
        elseif kind == "M" then
          modify = num
        elseif kind == "D" then
          delete = num
        elseif kind == "??" then
          unknown = num
        end
    end
    file:close()

    return add, modify, delete, unknown
end
Stagingエリアに追加済かの判定について
今は実装していませんが、必要であれば近い内に対応を検討しています。

リモートリポジトリとの差分を取得する関数の作成

新しくgit_ahead_behind_module関数を作成します。

powerline_git.luaのgit_ahead_behind_module()
function git_ahead_behind_module()
    local file = io.popen("git rev-list --count --left-right @{upstream}...HEAD 2>nul")

    for line in file:lines() do
        ahead, behind = string.match(line, "(%d+).+(%d+)")
    end
    file:close()

    return ahead, behind
end

各種情報をプロンプトに表示する

上記を利用してinit関数を編集します。

powerline_git.luaのinit()
local function init()
    segment.isNeeded = get_git_dir()
    if segment.isNeeded then
        -- if we're inside of git repo then try to detect current branch
        local branch = get_git_branch(git_dir)
        if branch then
            -- Has branch => therefore it is a git folder, now figure out status
            -- local gitStatus = get_git_status()

            local add, modify, delete, unknown = get_git_status()
            local ahead, behind = git_ahead_behind_module()
            local gitConflict = get_git_conflict()
            segment.text = " "..plc_git_branchSymbol.." "..branch.." "

            segment.textColor = segmentColors.clean.text
            segment.fillColor = segmentColors.clean.fill

            if gitConflict then
                segment.textColor = segmentColors.conflict.text
                segment.fillColor = segmentColors.conflict.fill
                if plc_git_conflictSymbol then
                    segment.text = segment.text..plc_git_conflictSymbol
                end 
                return
            end 

            if add ~= 0 then
                segment.textColor = segmentColors.dirty.text
                segment.fillColor = segmentColors.dirty.fill
                segment.text = segment.text.."+"..add.." "
            end
            if modify ~= 0 then
                segment.textColor = segmentColors.dirty.text
                segment.fillColor = segmentColors.dirty.fill
                segment.text = segment.text.."*"..modify.." "
            end
            if delete ~= 0 then
                segment.textColor = segmentColors.dirty.text
                segment.fillColor = segmentColors.dirty.fill
                segment.text = segment.text.."-"..delete.." "
            end
            if unknown ~= 0 then
                segment.textColor = segmentColors.dirty.text
                segment.fillColor = segmentColors.dirty.fill
                segment.text = segment.text.."?"..unknown.." "
            end
            if ahead ~= "0" then
                segment.textColor = segmentColors.ahead.text
                segment.fillColor = segmentColors.ahead.fill
                segment.text = segment.text.."↓ "..ahead.." "
            end
            if behind ~= "0" then
                segment.textColor = segmentColors.behind.text
                segment.fillColor = segmentColors.behind.fill
                segment.text = segment.text.."↑ "..behind.." "
            end

        end
    end
end

コードが醜いですって? Lua書いたのは初めてなので勘弁してください 🙇‍♂️

リポジトリディレクトリでのもっさり感を軽減する

普通のディレクトリではサクサクですが、Gitリポジトリ内ではプロンプトの表示がもっさりします。
調べたところ、私には不要なコマンドが毎回実行されていました。

%cmder_root%\vendor\clink.luaの一部をコメントアウトします。

  clink.prompt.register_filter(set_prompt_filter, 1)
- clink.prompt.register_filter(hg_prompt_filter, 50)
- clink.prompt.register_filter(git_prompt_filter, 50)
- clink.prompt.register_filter(svn_prompt_filter, 50)
+ -- clink.prompt.register_filter(hg_prompt_filter, 50)
+ -- clink.prompt.register_filter(git_prompt_filter, 50)
+ -- clink.prompt.register_filter(svn_prompt_filter, 50)
  clink.prompt.register_filter(percent_prompt_filter, 51)

hgはMercurial、svnはSVNですがどちらも使用していません。

以下のIssueが参考になりました。

マシン間の同期

Cmderはポータビリティがあるため、以下のディレクトリをGitやDropboxなどで同期しておけばOKです。

  • Cmder/config
  • Cmder/bin
clink.luaについて

clink.luavendor配下にあるため、上記2ディレクトリを設定しても同期されません。
そもそもvendor配下を同期すべきかという話にもなりますので、同期せずに初回だけ対応としています。

コメントアウトだけですので。

トラブルシューティング

ハマった事例をいくつか紹介します。

aliasしたコマンドに引数が渡らない場合

末尾に$*が付いていることを確認しましょう。
Linuxとは違い、そのままでは引数は渡されません。

  • 🆖 alias ll=ls -l
  • 🆗 alias ll=ls -l $*

aliasしたコマンドがPipeの先で上手く動かない

事象は把握しましたが関係する情報は見つけられていません。

  • alias sort="C:\tools\cmder\vendor\git-for-windows\usr\bin\sort.exe"とした場合
    • 🆖 ll | sort
    • 🆗 ll | C:\tools\cmder\vendor\git-for-windows\usr\bin\sort.exe

historyの結果をgrepできない

デフォルトで設定されているaliasが引数(Pipe)に対応していません。
以下のようにaliasを設定しましょう。

  • 🆖 history=cat "%CMDER_ROOT%\config\.history"
  • 🆗 history=cat "%CMDER_ROOT%\config\.history" $*

Vimに色が付かない

set termguicolors の設定を削除したら解消しました。
正確な原因は分かっていません..。

tigの表示がおかしい

マルチバイト文字の無いリポジトリなら set LANG=ISO-8859-1 した後にtig実行で解決します。
しかし、マルチバイト文字があると文字化けします..。

tig2.4.1で改修されていそうですが、git bashに同梱されているtigが2.4.0のため確認できていません。

bat出力結果の色がおかしい

git bashのless(MSYSのless)が使用されている場合、色付けに対応していないのが原因です。
Scoopでlessをインストールすると解消されます。

一部のKey Bindingが変更できない

これも情報源を見つけることができていません。
私はAutoHotKeyを使用しているので、変更できない設定はそちらで対応してしまいました。

findやsortのオプションが指定できない

findやsortはc:\Windows\System32\配下のコマンドが優先されます。
それらはLinuxのfindやsortとオプションが異なるため、指定できないように見えます。

aliasの設定もできなかったので、今のところスマートな解決方法は見つかっていません。

総括

Cmderを使ってWindowsのcmd.exeをオシャレに使う方法を紹介しました。

タイトルからも分かるように今回前編です。
後ほど執筆予定の後編では、自作コマンドを駆使してイケてるLinux環境に負けないカスタマイズの仕方を紹介します。

2018/11/17: 後編を追加しました