Skip to content

Commit 74bd6f7

Browse files
sinelawclaude
andcommitted
Refactor view rendering to use token-based ViewLineIterator pipeline
Replace the global FlattenedView approach with a per-line ViewLine structure from the new ViewLineIterator. This provides cleaner handling of line numbers by making rendering decisions based on token-level information rather than complex newline-based heuristics. Key changes: - Add view_pipeline.rs with ViewLineIterator, LineStart enum, and ViewLine - Refactor split_rendering.rs to use per-line char_mappings/styles/tab_starts - Fix line numbers for lines after injected headers using should_show_line_number() - Remove implicit line update to last_line_end to preserve cursor placement logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 2cc5255 commit 74bd6f7

File tree

5 files changed

+506
-274
lines changed

5 files changed

+506
-274
lines changed

plugins/git_blame.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,16 @@ globalThis.onViewTransformRequest = function(args: {
362362
viewport_end: number;
363363
tokens: ViewTokenWire[];
364364
}): void {
365+
editor.debug(`view_transform_request: buffer_id=${args.buffer_id}, blameState.bufferId=${blameState.bufferId}, isOpen=${blameState.isOpen}`);
366+
365367
// Only transform our blame buffer
366368
if (args.buffer_id !== blameState.bufferId || !blameState.isOpen) {
369+
editor.debug(`view_transform_request: skipping (buffer mismatch or not open)`);
367370
return;
368371
}
369372

373+
editor.debug(`view_transform_request: processing viewport ${args.viewport_start}-${args.viewport_end}, ${blameState.blocks.length} blocks`);
374+
370375
const transformed: ViewTokenWire[] = [];
371376
const processedBlocks = new Set<string>();
372377

@@ -376,23 +381,45 @@ globalThis.onViewTransformRequest = function(args: {
376381
// Check if block overlaps with viewport
377382
if (block.endByte > args.viewport_start && block.startByte < args.viewport_end) {
378383
blocksInViewport.push(block);
384+
editor.debug(`Block in viewport: hash=${block.shortHash}, startByte=${block.startByte}, endByte=${block.endByte}`);
379385
}
380386
}
387+
editor.debug(`Total blocks in viewport: ${blocksInViewport.length}, tokens: ${args.tokens.length}`);
381388

382389
// Process tokens and inject headers
390+
// We need to inject a header when:
391+
// 1. We encounter a token at the exact start of a block (byteOffset === block.startByte)
392+
// 2. OR the block starts before the viewport but extends into it (first visible token of that block)
393+
383394
let lastByteOffset: number | null = null;
384395

396+
// Debug: log first few tokens
397+
for (let i = 0; i < Math.min(5, args.tokens.length); i++) {
398+
const t = args.tokens[i];
399+
editor.debug(`Token[${i}]: source_offset=${t.source_offset}, kind=${JSON.stringify(t.kind)}`);
400+
}
401+
385402
for (const token of args.tokens) {
386403
const byteOffset = token.source_offset;
387404

388-
// Check if we're entering a new block
405+
// Check if we need to inject headers before this token
389406
if (byteOffset !== null && byteOffset !== lastByteOffset) {
390407
for (const block of blocksInViewport) {
391-
// Inject header at the start of each block
392-
if (byteOffset === block.startByte && !processedBlocks.has(block.hash + block.startByte)) {
393-
processedBlocks.add(block.hash + block.startByte);
394-
408+
const blockKey = block.hash + block.startByte;
409+
410+
// Inject header if:
411+
// 1. This token is at the exact start of the block, OR
412+
// 2. This token is the first visible token within this block
413+
// (block started before viewport, so header wasn't shown yet)
414+
const isBlockStart = byteOffset === block.startByte;
415+
const isFirstVisibleInBlock = byteOffset >= block.startByte &&
416+
byteOffset < block.endByte &&
417+
block.startByte < args.viewport_start;
418+
419+
if ((isBlockStart || isFirstVisibleInBlock) && !processedBlocks.has(blockKey)) {
395420
const headerText = formatBlockHeader(block);
421+
editor.debug(`INJECT: block=${block.shortHash}, byteOffset=${byteOffset}, headerText="${headerText.substring(0, 40)}..."`);
422+
processedBlocks.add(blockKey);
396423

397424
// Add header token (source_offset: null = no line number)
398425
transformed.push({
@@ -427,14 +454,22 @@ globalThis.onViewTransformRequest = function(args: {
427454
}
428455

429456
// Submit the transformed view
430-
editor.submitViewTransform(
457+
editor.debug(`Submitting view transform: original=${args.tokens.length} tokens, transformed=${transformed.length} tokens`);
458+
// Log first 5 transformed tokens
459+
for (let i = 0; i < Math.min(5, transformed.length); i++) {
460+
const t = transformed[i];
461+
const kindStr = typeof t.kind === 'string' ? t.kind : `Text:"${(t.kind as {Text:string}).Text.substring(0, 30)}"`;
462+
editor.debug(`OUT[${i}]: source_offset=${t.source_offset}, kind=${kindStr}`);
463+
}
464+
const result = editor.submitViewTransform(
431465
args.buffer_id,
432466
args.split_id,
433467
args.viewport_start,
434468
args.viewport_end,
435469
transformed,
436470
null // no layout hints
437471
);
472+
editor.debug(`submitViewTransform result: ${result}`);
438473
};
439474

440475
// Register for the view transform hook

src/ui/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod split_rendering;
1515
pub mod status_bar;
1616
pub mod suggestions;
1717
pub mod tabs;
18+
pub mod view_pipeline;
1819

1920
// Re-export main types for convenience
2021
pub use file_explorer::FileExplorerRenderer;

0 commit comments

Comments
 (0)