Skip to content

Commit

Permalink
Fixed diff highlight when wrapping text and it's combined with syntax…
Browse files Browse the repository at this point in the history
… highlighting
  • Loading branch information
JetpackDuba committed Nov 25, 2024
1 parent db06460 commit 372ba1a
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ fun String.removeLineDelimiters(): String {
return this
.removeSuffix("\r\n")
.removeSuffix("\n")
}

fun String.replaceTabs(): String {
return this.replace(
"\t",
" "
)
}

val String.lineDelimiter: String?
Expand Down
107 changes: 77 additions & 30 deletions src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.res.loadImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -1183,36 +1182,35 @@ fun DiffText(text: String, matchLine: MatchLine?, syntaxHighlighter: SyntaxHighl
val line = matchLine ?: MatchLine(listOf(DiffMatchPatch.Diff(DiffMatchPatch.Operation.EQUAL, text)))

Row {
Row(
val diffContentRemoved = MaterialTheme.colors.diffContentRemoved
val diffComment = MaterialTheme.colors.diffComment
val diffKeyword = MaterialTheme.colors.diffKeyword
val diffAnnotation = MaterialTheme.colors.diffAnnotation
val diffContentAdded = MaterialTheme.colors.diffContentAdded

val annotatedString = remember(line) {
formatDiff(
line = line,
commentColor = diffComment,
keywordColor = diffKeyword,
annotationColor = diffAnnotation,
contentAddedColor = diffContentAdded,
contentRemovedColor = diffContentRemoved,
syntaxHighlighter = syntaxHighlighter,
)
}

Text(
text = annotatedString,
modifier = Modifier
.padding(start = 16.dp)
.fillMaxSize()
) {
val isAllSameType = line.diffs.map { it.operation }.count() == 1

for (i in line.diffs) {

val color = if (isAllSameType) {
Color.Transparent
} else {
when (i.operation) {
DiffMatchPatch.Operation.DELETE -> MaterialTheme.colors.diffContentRemoved
DiffMatchPatch.Operation.INSERT -> MaterialTheme.colors.diffContentAdded
else -> Color.Transparent
}
}

Text(
text = syntaxHighlighter.syntaxHighlight(i.text.orEmpty()),
modifier = Modifier
.background(color),
fontFamily = notoSansMonoFontFamily,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onBackground,
overflow = TextOverflow.Visible,
)
}
}
.fillMaxWidth(),
fontFamily = notoSansMonoFontFamily,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onBackground,
overflow = TextOverflow.Visible,
softWrap = true,
)

val lineDelimiter = text.lineDelimiter

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

return numberBuilder.toString()
}

fun formatDiff(
line: MatchLine,
commentColor: Color,
keywordColor: Color,
annotationColor: Color,
contentAddedColor: Color,
contentRemovedColor: Color,
syntaxHighlighter: SyntaxHighlighter,
): AnnotatedString {
val isAllSameType = line.diffs
.filter { it.text != "\n" }
.map { it.operation }
.count() == 1

val diffBuilder = AnnotatedString.Builder()
val diffs = line.diffs

diffs
.forEach { diff ->
val color = if (isAllSameType) {
Color.Transparent
} else {
when (diff.operation) {
DiffMatchPatch.Operation.DELETE -> contentRemovedColor
DiffMatchPatch.Operation.INSERT -> contentAddedColor
else -> Color.Transparent
}
}

val newAnnotatedString = AnnotatedString(
text = diff.text
.replaceTabs()
.removeLineDelimiters(),
spanStyle = SpanStyle(background = color),
)

diffBuilder.append(newAnnotatedString)
}

val annotatedString = diffBuilder.toAnnotatedString()

return syntaxHighlighter.syntaxHighlight(
annotatedString = annotatedString,
commentColor = commentColor,
keywordColor = keywordColor,
annotationColor = annotationColor,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter

import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import com.jetpackduba.gitnuro.extensions.removeLineDelimiters
import com.jetpackduba.gitnuro.extensions.replaceTabs
import com.jetpackduba.gitnuro.git.diff.DiffMatchPatch
import com.jetpackduba.gitnuro.git.diff.MatchLine
import com.jetpackduba.gitnuro.theme.diffAnnotation
import com.jetpackduba.gitnuro.theme.diffComment
import com.jetpackduba.gitnuro.theme.diffKeyword
Expand All @@ -14,6 +18,64 @@ abstract class SyntaxHighlighter {
loadKeywords()
}

fun syntaxHighlight(
annotatedString: AnnotatedString,
commentColor: Color,
keywordColor: Color,
annotationColor: Color,
): AnnotatedString {
val cleanText = annotatedString.text

var iteratedCharsCount = 0
val builder = AnnotatedString.Builder()
builder.append(cleanText)

for (spanStyleRange in annotatedString.spanStyles) {
builder.addStyle(spanStyleRange.item, spanStyleRange.start, spanStyleRange.end)
}

if (isComment(cleanText.trimStart())) {
builder.addStyle(
style = SpanStyle(color = commentColor),
start = 0,
end = cleanText.count(),
)
} else {
val words = cleanText.split(" ")

words.forEachIndexed { index, word ->
val start = iteratedCharsCount
val end = iteratedCharsCount + word.count()

if (keywords.contains(word)) {
builder.addStyle(
style = SpanStyle(
color = keywordColor,
),
start = start,
end = end,
)
} else if (isAnnotation(word)) {
builder.addStyle(
style = SpanStyle(
color = annotationColor,
),
start = start,
end = end,
)
}

iteratedCharsCount += word.count() + 1

if (index == words.lastIndex) {
iteratedCharsCount--
}
}
}

return builder.toAnnotatedString()
}

@Composable
fun syntaxHighlight(text: String): AnnotatedString {
val cleanText = text.replace(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import org.junit.jupiter.api.Test

import org.junit.jupiter.api.Assertions.*

class SyntaxHighlighterTest {

@Test
fun syntaxHighlight() {
val annotatedString = AnnotatedString.Builder()

annotatedString.append("val variable = \"Hello ")
annotatedString.append(AnnotatedString("World", SpanStyle(color = Color.Blue)))
annotatedString.append("\"")

val syntaxHighlighter = getSyntaxHighlighterFromExtension("kt")
val result = syntaxHighlighter.syntaxHighlight(
annotatedString.toAnnotatedString(),
commentColor = Color.Green,
keywordColor = Color.Cyan,
annotationColor = Color.Yellow,
)

assertTrue(
result.spanStyles.contains(
AnnotatedString.Range(
SpanStyle(Color.Cyan),
start = 0,
end = 3,
)
)
)
}
}

0 comments on commit 372ba1a

Please sign in to comment.