git.iko.soy/opencode-android
on 8865a77 help sign in
131 lines 5.2 kB history raw
1package soy.iko.opencode.ui.components
3import androidx.compose.foundation.background
4import androidx.compose.foundation.horizontalScroll
5import androidx.compose.foundation.layout.Column
6import androidx.compose.foundation.layout.Row
7import androidx.compose.foundation.layout.fillMaxWidth
8import androidx.compose.foundation.layout.padding
9import androidx.compose.foundation.layout.width
10import androidx.compose.foundation.layout.widthIn
11import androidx.compose.foundation.rememberScrollState
12import androidx.compose.foundation.shape.RoundedCornerShape
13import androidx.compose.foundation.verticalScroll
14import androidx.compose.material3.MaterialTheme
15import androidx.compose.material3.Text
16import androidx.compose.runtime.Composable
17import androidx.compose.runtime.remember
18import androidx.compose.ui.Alignment
19import androidx.compose.ui.Modifier
20import androidx.compose.ui.draw.clip
21import androidx.compose.ui.graphics.Color
22import androidx.compose.ui.text.font.FontFamily
23import androidx.compose.ui.unit.dp
25/** A single line of a parsed unified diff. */
26sealed interface DiffLine {
27 val text: String
29 data class Hunk(override val text: String) : DiffLine // @@ ... @@
30 data class FileHeader(override val text: String) : DiffLine // --- / +++
31 data class Context(override val text: String) : DiffLine
32 data class Add(override val text: String) : DiffLine
33 data class Remove(override val text: String) : DiffLine
36/** Parse a unified diff string into typed [DiffLine]s. */
37fun parseDiff(diff: String): List<DiffLine> {
38 val result = mutableListOf<DiffLine>()
39 for (raw in diff.split("\n")) {
40 when {
41 raw.startsWith("@@") -> result.add(DiffLine.Hunk(raw))
42 raw.startsWith("---") || raw.startsWith("+++") -> result.add(DiffLine.FileHeader(raw))
43 raw.startsWith("+") -> result.add(DiffLine.Add(raw.removePrefix("+")))
44 raw.startsWith("-") -> result.add(DiffLine.Remove(raw.removePrefix("-")))
45 raw.startsWith(" ") -> result.add(DiffLine.Context(raw.removePrefix(" ")))
46 raw.startsWith("diff ") || raw.startsWith("index ") -> result.add(DiffLine.FileHeader(raw))
47 raw.isNotBlank() -> result.add(DiffLine.Context(raw))
48 }
49 }
50 return result
53/** Heuristic: does this string look like a unified diff? */
54fun looksLikeDiff(text: String): Boolean {
55 val lines = text.lineSequence()
56 var hunkCount = 0
57 var addCount = 0
58 var removeCount = 0
59 for (line in lines) {
60 when {
61 line.startsWith("@@") -> hunkCount++
62 line.startsWith("+") && !line.startsWith("+++") -> addCount++
63 line.startsWith("-") && !line.startsWith("---") -> removeCount++
64 }
65 if (hunkCount >= 1 && (addCount + removeCount) >= 1) return true
66 }
67 return false
70@Composable
71fun DiffView(diff: String, modifier: Modifier = Modifier) {
72 val lines = remember(diff) { parseDiff(diff) }
73 val addColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f)
74 val removeColor = MaterialTheme.colorScheme.error.copy(alpha = 0.15f)
75 val addText = MaterialTheme.colorScheme.primary
76 val removeText = MaterialTheme.colorScheme.error
78 Column(
79 modifier = modifier
80 .fillMaxWidth()
81 .padding(vertical = 4.dp)
82 .clip(RoundedCornerShape(8.dp))
83 .background(MaterialTheme.colorScheme.surfaceVariant)
84 .verticalScroll(rememberScrollState())
85 .horizontalScroll(rememberScrollState())
86 .padding(10.dp),
87 ) {
88 for (line in lines) {
89 when (line) {
90 is DiffLine.Hunk -> Text(
91 line.text,
92 style = MaterialTheme.typography.bodySmall,
93 fontFamily = FontFamily.Monospace,
94 color = MaterialTheme.colorScheme.tertiary,
95 modifier = Modifier.padding(vertical = 2.dp),
96 )
97 is DiffLine.FileHeader -> Text(
98 line.text,
99 style = MaterialTheme.typography.bodySmall,
100 fontFamily = FontFamily.Monospace,
101 color = MaterialTheme.colorScheme.onSurfaceVariant,
102 modifier = Modifier.padding(vertical = 1.dp),
103 )
104 is DiffLine.Add -> DiffRow(line.text, "+", addColor, addText)
105 is DiffLine.Remove -> DiffRow(line.text, "-", removeColor, removeText)
106 is DiffLine.Context -> DiffRow(line.text, " ", Color.Transparent, MaterialTheme.colorScheme.onSurface)
107 }
108 }
109 }
112@Composable
113private fun DiffRow(text: String, prefix: String, bg: Color, textColor: androidx.compose.ui.graphics.Color) {
114 Row(
115 modifier = Modifier.fillMaxWidth().background(bg),
116 ) {
117 Text(
118 prefix,
119 style = MaterialTheme.typography.bodySmall,
120 fontFamily = FontFamily.Monospace,
121 color = textColor,
122 modifier = Modifier.width(16.dp),
123 )
124 Text(
125 text,
126 style = MaterialTheme.typography.bodySmall,
127 fontFamily = FontFamily.Monospace,
128 color = textColor,
129 )
130 }