Skip to content

Commit b81c9bb

Browse files
committed
feat: Add syntax highlighting and language selection
This commit introduces syntax highlighting to the diff viewer and input fields. It also adds a language selection dropdown to choose the programming language for highlighting. Key changes: - Integrated `compose-code-editor` library for syntax highlighting. - Added `LanguageDropdown` composable for language selection. - Modified `InlineCharDiffText`, `TwoSideCharDiffText`, `SeparateCharDiffText`, and `UnifiedCharDiffText` to support syntax highlighting. - Updated `DiffCheckerViewModel` to manage selected language and theme. - Applied syntax highlighting to text input fields in `DiffCheckerScreen`. - Added a custom `MonokaiDeepTheme` for code highlighting.
1 parent f4930ff commit b81c9bb

File tree

13 files changed

+418
-161
lines changed

13 files changed

+418
-161
lines changed

.idea/deploymentTargetSelector.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ dependencies {
6969
implementation(libs.androidx.material.icons.core)
7070
implementation(libs.androidx.material.icons.extended)
7171
implementation(libs.navigation.compose)
72-
72+
implementation(libs.compose.code.editor)
7373

7474
androidTestImplementation(libs.androidx.espresso.core)
7575
androidTestImplementation(platform(libs.androidx.compose.bom))

app/src/main/java/dev/jahidhasanco/diffly/presentation/component/InlineCharDiffText.kt

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,72 @@ package dev.jahidhasanco.diffly.presentation.component
22

33
import androidx.compose.material3.Text
44
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.remember
56
import androidx.compose.ui.graphics.Color
67
import androidx.compose.ui.text.SpanStyle
78
import androidx.compose.ui.text.buildAnnotatedString
89
import androidx.compose.ui.text.withStyle
10+
import com.wakaztahir.codeeditor.highlight.model.CodeLang
11+
import com.wakaztahir.codeeditor.highlight.prettify.PrettifyParser
12+
import com.wakaztahir.codeeditor.highlight.theme.CodeTheme
13+
import com.wakaztahir.codeeditor.highlight.utils.parseCodeAsAnnotatedString
914
import dev.jahidhasanco.diffly.domain.model.CharDiff
1015
import dev.jahidhasanco.diffly.domain.model.CharDiffType
1116
import dev.jahidhasanco.diffly.presentation.theme.added
1217
import dev.jahidhasanco.diffly.presentation.theme.delete
1318

1419
@Composable
15-
fun InlineCharDiffText(charDiffs: List<CharDiff>) {
16-
val annotatedString = buildAnnotatedString {
17-
charDiffs.forEach { cd ->
18-
val backgroundColor = when (cd.type) {
19-
CharDiffType.UNCHANGED -> Color.Unspecified
20-
CharDiffType.INSERTED -> added
21-
CharDiffType.DELETED -> delete
20+
fun InlineCharDiffText(
21+
code: String,
22+
charDiffs: List<CharDiff>,
23+
language: CodeLang,
24+
parser: PrettifyParser,
25+
theme: CodeTheme
26+
) {
27+
val syntaxAnnotatedString = remember(code, language, theme) {
28+
parseCodeAsAnnotatedString(parser, theme, language, code)
29+
}
30+
31+
val combinedAnnotatedString = buildAnnotatedString {
32+
for (i in code.indices) {
33+
val char = code[i]
34+
35+
36+
// get all syntax span styles at index i
37+
val spanStylesAtIndex =
38+
syntaxAnnotatedString.spanStyles.filter { it.start <= i && i < it.end }
39+
.map { it.item }
40+
41+
val charStyle = spanStylesAtIndex.fold(SpanStyle()) { acc, style ->
42+
acc.merge(style)
2243
}
23-
withStyle(
24-
SpanStyle(background = backgroundColor)
25-
) {
26-
append(cd.char)
44+
45+
val backgroundColor = when (charDiffs.getOrNull(i)?.type) {
46+
CharDiffType.INSERTED -> added.copy(alpha = 0.2f)
47+
CharDiffType.DELETED -> delete.copy(alpha = 0.2f)
48+
CharDiffType.UNCHANGED -> Color.Transparent
49+
else -> Color.Transparent
50+
}
51+
52+
val finalColor = if (charStyle.color == Color.Unspecified) {
53+
Color.Black
54+
} else {
55+
charStyle.color
56+
}
57+
58+
val mergedStyle = charStyle.merge(
59+
SpanStyle(
60+
color = finalColor,
61+
background = backgroundColor
62+
)
63+
)
64+
65+
66+
withStyle(mergedStyle) {
67+
append(char)
2768
}
2869
}
2970
}
30-
Text(annotatedString)
71+
72+
Text(text = combinedAnnotatedString)
3173
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package dev.jahidhasanco.diffly.presentation.component
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.material.TextButton
5+
import androidx.compose.material.icons.Icons
6+
import androidx.compose.material.icons.filled.ArrowDropDown
7+
import androidx.compose.material.icons.filled.Check
8+
import androidx.compose.material3.DropdownMenu
9+
import androidx.compose.material3.DropdownMenuItem
10+
import androidx.compose.material3.Icon
11+
import androidx.compose.material3.Text
12+
import androidx.compose.runtime.Composable
13+
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.mutableStateOf
15+
import androidx.compose.runtime.remember
16+
import androidx.compose.runtime.setValue
17+
import com.wakaztahir.codeeditor.highlight.model.CodeLang
18+
19+
@Composable
20+
fun LanguageDropdown(
21+
selectedLanguage: CodeLang, onLanguageSelected: (CodeLang) -> Unit
22+
) {
23+
var expanded by remember { mutableStateOf(false) }
24+
Box {
25+
TextButton(onClick = { expanded = !expanded }) {
26+
Text(selectedLanguage.name)
27+
Icon(
28+
imageVector = Icons.Default.ArrowDropDown,
29+
contentDescription = "Select Language"
30+
)
31+
}
32+
33+
DropdownMenu(
34+
expanded = expanded, onDismissRequest = { expanded = false }) {
35+
CodeLang.entries.forEach { lang ->
36+
DropdownMenuItem(
37+
text = { Text(lang.name) },
38+
trailingIcon = if (lang == selectedLanguage) {
39+
{ Icon(Icons.Default.Check, contentDescription = null) }
40+
} else null,
41+
onClick = {
42+
onLanguageSelected(lang)
43+
expanded = false
44+
})
45+
}
46+
}
47+
}
48+
}

app/src/main/java/dev/jahidhasanco/diffly/presentation/component/SeparateCharDiffText.kt

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,23 @@ import androidx.compose.runtime.remember
1818
import androidx.compose.ui.Modifier
1919
import androidx.compose.ui.graphics.Color
2020
import androidx.compose.ui.unit.dp
21+
import com.wakaztahir.codeeditor.highlight.model.CodeLang
22+
import com.wakaztahir.codeeditor.highlight.prettify.PrettifyParser
23+
import com.wakaztahir.codeeditor.highlight.theme.CodeTheme
24+
import com.wakaztahir.codeeditor.highlight.utils.parseCodeAsAnnotatedString
2125
import dev.jahidhasanco.diffly.domain.model.CharDiffType
2226
import dev.jahidhasanco.diffly.domain.model.DiffEntry
2327
import dev.jahidhasanco.diffly.domain.model.DiffType
2428
import dev.jahidhasanco.diffly.presentation.theme.added
2529
import dev.jahidhasanco.diffly.presentation.theme.delete
2630

2731
@Composable
28-
fun SeparateCharDiffText(diffResult: List<DiffEntry>) {
32+
fun SeparateCharDiffText(
33+
language: CodeLang,
34+
parser: PrettifyParser,
35+
theme: CodeTheme,
36+
diffResult: List<DiffEntry>
37+
) {
2938
Column(modifier = Modifier.fillMaxSize()) {
3039

3140
// Original Text Column
@@ -61,7 +70,7 @@ fun SeparateCharDiffText(diffResult: List<DiffEntry>) {
6170
}
6271
val color = when (entry.type) {
6372
DiffType.ADDED, DiffType.DELETED, DiffType.CHANGED -> delete.copy(
64-
alpha = 0.3f
73+
alpha = 0.05f
6574
)
6675

6776
else -> Color.Unspecified
@@ -74,9 +83,21 @@ fun SeparateCharDiffText(diffResult: List<DiffEntry>) {
7483
.background(color)
7584
) {
7685
if (!charDiffs.isNullOrEmpty()) {
77-
InlineCharDiffText(charDiffs = charDiffs)
86+
InlineCharDiffText(
87+
line,
88+
charDiffs = charDiffs,
89+
language,
90+
parser,
91+
theme
92+
)
7893
} else {
79-
Text(line)
94+
val syntaxAnnotatedString =
95+
remember(line, language, theme) {
96+
parseCodeAsAnnotatedString(
97+
parser, theme, language, line
98+
)
99+
}
100+
Text(syntaxAnnotatedString)
80101
}
81102
}
82103
}
@@ -116,7 +137,7 @@ fun SeparateCharDiffText(diffResult: List<DiffEntry>) {
116137
}
117138
val color = when (entry.type) {
118139
DiffType.ADDED, DiffType.DELETED, DiffType.CHANGED -> added.copy(
119-
alpha = 0.3f
140+
alpha = 0.05f
120141
)
121142

122143
else -> Color.Unspecified
@@ -129,9 +150,21 @@ fun SeparateCharDiffText(diffResult: List<DiffEntry>) {
129150
.background(color)
130151
) {
131152
if (!charDiffs.isNullOrEmpty()) {
132-
InlineCharDiffText(charDiffs = charDiffs)
153+
InlineCharDiffText(
154+
line,
155+
charDiffs = charDiffs,
156+
language,
157+
parser,
158+
theme
159+
)
133160
} else {
134-
Text(line)
161+
val syntaxAnnotatedString =
162+
remember(line, language, theme) {
163+
parseCodeAsAnnotatedString(
164+
parser, theme, language, line
165+
)
166+
}
167+
Text(syntaxAnnotatedString)
135168
}
136169
}
137170
}

0 commit comments

Comments
 (0)