Skip to content

Commit 372ba1a

Browse files
committed
Fixed diff highlight when wrapping text and it's combined with syntax highlighting
1 parent db06460 commit 372ba1a

File tree

4 files changed

+183
-30
lines changed

4 files changed

+183
-30
lines changed

src/main/kotlin/com/jetpackduba/gitnuro/extensions/StringExtensions.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ fun String.removeLineDelimiters(): String {
3636
return this
3737
.removeSuffix("\r\n")
3838
.removeSuffix("\n")
39+
}
3940

41+
fun String.replaceTabs(): String {
42+
return this.replace(
43+
"\t",
44+
" "
45+
)
4046
}
4147

4248
val String.lineDelimiter: String?

src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ import androidx.compose.ui.input.pointer.PointerEventType
2828
import androidx.compose.ui.input.pointer.onPointerEvent
2929
import androidx.compose.ui.res.loadImageBitmap
3030
import androidx.compose.ui.res.painterResource
31-
import androidx.compose.ui.text.AnnotatedString
32-
import androidx.compose.ui.text.SpanStyle
31+
import androidx.compose.ui.text.*
3332
import androidx.compose.ui.text.font.FontWeight
3433
import androidx.compose.ui.text.style.TextOverflow
3534
import androidx.compose.ui.unit.dp
@@ -1183,36 +1182,35 @@ fun DiffText(text: String, matchLine: MatchLine?, syntaxHighlighter: SyntaxHighl
11831182
val line = matchLine ?: MatchLine(listOf(DiffMatchPatch.Diff(DiffMatchPatch.Operation.EQUAL, text)))
11841183

11851184
Row {
1186-
Row(
1185+
val diffContentRemoved = MaterialTheme.colors.diffContentRemoved
1186+
val diffComment = MaterialTheme.colors.diffComment
1187+
val diffKeyword = MaterialTheme.colors.diffKeyword
1188+
val diffAnnotation = MaterialTheme.colors.diffAnnotation
1189+
val diffContentAdded = MaterialTheme.colors.diffContentAdded
1190+
1191+
val annotatedString = remember(line) {
1192+
formatDiff(
1193+
line = line,
1194+
commentColor = diffComment,
1195+
keywordColor = diffKeyword,
1196+
annotationColor = diffAnnotation,
1197+
contentAddedColor = diffContentAdded,
1198+
contentRemovedColor = diffContentRemoved,
1199+
syntaxHighlighter = syntaxHighlighter,
1200+
)
1201+
}
1202+
1203+
Text(
1204+
text = annotatedString,
11871205
modifier = Modifier
11881206
.padding(start = 16.dp)
1189-
.fillMaxSize()
1190-
) {
1191-
val isAllSameType = line.diffs.map { it.operation }.count() == 1
1192-
1193-
for (i in line.diffs) {
1194-
1195-
val color = if (isAllSameType) {
1196-
Color.Transparent
1197-
} else {
1198-
when (i.operation) {
1199-
DiffMatchPatch.Operation.DELETE -> MaterialTheme.colors.diffContentRemoved
1200-
DiffMatchPatch.Operation.INSERT -> MaterialTheme.colors.diffContentAdded
1201-
else -> Color.Transparent
1202-
}
1203-
}
1204-
1205-
Text(
1206-
text = syntaxHighlighter.syntaxHighlight(i.text.orEmpty()),
1207-
modifier = Modifier
1208-
.background(color),
1209-
fontFamily = notoSansMonoFontFamily,
1210-
style = MaterialTheme.typography.body2,
1211-
color = MaterialTheme.colors.onBackground,
1212-
overflow = TextOverflow.Visible,
1213-
)
1214-
}
1215-
}
1207+
.fillMaxWidth(),
1208+
fontFamily = notoSansMonoFontFamily,
1209+
style = MaterialTheme.typography.body2,
1210+
color = MaterialTheme.colors.onBackground,
1211+
overflow = TextOverflow.Visible,
1212+
softWrap = true,
1213+
)
12161214

12171215
val lineDelimiter = text.lineDelimiter
12181216

@@ -1250,3 +1248,52 @@ fun emptyLineNumber(charactersCount: Int): String {
12501248

12511249
return numberBuilder.toString()
12521250
}
1251+
1252+
fun formatDiff(
1253+
line: MatchLine,
1254+
commentColor: Color,
1255+
keywordColor: Color,
1256+
annotationColor: Color,
1257+
contentAddedColor: Color,
1258+
contentRemovedColor: Color,
1259+
syntaxHighlighter: SyntaxHighlighter,
1260+
): AnnotatedString {
1261+
val isAllSameType = line.diffs
1262+
.filter { it.text != "\n" }
1263+
.map { it.operation }
1264+
.count() == 1
1265+
1266+
val diffBuilder = AnnotatedString.Builder()
1267+
val diffs = line.diffs
1268+
1269+
diffs
1270+
.forEach { diff ->
1271+
val color = if (isAllSameType) {
1272+
Color.Transparent
1273+
} else {
1274+
when (diff.operation) {
1275+
DiffMatchPatch.Operation.DELETE -> contentRemovedColor
1276+
DiffMatchPatch.Operation.INSERT -> contentAddedColor
1277+
else -> Color.Transparent
1278+
}
1279+
}
1280+
1281+
val newAnnotatedString = AnnotatedString(
1282+
text = diff.text
1283+
.replaceTabs()
1284+
.removeLineDelimiters(),
1285+
spanStyle = SpanStyle(background = color),
1286+
)
1287+
1288+
diffBuilder.append(newAnnotatedString)
1289+
}
1290+
1291+
val annotatedString = diffBuilder.toAnnotatedString()
1292+
1293+
return syntaxHighlighter.syntaxHighlight(
1294+
annotatedString = annotatedString,
1295+
commentColor = commentColor,
1296+
keywordColor = keywordColor,
1297+
annotationColor = annotationColor,
1298+
)
1299+
}

src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/SyntaxHighlighter.kt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter
22

33
import androidx.compose.material.MaterialTheme
44
import androidx.compose.runtime.Composable
5+
import androidx.compose.ui.graphics.Color
56
import androidx.compose.ui.text.AnnotatedString
67
import androidx.compose.ui.text.SpanStyle
78
import com.jetpackduba.gitnuro.extensions.removeLineDelimiters
9+
import com.jetpackduba.gitnuro.extensions.replaceTabs
10+
import com.jetpackduba.gitnuro.git.diff.DiffMatchPatch
11+
import com.jetpackduba.gitnuro.git.diff.MatchLine
812
import com.jetpackduba.gitnuro.theme.diffAnnotation
913
import com.jetpackduba.gitnuro.theme.diffComment
1014
import com.jetpackduba.gitnuro.theme.diffKeyword
@@ -14,6 +18,64 @@ abstract class SyntaxHighlighter {
1418
loadKeywords()
1519
}
1620

21+
fun syntaxHighlight(
22+
annotatedString: AnnotatedString,
23+
commentColor: Color,
24+
keywordColor: Color,
25+
annotationColor: Color,
26+
): AnnotatedString {
27+
val cleanText = annotatedString.text
28+
29+
var iteratedCharsCount = 0
30+
val builder = AnnotatedString.Builder()
31+
builder.append(cleanText)
32+
33+
for (spanStyleRange in annotatedString.spanStyles) {
34+
builder.addStyle(spanStyleRange.item, spanStyleRange.start, spanStyleRange.end)
35+
}
36+
37+
if (isComment(cleanText.trimStart())) {
38+
builder.addStyle(
39+
style = SpanStyle(color = commentColor),
40+
start = 0,
41+
end = cleanText.count(),
42+
)
43+
} else {
44+
val words = cleanText.split(" ")
45+
46+
words.forEachIndexed { index, word ->
47+
val start = iteratedCharsCount
48+
val end = iteratedCharsCount + word.count()
49+
50+
if (keywords.contains(word)) {
51+
builder.addStyle(
52+
style = SpanStyle(
53+
color = keywordColor,
54+
),
55+
start = start,
56+
end = end,
57+
)
58+
} else if (isAnnotation(word)) {
59+
builder.addStyle(
60+
style = SpanStyle(
61+
color = annotationColor,
62+
),
63+
start = start,
64+
end = end,
65+
)
66+
}
67+
68+
iteratedCharsCount += word.count() + 1
69+
70+
if (index == words.lastIndex) {
71+
iteratedCharsCount--
72+
}
73+
}
74+
}
75+
76+
return builder.toAnnotatedString()
77+
}
78+
1779
@Composable
1880
fun syntaxHighlight(text: String): AnnotatedString {
1981
val cleanText = text.replace(
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter
2+
3+
import androidx.compose.ui.graphics.Color
4+
import androidx.compose.ui.text.AnnotatedString
5+
import androidx.compose.ui.text.SpanStyle
6+
import org.junit.jupiter.api.Test
7+
8+
import org.junit.jupiter.api.Assertions.*
9+
10+
class SyntaxHighlighterTest {
11+
12+
@Test
13+
fun syntaxHighlight() {
14+
val annotatedString = AnnotatedString.Builder()
15+
16+
annotatedString.append("val variable = \"Hello ")
17+
annotatedString.append(AnnotatedString("World", SpanStyle(color = Color.Blue)))
18+
annotatedString.append("\"")
19+
20+
val syntaxHighlighter = getSyntaxHighlighterFromExtension("kt")
21+
val result = syntaxHighlighter.syntaxHighlight(
22+
annotatedString.toAnnotatedString(),
23+
commentColor = Color.Green,
24+
keywordColor = Color.Cyan,
25+
annotationColor = Color.Yellow,
26+
)
27+
28+
assertTrue(
29+
result.spanStyles.contains(
30+
AnnotatedString.Range(
31+
SpanStyle(Color.Cyan),
32+
start = 0,
33+
end = 3,
34+
)
35+
)
36+
)
37+
}
38+
}

0 commit comments

Comments
 (0)