テキストファイルビューワ (2)
 
 
[ index | 前のドキュメント | 次のドキュメント | このドキュメントのソース ]
前のドキュメントでテキストファイルを読み込み表示できるビューワのコードを示した。だが、その画面はお世辞にも視認性に優れたものとは言えない。現代的なビューワは、コメント、キーワード、文字列、シングルクォート、HTMLタグ、<? ... ?>, <script>...</script> ブロックを認識し、指定色で表示できるようでなくてはならない。このような処理は「構文強調 (Syntax Highlight)」と呼ばれる。本稿以降では、構文強調表示機能の実装について述べることにする。
■ 行コメント
C++ では、行の "//" 以降はコメントとみなされる。コメント以降は別の色で表示すると、その部分がコメントであることが直ぐに認識できてよい。
そのためには、行描画の部分で "//" を発見した場合は、それ以前の文字列を描画し、それ以降をコメント色で描画するようにするとよい。
プログラムは以下のようになる。

bool isLineComment(cchar *ptr, int length)
{
    return length >= 2 && ptr[0] == '/' && ptr[1] == '/';
}

void CViewView::OnDraw(CDC* pDC)
{
    .....
        bool lineComment = false;
        bool foundLineComment = false;
        while( ptr < eolptr ) {
            int px = column * VW_FONT_WIDTH;
            if( *ptr == '\t' ) {        //  TAB の場合
                pDC->SetTextColor(RGB(0x80, 0x80, 0x80));   //  文字色はグレイ
                pDC->TextOut(px, py, ">");
                column = column - column % VW_TAB_WIDTH + VW_TAB_WIDTH;
                ptr += 1;
            } else {                    //  通常文字
                cchar *ptr0 = ptr;
                while( ptr < eolptr && *ptr != '\t' ) {
                    if( !lineComment && isLineComment(ptr, eolptr-ptr) ) {      //  非コメント状態で行コメント開始文字列を発見
                        foundLineComment = true;
                        break;
                    }
                    ptr += 1;
                }
                int len = ptr - ptr0;
                if( len > 0 ) {
                    pDC->SetTextColor(!lineComment ? RGB(0, 0, 0) : RGB(0, 0x80, 0));   //  文字色は黒 or 黒緑(行コメント以降)
                    pDC->TextOut(px, py, ptr0, len);
                    column += len;
                }
                if( foundLineComment )
                    lineComment = true;
            }
        }
    .....
}
行コメントをダークグリーン表示するようにした版の実行結果を ここ に置く。コメント部分が直ぐに認識されるので、ソースがとても見やすくなった。次はキーワードの色表示だ。
■ キーワード
"int", "if", "for", "#include" などの C/C++ キーワードを指定色で描画すると、視認性はさらに高まる。これを実現するには、行コメントの処理と同じ用に、テキスト中にキーワードが現れたかどうかをチェックしなくてはいけない。ただし、"//" の場合はテキストの任意の位置でチェックしたが、英単語が別の英単語の途中に現れる場合(例えば "motif" や "iffy" 中の "if")はキーワードとは認識しない方が良いと考えられるので、任意の位置でチェックするのでは都合がわるいと思われる。キーワードが英数字で始まっている場合は単語区切り個所でのみチェックし、キーワードが英数字で終わっている場合は、マッチした部分の次の文字が英数字でないことをチェックするというのがひとつの解決策である。
上記の方法でプログラム実装するために、英数字で始まるキーワードかどうかをチェックする isKeyWord() と、そうでない場合にチェックを行う isSBKeyWord() の2つ関数を実装することにした。リターン値でキーワードかどうかを返すとともに、最後の引数でキーワードバイト数を返すことにする。

bool isSBKeyWord(cchar *srcptr, int srclen, int &kwlen)
{
    .....
}

bool isKeyWord(cchar *srcptr, int srclen, int &kwlen)
{
    .....
}
これらを利用すると、プログラムは以下のように記述できる。

void CViewView::OnDraw(CDC* pDC)
{
    .....
        int kwlen = 0;          //  キーワードを発見した場合、そのバイト数
        while( ptr < eolptr ) {
            int px = column * VW_FONT_WIDTH;
            if( kwlen > 0 ) {               //  キーワード発見済みの場合
                pDC->SetTextColor(RGB(0x80, 0, 0)); //  文字色は黒赤
                pDC->TextOut(px, py, ptr, kwlen);
                column += kwlen;
                ptr += kwlen;
                kwlen = 0;
                csym = __iscsym(ptr[-1]) ? true : false;
            } else if( *ptr == '\t' ) {     //  TAB の場合
                pDC->SetTextColor(RGB(0xa0, 0xa0, 0xa0));   //  文字色はグレイ
                pDC->TextOut(px, py, ">");
                column = column - column % VW_TAB_WIDTH + VW_TAB_WIDTH;
                ptr += 1;
                csym = false;
            } else {                    //  通常文字
                cchar *ptr0 = ptr;
                while( ptr < eolptr && *ptr != '\t' ) {
                    if( !lineComment ) {        //  非コメント状態
                        if( __iscsym((uchar)*ptr) ) {
                            if( !csym && isKeyWord(ptr, eolptr-ptr, kwlen) )    //  単語区切り&キーワード発見
                                break;
                            csym = true;
                        } else {
                            if( isSBKeyWord(ptr, eolptr-ptr, kwlen) )           //  シンボルで始まるキーワード発見
                                break;
                            csym = false;
                        }
                        if( isLineComment(ptr, eolptr-ptr) ) {      //  非コメント状態で行コメント開始文字列を発見
                            foundLineComment = true;
                            break;
                        }
                    }
                    ptr += 1;
                }
                int len = ptr - ptr0;
                if( len > 0 ) {
                    pDC->SetTextColor(!lineComment ? RGB(0, 0, 0) : RGB(0, 0x80, 0));   //  文字色は黒 or 黒緑(行コメント以降)
                    pDC->TextOut(px, py, ptr0, len);
                    column += len;
                }
                if( foundLineComment )
                    lineComment = true;
            }
        }
    .....
}
プログラムの実行結果を ここ に示す。上のリストに比べ、ソースコードがとても見やすくなったのを実感してほしい。
[ index | 前のドキュメント | 次のドキュメント | このドキュメントのソース ]