lessでシンタックスハイライトする

`less` でシンタックスハイライトする方法があると聞いたので、GNU source-highlight を使った方法を試してみました。
より詳しい記事として以下のような記事もあるので参考のこと。

- [Ubuntuでlessを使って構文をカラー表示する方法](http://www.nemotos.net/?p=1100)
- [ソースを綺麗に整形表示するGNU source-highlight - それマグで！](http://takuya-1st.hatenablog.jp/entry/20120623/1340476282)
- [lessコマンドを少し便利にしておく - Qiita](http://qiita.com/hatchinee/items/586fb1c4915e2bb5c03b)
- [漢(オトコ)のコンピュータ道: lessでソースコードに色をつける](http://nippondanji.blogspot.jp/2011/11/less.html)

これらの記事では環境変数を弄っているのに対して、本記事では `alias` を使用する方法を紹介します。

# GNU source-highlight のインストール

ほとんどの環境で `less` は最初から使用できるはずなのでインストールはスキップします。とは言うものの、もしかしたらバージョン違いで同じように設定できない恐れもあるので、その辺りは確認しておきます。

```bash
# cat /etc/centos-release
CentOS Linux release 7.2.1511 (Core)
# less --version
less 458 (POSIX regular expressions)
Copyright (C) 1984-2012 Mark Nudelman

less comes with NO WARRANTY, to the extent permitted by law.
For information about the terms of redistribution,
see the file named README in the less distribution.
Homepage: http://www.greenwoodsoftware.com/less
```

ではインストールします。root でない人は `sudo` してください。

```bash
# yum install source-highlight
略
```

インストールが完了したら大雑把な構成を把握します。

```bash
# rpm -q source-highlight
source-highlight-3.1.6-6.el7.x86_64
# rpm -ql source-highlight
/etc/bash_completion.d
/etc/bash_completion.d/source-highlight
/usr/bin/check-regexp
/usr/bin/cpp2html
/usr/bin/java2html
/usr/bin/source-highlight
/usr/bin/source-highlight-settings
/usr/bin/src-hilite-lesspipe.sh
/usr/lib64/libsource-highlight.so.4
/usr/lib64/libsource-highlight.so.4.0.0
/usr/share/doc/source-highlight-3.1.6
略（マニュアルっぽいhtmlファイルがいっぱい）
/usr/share/info/source-highlight-lib.info.gz
/usr/share/info/source-highlight.info.gz
/usr/share/man/man1/check-regexp.1.gz
/usr/share/man/man1/source-highlight-settings.1.gz
/usr/share/man/man1/source-highlight.1.gz
/usr/share/source-highlight
/usr/share/source-highlight/Hello.css
略（共通設定と思われるファイルがいっぱい）
```

バージョンは `3.1.6` がインストールされたようです。
`/etc/` の下は `bash` 関連のようなので一旦無視でいいでしょう。実行ファイルは `/usr/bin/` に入ったようです。その他はマニュアル類と、共通設定ファイルと思われるファイルがインストールされます。

# 使い方

先ほどインストールされた `/usr/bin/src-hilite-lesspipe.sh` を使います。ソースコードをこのスクリプトに与えると、[カラーシーケンス](http://qiita.com/hidai@github/items/1704bf2926ab8b157a4f)が付与されたテキストが出力されます。色の定義は拡張子に依存するようです（後述）。これを `-R` オプション付きの `less` にパイプします。

やってみます。まずコマンドが長くなるので `alias` を設定します。

```bash
# which hilite
/usr/bin/which: no hilite in (/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin)
# 
# alias hilite='/usr/bin/src-hilite-lesspipe.sh'
# alias less='less -R'
# alias | grep less
alias hilite='/usr/bin/src-hilite-lesspipe.sh'
alias less='less -R'
```

動作確認は以下のように。

```bash
# hilite hoge.sh | less
```

# カラースキームの調整

デフォルトのカラースキームに不満がある場合は、`/usr/share/source-highlight/` 配下のファイルを弄ります。
ごちゃごちゃしていますが、拡張子ごとに見ると以下のようなファイル構成です。

```bash
# ls /usr/share/source-highlight/ | sed -e 's/.*\.//' | sort | uniq -c
     46 css
      1 defaults
    100 lang
      2 map
     21 outlang
      6 style
```
 
`lang` は言語の構文定義ファイルのようです。`outlang` は出力方式を定義するファイルのようです。`map` は拡張子と構文定義ファイルの紐づけを行っている `lang.map` と、出力フォーマット名と `outlnag` ファイルを紐づけているらしい `outlang.map` の 2 つです。実際のカラースキームを定義しているのは、`css` か `style` のようです。`style.defaults` は `style` ファイル全体のデフォルト値だと思われます。

詳細はちょっとわからないので、`hilite` コマンドの中で何が行われているのか確認してみます。

```bash
# cat /usr/bin/src-hilite-lesspipe.sh
#! /bin/sh

for source in "$@"; do
    case $source in
        *ChangeLog|*changelog)
        source-highlight --failsafe -f esc --lang-def=changelog.lang --style-file=esc.style -i "$source" ;;
        *Makefile|*makefile)
        source-highlight --failsafe -f esc --lang-def=makefile.lang --style-file=esc.style -i "$source" ;;
        *) source-highlight --failsafe --infer-lang -f esc --style-file=esc.style -i "$source" ;;
    esac
done
```

思ったよりシンプルでした。`--style-file=esc.style` が怪しいですね。見てみましょう。

```bash
# cat /usr/share/source-highlight/esc.style
keyword blue b ;
type, classname darkgreen ;
string red ;
regexp orange ;
specialchar pink ;
comment cyan i ;
number purple ;
preproc darkblue b ;
symbol darkred ;
function black b;
cbracket red;
variable darkgreen ;

// line numbers
linenum yellow;

// other elements for ChangeLog and Log files
date blue ;
time darkblue ;
ip darkgreen ;
file darkblue ;
name darkgreen ;

// Internet related
url blue u;

// for diffs
oldfile orange;
newfile darkgreen;
difflines blue;

// for css
selector purple;
property blue;
value darkgreen i;
```

はい。お気づきでしょうか、この中に `function black b;` という項目があります。これは関数名を黒のボールドで表示せよという指定になるのですが、通常の端末は背景が黒であるため異様に見づらいです（というか見えませんでした）。

変えました。

```bash
# diff /usr/share/source-highlight/esc.style /usr/share/source-highlight/esc.style.20160625
10c10
< function yellow b;
---
> function black b;
```

# おわりに

各記事では `LESS` や `LESSOPEN` といった環境変数を変更しているのですが、この記事では環境変数に手を加えない方向でシンタックスハイライトできるようにしてみました。

なぜそのようなことをしたかというと、`LESSOPEN` には既に値が設定されていたからです（`LESS` は空でしたが）。

```bash
$ echo $LESSOPEN
|/usr/bin/lesspipe.sh %s
```

`LESSOPEN` に指定される値は[入力プリプロセス](https://linuxjm.osdn.jp/html/GNU_less/man1/less.1.html)と呼ばれるもので、`less` が実行される前に実行されるコマンドだそうです。

では、`/usr/bin/lesspipe.sh` は一体何をしているのかというと、

```bash
$ cat /usr/bin/lesspipe.sh
#!/bin/sh
#
# To use this filter with less, define LESSOPEN:
# export LESSOPEN="|/usr/bin/lesspipe.sh %s"
#
# The script should return zero if the output was valid and non-zero
# otherwise, so less could detect even a valid empty output
# (for example while uncompressing gzipped empty file).
# For backward-compatibility, this is not required by default. To turn
# this functionality there should be another vertical bar (|) straight
# after the first one in the LESSOPEN environment variable:
# export LESSOPEN="||/usr/bin/lesspipe.sh %s"

if [ ! -e "$1" ] ; then
        exit 1
fi

if [ -d "$1" ] ; then
        ls -alF -- "$1"
        exit $?
fi

exec 2>/dev/null

# Allow for user defined filters
if [ -x ~/.lessfilter ]; then
        ~/.lessfilter "$1"
        if [ $? -eq 0 ]; then
                exit 0
        fi
fi

case "$1" in
*.[1-9n].bz2|*.[1-9]x.bz2|*.man.bz2|*.[1-9n].[gx]z|*.[1-9]x.[gx]z|*.man.[gx]z|*.[1-9n].lzma|*.[1-9]x.lzma|*.man.lzma)
        case "$1" in
        *.gz)           DECOMPRESSOR="gzip -dc" ;;
        *.bz2)          DECOMPRESSOR="bzip2 -dc" ;;
        *.xz|*.lzma)    DECOMPRESSOR="xz -dc" ;;
        esac
        if [ -n "$DECOMPRESSOR" ] && $DECOMPRESSOR -- "$1" | file - | grep -q troff; then
                $DECOMPRESSOR -- "$1" | groff -Tascii -mandoc -
                exit $?
        fi ;;&
*.[1-9n]|*.[1-9]x|*.man)
        if file "$1" | grep -q troff; then
                man -l "$1" | cat -s
                exit $?
        fi ;;&
*.tar) tar tvvf "$1" ;;
*.tgz|*.tar.gz|*.tar.[zZ]) tar tzvvf "$1" ;;
*.tar.xz) tar Jtvvf "$1" ;;
*.xz|*.lzma) xz -dc -- "$1" ;;
*.tar.bz2|*.tbz2) bzip2 -dc -- "$1" | tar tvvf - ;;
*.[zZ]|*.gz) gzip -dc -- "$1" ;;
*.bz2) bzip2 -dc -- "$1" ;;
*.zip|*.jar|*.nbm) zipinfo -- "$1" ;;
*.rpm) rpm -qpivl --changelog -- "$1" ;;
*.cpi|*.cpio) cpio -itv < "$1" ;;
*.gpg) gpg -d "$1" ;;
*.gif|*.jpeg|*.jpg|*.pcd|*.png|*.tga|*.tiff|*.tif)
        if [ -x /usr/bin/identify ]; then
                identify "$1"
        elif [ -x /usr/bin/gm ]; then
                gm identify "$1"
        else
                echo "No identify available"
                echo "Install ImageMagick or GraphicsMagick to browse images"
                exit 1
        fi ;;
*)
        if [ -x /usr/bin/file ] && [ -x /usr/bin/iconv ] && [ -x /usr/bin/cut ]; then
                case `file -b "$1"` in
                *UTF-16*) conv='UTF-16' ;;
                *UTF-32*) conv='UTF-32' ;;
                esac
                if [ -n "$conv" ]; then
                        env=`echo $LANG | cut -d. -f2`
                        if [ -n "$env" -a "$conv" != "$env" ]; then
                                iconv -f $conv -t $env "$1"
                                exit $?
                        fi
                fi
        fi
        exit 1
esac
exit $?
```

拡張子によって実行するコマンドを振り分けています。つまりどういうことかというと、`less` は各種バイナリファイルを開く（ファイルのサマリを表示する）のにも使えてしまうということです。
折角の便利機能なので、使えなくしてしまうことは避けたいと思った結果、上記のような方法を選択しました。

というわけで我が家の `less` でもシンタックスハイライトができるようになりました。