Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlin.math.min
internal class ReviewDiffCanvasDrawing(context: Context) {
private val density = context.resources.displayMetrics.density
var theme: DiffTheme = DiffTheme.fallback("light")
private val wordDiffRangesByRowId = mutableMapOf<String, List<DiffWordDiffRange>>()

val backgroundPaint = Paint()
val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
Expand All @@ -41,6 +42,14 @@ internal class ReviewDiffCanvasDrawing(context: Context) {
uiPaint.typeface = Typeface.DEFAULT_BOLD
}

fun mergeWordDiffRangesByRowId(patch: Map<String, List<DiffWordDiffRange>>) {
wordDiffRangesByRowId.putAll(patch)
}

fun clearWordDiffRanges() {
wordDiffRangesByRowId.clear()
}

fun fileHeaderChevronRect(top: Int, bottom: Int, style: DiffStyle): RectF {
val centerY = (top + bottom) / 2f
val left = style.fileHeaderHorizontalPaddingPx
Expand Down Expand Up @@ -205,14 +214,15 @@ internal class ReviewDiffCanvasDrawing(context: Context) {
top: Int,
bottom: Int
) {
if (row.wordDiffRanges.isEmpty() || (row.change != "add" && row.change != "delete")) return
val ranges = wordDiffRangesByRowId[row.id] ?: row.wordDiffRanges
if (ranges.isEmpty() || (row.change != "add" && row.change != "delete")) return
val color = if (row.change == "add") theme.addBar else theme.deleteBar
backgroundPaint.color = withAlpha(color, 71)
val characterWidth = textPaint.measureText("M")
val fontHeight = textPaint.fontMetrics.run { descent - ascent }
val highlightHeight = max(4f * density, min(bottom - top - 4f * density, fontHeight))
val highlightTop = (top + bottom - highlightHeight) / 2f
row.wordDiffRanges.forEach { range ->
ranges.forEach { range ->
val left = codeX + range.start * characterWidth
val right = max(left + 2f * density, codeX + range.end * characterWidth)
canvas.drawRoundRect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ class T3ReviewDiffView(context: Context, appContext: AppContext) : ExpoView(cont
onDebug(
mapOf(
"message" to "visible-range",
"firstRowIndex" to first,
"lastRowIndex" to last,
"firstRowIndex" to (visibleRows.getOrNull(first)?.sourceIndex ?: first),
"lastRowIndex" to (visibleRows.getOrNull(last)?.sourceIndex ?: last),
),
)
emitVisibleFile(first)
Expand All @@ -78,13 +78,15 @@ class T3ReviewDiffView(context: Context, appContext: AppContext) : ExpoView(cont
if (tokensResetKey == value) return
tokensResetKey = value
canvasView.tokensByRowId = emptyMap()
canvasView.drawing.clearWordDiffRanges()
}

fun setContentResetKey(value: String) {
if (contentResetKey == value) return
contentResetKey = value
tokensDecodeGeneration += 1
canvasView.tokensByRowId = emptyMap()
canvasView.drawing.clearWordDiffRanges()
lastVisibleFileId = null
pendingInitialScroll = true
canvasView.setVerticalOffset(0)
Expand Down Expand Up @@ -167,18 +169,27 @@ class T3ReviewDiffView(context: Context, appContext: AppContext) : ExpoView(cont
}

fun setTokensPatchJson(value: String) {
val expectedContentResetKey = contentResetKey
payloadDecodeExecutor.execute {
try {
val payload = JSONObject(value)
val resetKey = payload.optString("resetKey")
val decodedTokens = parseTokensObject(
payload.optJSONObject("tokensByRowId") ?: JSONObject(),
)
val decodedWordDiffRanges = parseWordDiffRangesObject(
payload.optJSONObject("wordDiffRangesByRowId") ?: JSONObject(),
)
post {
if (expectedContentResetKey != contentResetKey) return@post
if (resetKey.isNotEmpty() && resetKey != tokensResetKey) return@post
if (decodedTokens.isNotEmpty()) {
canvasView.tokensByRowId = canvasView.tokensByRowId + decodedTokens
}
if (decodedWordDiffRanges.isNotEmpty()) {
canvasView.drawing.mergeWordDiffRangesByRowId(decodedWordDiffRanges)
canvasView.invalidate()
}
}
} catch (_: Exception) {
}
Expand Down Expand Up @@ -434,6 +445,8 @@ private data class HorizontalPanTarget(
)

internal data class DiffRow(
// JavaScript range requests use the full payload position, even when files are collapsed.
val sourceIndex: Int,
val kind: String,
val id: String,
val fileId: String,
Expand Down Expand Up @@ -646,7 +659,7 @@ internal data class DiffStyle(

private class DiffCanvasView(context: Context) : View(context) {
private val density = resources.displayMetrics.density
private val drawing = ReviewDiffCanvasDrawing(context)
val drawing = ReviewDiffCanvasDrawing(context)
private val backgroundPaint = drawing.backgroundPaint
private val borderPaint = drawing.borderPaint
private val textPaint = drawing.textPaint
Expand Down Expand Up @@ -1337,6 +1350,7 @@ private fun parseRows(value: String): List<DiffRow> = try {
List(array.length()) { index ->
val row = array.getJSONObject(index)
DiffRow(
sourceIndex = index,
kind = row.optString("kind"),
id = row.optString("id"),
fileId = row.optString("fileId"),
Expand Down Expand Up @@ -1371,6 +1385,17 @@ private fun parseWordDiffRanges(value: JSONArray): List<DiffWordDiffRange> = bui
}
}

private fun parseWordDiffRangesObject(
value: JSONObject
): Map<String, List<DiffWordDiffRange>> = buildMap {
val keys = value.keys()
while (keys.hasNext()) {
val rowId = keys.next()
val ranges = value.optJSONArray(rowId) ?: continue
put(rowId, parseWordDiffRanges(ranges))
}
}

private fun parseTokensObject(value: String): Map<String, List<DiffToken>> = try {
parseTokensObject(JSONObject(value))
} catch (_: Exception) {
Expand Down
27 changes: 24 additions & 3 deletions apps/mobile/modules/t3-review-diff/ios/T3ReviewDiffView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private struct ReviewDiffNativeTokenPatch: Decodable, Sendable {
let resetKey: String?
let chunkIndex: Int?
let tokensByRowId: [String: [ReviewDiffNativeToken]]?
let wordDiffRangesByRowId: [String: [ReviewDiffNativeWordDiffRange]]?
}

private struct ReviewDiffNativeThemePayload: Decodable {
Expand Down Expand Up @@ -520,11 +521,17 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate {
}

let tokensByRowId = patch.tokensByRowId ?? [:]
if tokensByRowId.isEmpty {
let wordDiffRangesByRowId = patch.wordDiffRangesByRowId ?? [:]
if tokensByRowId.isEmpty && wordDiffRangesByRowId.isEmpty {
return
}

self.contentView.mergeTokensByRowId(tokensByRowId)
if !tokensByRowId.isEmpty {
self.contentView.mergeTokensByRowId(tokensByRowId)
}
if !wordDiffRangesByRowId.isEmpty {
self.contentView.mergeWordDiffRangesByRowId(wordDiffRangesByRowId)
}
if let chunkIndex = patch.chunkIndex, chunkIndex < 5 || chunkIndex.isMultiple(of: 10) {
self.emitDebug("tokens-patch-decoded", [
"chunkIndex": chunkIndex,
Expand All @@ -549,6 +556,7 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate {

self.tokensResetKey = tokensResetKey
contentView.tokensByRowId = [:]
contentView.clearWordDiffRanges()
emitDebug("tokens-reset", [
"resetKey": tokensResetKey,
])
Expand All @@ -563,6 +571,7 @@ public final class T3ReviewDiffView: ExpoView, UIScrollViewDelegate {
rowsDecodeGeneration += 1
tokensDecodeGeneration += 1
contentView.tokensByRowId = [:]
contentView.clearWordDiffRanges()
rows = []
contentView.rows = []
hasAppliedInitialRowIndex = false
Expand Down Expand Up @@ -951,6 +960,18 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate {
setNeedsDisplayForVisibleBounds()
}

private var wordDiffRangesByRowId: [String: [ReviewDiffNativeWordDiffRange]] = [:]

func mergeWordDiffRangesByRowId(_ patch: [String: [ReviewDiffNativeWordDiffRange]]) {
wordDiffRangesByRowId.merge(patch) { _, next in next }
setNeedsDisplayForVisibleBounds()
}

func clearWordDiffRanges() {
wordDiffRangesByRowId.removeAll()
setNeedsDisplayForVisibleBounds()
}

var collapsedFileIds: Set<String> = [] {
didSet {
rebuildRowLayout()
Expand Down Expand Up @@ -2268,7 +2289,7 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate {
context: CGContext,
horizontalOffset: CGFloat
) {
guard let ranges = row.wordDiffRanges, !ranges.isEmpty else {
guard let ranges = wordDiffRangesByRowId[row.id] ?? row.wordDiffRanges, !ranges.isEmpty else {
return
}

Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@
"@pierre/trees": "1.0.0-beta.4",
"@types/react": "~19.2.0",
"@types/react-dom": "~19.2.3",
"@types/react-test-renderer": "19.1.0",
"babel-preset-expo": "~57.0.9",
"react-test-renderer": "19.2.3",
"tailwindcss": "^4.0.0",
"typescript": "catalog:"
},
Expand Down
Loading
Loading