@@ -291,7 +291,7 @@ pub const View = struct {
291291 }
292292
293293 // 行番号の表示幅を計算(999行まで固定、1000行以上で動的拡張)
294- pub fn getLineNumberWidth (self : * View ) usize {
294+ pub fn getLineNumberWidth (self : * const View ) usize {
295295 if (! config .Editor .SHOW_LINE_NUMBERS ) return 0 ;
296296
297297 const total_lines = self .buffer .lineCount ();
@@ -372,8 +372,13 @@ pub const View = struct {
372372 self .prev_top_line = self .top_line ;
373373 }
374374
375+ /// 再描画が必要かどうか
376+ pub fn needsRedraw (self : * const View ) bool {
377+ return self .needs_full_redraw or self .dirty_start != null ;
378+ }
379+
375380 /// 指定行より前の行をスキャンして、ブロックコメント内で開始するかを判定
376- /// キャッシュを使用してO(n)からO(1 )に最適化(top_lineが変わらない場合 )
381+ /// キャッシュを使用してO(n)からO(k )に最適化(kはキャッシュ位置からの差分行数 )
377382 fn computeBlockCommentState (self : * View , target_line : usize ) bool {
378383 // ブロックコメントがない言語なら常にfalse
379384 if (self .language .block_comment == null ) return false ;
@@ -385,13 +390,31 @@ pub const View = struct {
385390 }
386391 }
387392
388- // キャッシュミス: 再計算
389- var in_block = false ;
393+ // キャッシュからスタートできるか確認(キャッシュ行 <= target_line の場合のみ)
394+ var start_line : usize = 0 ;
395+ var in_block : bool = false ;
396+ if (self .cached_block_state ) | cached_state | {
397+ if (self .cached_block_top_line < target_line ) {
398+ // キャッシュ位置からスキャン開始
399+ start_line = self .cached_block_top_line ;
400+ in_block = cached_state ;
401+ }
402+ }
403+
404+ // start_lineからtarget_lineまでスキャン
390405 var iter = PieceIterator .init (self .buffer );
391406 var line_buf = std .ArrayList (u8 ){};
392407 defer line_buf .deinit (self .allocator );
393408
409+ // start_lineまでスキップ
394410 var current_line : usize = 0 ;
411+ while (current_line < start_line ) : (current_line += 1 ) {
412+ while (iter .next ()) | ch | {
413+ if (ch == '\n ' ) break ;
414+ }
415+ }
416+
417+ // start_lineからtarget_lineまでスキャン
395418 while (current_line < target_line ) : (current_line += 1 ) {
396419 // 行を読み取る
397420 line_buf .clearRetainingCapacity ();
@@ -515,14 +538,77 @@ pub const View = struct {
515538 // 行番号の表示幅を取得
516539 const line_num_display_width = self .getLineNumberWidth ();
517540
518- // 行番号がある場合は常に行全体を再描画
519- // (ANSIシーケンスの計算が複雑なため、シンプルな実装を優先)
520- if (line_num_display_width > 0 ) {
541+ // 行番号がある場合、行番号部分のバイト範囲を推定してスキップ
542+ // 行番号フォーマット: "\x1b[90m N\x1b[m " (ANSIエスケープ付き)
543+ // 行番号がない、または差分が行番号より後から始まる場合は部分更新が可能
544+ const line_num_byte_end = if (line_num_display_width > 0 ) blk : {
545+ // 行番号部分のバイト終端を探す(最初のリセットエスケープ \x1b[m の後のスペース2つ)
546+ if (std .mem .indexOf (u8 , new_line , "\x1b [m " )) | pos | {
547+ break :blk pos + 5 ; // "\x1b[m ".len
548+ }
549+ // フォールバック:行全体を再描画
550+ break :blk 0 ;
551+ } else 0 ;
552+
553+ // 差分が行番号部分にかかっている場合は行全体を再描画
554+ if (line_num_display_width > 0 and start_raw < line_num_byte_end ) {
521555 // 行頭から全体を再描画
522556 try term .moveCursor (abs_row , viewport_x );
523557 try term .write (new_line );
524558 // 残りをスペースで埋める(CLEAR_LINEの代わり)
525559 try self .padToWidth (term , new_line , viewport_width );
560+ } else if (line_num_display_width > 0 ) {
561+ // 行番号があるが本文部分のみの差分
562+ // 差分開始位置から画面カラム位置を計算
563+ var start = start_raw ;
564+ while (start > 0 and start < new_line .len and unicode .isUtf8Continuation (new_line [start ])) {
565+ start -= 1 ;
566+ }
567+
568+ // バイト位置から画面カラム位置を計算(行番号部分をスキップ)
569+ var screen_col : usize = 0 ;
570+ var b : usize = line_num_byte_end ;
571+ // 行番号分の表示幅を加算
572+ screen_col = line_num_display_width ;
573+
574+ while (b < start ) {
575+ const c = new_line [b ];
576+ // ANSIエスケープシーケンスをスキップ
577+ if (b + 1 < new_line .len and unicode .isAnsiEscapeStart (c , new_line [b + 1 ])) {
578+ b += 2 ;
579+ while (b < new_line .len and new_line [b ] != 'm' ) : (b += 1 ) {}
580+ if (b < new_line .len ) b += 1 ;
581+ continue ;
582+ }
583+
584+ if (unicode .isAsciiByte (c )) {
585+ b += 1 ;
586+ screen_col += 1 ;
587+ } else {
588+ const len = unicode .utf8SeqLen (c );
589+ if (b + len <= new_line .len ) {
590+ const cp = std .unicode .utf8Decode (new_line [b .. b + len ]) catch {
591+ b += 1 ;
592+ screen_col += 1 ;
593+ continue ;
594+ };
595+ screen_col += unicode .displayWidth (cp );
596+ b += len ;
597+ } else {
598+ b += 1 ;
599+ screen_col += 1 ;
600+ }
601+ }
602+ }
603+
604+ // 差分部分のみ描画
605+ try term .moveCursor (abs_row , viewport_x + screen_col );
606+ try term .write (new_line [start .. ]);
607+
608+ // 古い行の方が長い場合は残りをスペースで埋める
609+ if (old_line .len > new_line .len ) {
610+ try self .padToWidth (term , new_line , viewport_width );
611+ }
526612 } else {
527613 // UTF-8文字境界に調整(継続バイトの途中から始まらないように)
528614 var start = start_raw ;
@@ -875,7 +961,7 @@ pub const View = struct {
875961 }
876962
877963 /// アクティブウィンドウ用のカーソル位置を計算
878- pub fn getCursorScreenPosition (self : * View , viewport_x : usize , viewport_y : usize , viewport_width : usize ) struct { row : usize , col : usize } {
964+ pub fn getCursorScreenPosition (self : * const View , viewport_x : usize , viewport_y : usize , viewport_width : usize ) struct { row : usize , col : usize } {
879965 const line_num_width = self .getLineNumberWidth ();
880966 var screen_cursor_x = line_num_width + (if (self .cursor_x >= self .top_col ) self .cursor_x - self .top_col else 0 );
881967 // viewport幅を超えないようにクリップ(幅0の場合も考慮)
0 commit comments