@@ -137,6 +137,16 @@ fn calculateScreenColumn(line: []const u8, start_byte: usize, end_byte: usize, i
137137 return screen_col ;
138138}
139139
140+ /// 行の本文部分(行番号の後)にANSIグレーコード(\x1b[90m)が含まれているかチェック
141+ /// コメントハイライトの有無を判定するために使用
142+ fn hasAnsiGray (line : []const u8 , content_start : usize ) bool {
143+ // 行番号の後ろの部分のみチェック
144+ if (content_start >= line .len ) return false ;
145+ const content = line [content_start .. ];
146+ // グレーのANSIコード \x1b[90m を探す
147+ return std .mem .indexOf (u8 , content , "\x1b [90m" ) != null ;
148+ }
149+
140150pub const View = struct {
141151 buffer : * Buffer ,
142152 top_line : usize ,
@@ -691,84 +701,97 @@ pub const View = struct {
691701 }
692702 }
693703
694- // 差分を検出
695- const min_len = @min (old_line .len , new_line .len );
696- var diff_start : ? usize = null ;
697- var diff_end : usize = 0 ;
704+ // 行番号の表示幅を取得
705+ const line_num_display_width = self .getLineNumberWidth ();
698706
699- // 前方から差分を探す
700- for (0.. min_len ) | i | {
701- if (old_line [i ] != new_line [i ]) {
702- if (diff_start == null ) diff_start = i ;
703- diff_end = i + 1 ;
707+ // 行番号がある場合、行番号部分のバイト範囲を推定
708+ // 行番号フォーマット: "\x1b[90m N\x1b[m " (ANSIエスケープ付き)
709+ const line_num_byte_end = if (line_num_display_width > 0 ) blk : {
710+ // 行番号部分のバイト終端を探す(最初のリセットエスケープ \x1b[m の後のスペース2つ)
711+ if (std .mem .indexOf (u8 , new_line , "\x1b [m " )) | pos | {
712+ break :blk pos + 5 ; // "\x1b[m ".len
704713 }
705- }
706-
707- // 長さが違う場合は後ろも差分
708- if (old_line .len != new_line .len ) {
709- if (diff_start == null ) diff_start = min_len ;
710- diff_end = @max (old_line .len , new_line .len );
711- }
712-
713- if (diff_start ) | start_raw | {
714- // 行番号の表示幅を取得
715- const line_num_display_width = self .getLineNumberWidth ();
716-
717- // 行番号がある場合、行番号部分のバイト範囲を推定してスキップ
718- // 行番号フォーマット: "\x1b[90m N\x1b[m " (ANSIエスケープ付き)
719- // 行番号がない、または差分が行番号より後から始まる場合は部分更新が可能
720- const line_num_byte_end = if (line_num_display_width > 0 ) blk : {
721- // 行番号部分のバイト終端を探す(最初のリセットエスケープ \x1b[m の後のスペース2つ)
722- if (std .mem .indexOf (u8 , new_line , "\x1b [m " )) | pos | {
723- break :blk pos + 5 ; // "\x1b[m ".len
724- }
725- // フォールバック:行全体を再描画
726- break :blk 0 ;
727- } else 0 ;
728-
729- // 差分が行番号部分にかかっている場合は行全体を再描画
730- if (line_num_display_width > 0 and start_raw < line_num_byte_end ) {
731- // 行頭から全体を再描画
714+ // フォールバック:行全体を再描画
715+ break :blk 0 ;
716+ } else 0 ;
717+
718+ // シンタックスハイライト(ANSIエスケープ)の変化をチェック
719+ // コメント色のANSIコードがある場合は行全体を再描画(差分描画ではカラーが正しく適用されない)
720+ const old_has_gray = hasAnsiGray (old_line , line_num_byte_end );
721+ const new_has_gray = hasAnsiGray (new_line , line_num_byte_end );
722+ // グレーがある場合は行全体を再描画(差分描画だとカラーが途切れる問題を回避)
723+ if (old_has_gray or new_has_gray ) {
724+ // 内容が異なる場合のみ再描画
725+ if (! std .mem .eql (u8 , old_line , new_line )) {
732726 try term .moveCursor (abs_row , viewport_x );
733727 try term .write (new_line );
734- // 残りをスペースで埋める(CLEAR_LINEの代わり)
735728 try self .padToWidth (term , new_line , viewport_width );
736- } else if (line_num_display_width > 0 ) {
737- // 行番号があるが本文部分のみの差分
738- // UTF-8文字境界に調整
739- var start = start_raw ;
740- while (start > 0 and start < new_line .len and unicode .isUtf8Continuation (new_line [start ])) {
741- start -= 1 ;
729+ }
730+ } else {
731+ // 差分を検出
732+ const min_len = @min (old_line .len , new_line .len );
733+ var diff_start : ? usize = null ;
734+ var diff_end : usize = 0 ;
735+
736+ // 前方から差分を探す
737+ for (0.. min_len ) | i | {
738+ if (old_line [i ] != new_line [i ]) {
739+ if (diff_start == null ) diff_start = i ;
740+ diff_end = i + 1 ;
742741 }
742+ }
743743
744- // バイト位置から画面カラム位置を計算(行番号部分をスキップ)
745- const screen_col = calculateScreenColumn (new_line , line_num_byte_end , start , line_num_display_width );
746-
747- // 差分部分のみ描画
748- try term .moveCursor (abs_row , viewport_x + screen_col );
749- try term .write (new_line [start .. ]);
744+ // 長さが違う場合は後ろも差分
745+ if (old_line .len != new_line .len ) {
746+ if (diff_start == null ) diff_start = min_len ;
747+ diff_end = @max (old_line .len , new_line .len );
748+ }
750749
751- // 古い行の方が長い場合は残りをスペースで埋める
752- if (old_line .len > new_line .len ) {
750+ if (diff_start ) | start_raw | {
751+ // 差分が行番号部分にかかっている場合は行全体を再描画
752+ if (line_num_display_width > 0 and start_raw < line_num_byte_end ) {
753+ // 行頭から全体を再描画
754+ try term .moveCursor (abs_row , viewport_x );
755+ try term .write (new_line );
756+ // 残りをスペースで埋める(CLEAR_LINEの代わり)
753757 try self .padToWidth (term , new_line , viewport_width );
754- }
755- } else {
756- // UTF-8文字境界に調整(継続バイトの途中から始まらないように)
757- var start = start_raw ;
758- while (start > 0 and start < new_line .len and unicode .isUtf8Continuation (new_line [start ])) {
759- start -= 1 ;
760- }
758+ } else if ( line_num_display_width > 0 ) {
759+ // 行番号があるが本文部分のみの差分
760+ // UTF-8文字境界に調整
761+ var start = start_raw ;
762+ while (start > 0 and start < new_line .len and unicode .isUtf8Continuation (new_line [start ])) {
763+ start -= 1 ;
764+ }
761765
762- // バイト位置から画面カラム位置を計算(ANSIエスケープシーケンスをスキップ )
763- const screen_col = calculateScreenColumn (new_line , 0 , start , 0 );
766+ // バイト位置から画面カラム位置を計算(行番号部分をスキップ )
767+ const screen_col = calculateScreenColumn (new_line , line_num_byte_end , start , line_num_display_width );
764768
765- // 差分部分のみ描画(viewport_xオフセットを加算)
766- try term .moveCursor (abs_row , viewport_x + screen_col );
767- try term .write (new_line [start .. ]);
769+ // 差分部分のみ描画
770+ try term .moveCursor (abs_row , viewport_x + screen_col );
771+ try term .write (new_line [start .. ]);
768772
769- // 古い行の方が長い場合は残りをスペースで埋める
770- if (old_line .len > new_line .len ) {
771- try self .padToWidth (term , new_line , viewport_width );
773+ // 古い行の方が長い場合は残りをスペースで埋める
774+ if (old_line .len > new_line .len ) {
775+ try self .padToWidth (term , new_line , viewport_width );
776+ }
777+ } else {
778+ // UTF-8文字境界に調整(継続バイトの途中から始まらないように)
779+ var start = start_raw ;
780+ while (start > 0 and start < new_line .len and unicode .isUtf8Continuation (new_line [start ])) {
781+ start -= 1 ;
782+ }
783+
784+ // バイト位置から画面カラム位置を計算(ANSIエスケープシーケンスをスキップ)
785+ const screen_col = calculateScreenColumn (new_line , 0 , start , 0 );
786+
787+ // 差分部分のみ描画(viewport_xオフセットを加算)
788+ try term .moveCursor (abs_row , viewport_x + screen_col );
789+ try term .write (new_line [start .. ]);
790+
791+ // 古い行の方が長い場合は残りをスペースで埋める
792+ if (old_line .len > new_line .len ) {
793+ try self .padToWidth (term , new_line , viewport_width );
794+ }
772795 }
773796 }
774797 }
0 commit comments