Refactored syntax highlight to support multiple languages

This commit is contained in:
Abdelilah El Aissaoui 2024-07-15 02:40:23 +02:00
parent 6011b620f9
commit 284597cdeb
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
9 changed files with 304 additions and 135 deletions

View File

@ -25,6 +25,9 @@ val lightTheme = ColorsScheme(
hoverScrollbar = Color(0xFF0070D8),
diffLineAdded = Color(0xAAd7ebd0),
diffLineRemoved = Color(0xAAf0d4d4),
diffKeyword = Color(0xff3b83cf),
diffAnnotation = Color(0xff9f9927),
diffComment = Color(0xff0d9141),
isLight = true,
)
@ -52,6 +55,9 @@ val darkBlueTheme = ColorsScheme(
hoverScrollbar = Color(0xFFCCCCCC),
diffLineAdded = Color(0xAA566f5a),
diffLineRemoved = Color(0xAA6f585e),
diffKeyword = Color(0xFF90c0f0),
diffAnnotation = Color(0xFFB3AE5F),
diffComment = Color(0xFF70C290),
isLight = false,
)
@ -78,5 +84,8 @@ val darkGrayTheme = ColorsScheme(
hoverScrollbar = Color(0xFFCCCCCC),
diffLineAdded = Color(0xAA5b7059),
diffLineRemoved = Color(0xAA74595c),
diffKeyword = Color(0xFF90c0f0),
diffAnnotation = Color(0xFFB3AE5F),
diffComment = Color(0xFF70C290),
isLight = false,
)

View File

@ -38,6 +38,9 @@ data class ColorsScheme(
val hoverScrollbar: Color,
val diffLineAdded: Color,
val diffLineRemoved: Color,
val diffKeyword: Color,
val diffAnnotation: Color,
val diffComment: Color,
val isLight: Boolean,
) {
fun toComposeColors(): Colors {

View File

@ -123,6 +123,15 @@ val Colors.diffLineAdded: Color
val Colors.diffLineRemoved: Color
get() = appTheme.diffLineRemoved
val Colors.diffKeyword: Color
get() = appTheme.diffKeyword
val Colors.diffAnnotation: Color
get() = appTheme.diffAnnotation
val Colors.diffComment: Color
get() = appTheme.diffComment
val Colors.isDark: Boolean
get() = !this.isLight

View File

@ -57,6 +57,7 @@ import com.jetpackduba.gitnuro.ui.components.tooltip.DelayedTooltip
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenu
import com.jetpackduba.gitnuro.ui.context_menu.ContextMenuElement
import com.jetpackduba.gitnuro.ui.context_menu.SelectionAwareTextContextMenu
import com.jetpackduba.gitnuro.ui.diff.syntax_highlighter.getSyntaxHighlighterFromExtension
import com.jetpackduba.gitnuro.viewmodels.DiffViewModel
import com.jetpackduba.gitnuro.viewmodels.TextDiffType
import com.jetpackduba.gitnuro.viewmodels.ViewDiffResult
@ -1137,6 +1138,8 @@ fun SplitDiffLine(
@Composable
fun DiffLineText(line: Line, diffType: DiffType, onActionTriggered: () -> Unit) {
val fileExtension = diffType.filePath.split(".").lastOrNull()
val syntaxHighlighter = getSyntaxHighlighterFromExtension(fileExtension)
val text = line.text
val hoverInteraction = remember { MutableInteractionSource() }
val isHovered by hoverInteraction.collectIsHoveredAsState()
@ -1171,7 +1174,7 @@ fun DiffLineText(line: Line, diffType: DiffType, onActionTriggered: () -> Unit)
Row {
Text(
text = syntaxHighlight(fileExtension, text),
text = syntaxHighlighter.syntaxHighlight(text),
modifier = Modifier
.padding(start = 16.dp)
.fillMaxSize(),
@ -1197,140 +1200,6 @@ fun DiffLineText(line: Line, diffType: DiffType, onActionTriggered: () -> Unit)
}
}
fun syntaxHighlight(fileExtension: String?, text: String): AnnotatedString {
val cleanText = text.replace(
"\t",
" "
).removeLineDelimiters()
return if (cleanText.trimStart().startsWith("//")) {
AnnotatedString(cleanText, spanStyle = SpanStyle(color = Color(0xFF70C290)))
} else {
val words = cleanText.split(" ")
val builder = AnnotatedString.Builder()
val keywords = listOf(
"as",
"as?",
"break",
"by",
"catch",
"class",
"constructor",
"continue",
"do",
"dynamic",
"else",
"false",
"finally",
"for",
"fun",
"if",
"import",
"in",
"!in",
"interface",
"is",
"!is",
"null",
"object",
"package",
"return",
"super",
"this",
"throw",
"true",
"try",
"val",
"var",
"when",
"where",
"while",
// Modifiers
"actual",
"abstract",
"annotation",
"companion",
"const",
"crossinline",
"data",
"enum",
"expect",
"external",
"final",
"infix",
"inline",
"inner",
"internal",
"lateinit",
"noinline",
"open",
"operator",
"out",
"override",
"private",
"protected",
"public",
"reified",
"sealed",
"suspend",
"tailrec",
"vararg",
)
fun isAnnotation(word: String): Boolean = word.startsWith("@")
words.forEachIndexed { index, word ->
if (keywords.contains(word)) {
builder.append(
AnnotatedString(
word,
spanStyle = SpanStyle(
// color = Color(0xFF669ACD)
color = Color(0xFF90c0f0)
)
)
)
} else if (isAnnotation(word)) {
builder.append(
AnnotatedString(
word,
spanStyle = SpanStyle(
color = Color(0xFFB3AE5F)
)
)
)
} else {
builder.append(word)
}
if (index < words.lastIndex) {
builder.append(" ")
}
}
builder.toAnnotatedString()
}
}
class Testtt {
companion object {
@JvmStatic
external fun test1()
}
}
//
//class Pancakes private constructor(val pointer: Long) {
// constructor(message: String, num: Int) : this(initialize(message, num))
// companion object {
// fun initialize(message: String, num: Int): Long {
// return 0L
// }
// }
//}
@Composable
fun LineNumber(text: String, remarked: Boolean) {
Text(

View File

@ -0,0 +1,7 @@
package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter
class DefaultSyntaxHighlighter : SyntaxHighlighter() {
override fun loadKeywords(): List<String> = emptyList()
override fun isAnnotation(word: String): Boolean = false
override fun isComment(line: String): Boolean = false
}

View File

@ -0,0 +1,74 @@
package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter
class KotlinSyntaxHighlighter : SyntaxHighlighter() {
override fun loadKeywords(): List<String> = listOf(
"as",
"as?",
"break",
"by",
"catch",
"class",
"constructor",
"continue",
"do",
"dynamic",
"else",
"false",
"finally",
"for",
"fun",
"if",
"import",
"in",
"!in",
"interface",
"is",
"!is",
"null",
"object",
"package",
"return",
"super",
"this",
"throw",
"true",
"try",
"val",
"var",
"when",
"where",
"while",
"actual",
"abstract",
"annotation",
"companion",
"const",
"crossinline",
"data",
"enum",
"expect",
"external",
"final",
"infix",
"inline",
"inner",
"internal",
"lateinit",
"noinline",
"open",
"operator",
"out",
"override",
"private",
"protected",
"public",
"reified",
"sealed",
"suspend",
"tailrec",
"vararg",
)
override fun isAnnotation(word: String): Boolean = word.startsWith("@")
override fun isComment(line: String): Boolean = line.startsWith("//")
}

View File

@ -0,0 +1,49 @@
package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter
class RustSyntaxHighlighter : SyntaxHighlighter() {
override fun loadKeywords(): List<String> = listOf(
"as",
"async",
"await",
"break",
"const",
"continue",
"crate",
"dyn",
"else",
"enum",
"extern",
"false",
"fn",
"for",
"if",
"impl",
"in",
"let",
"loop",
"match",
"mod",
"move",
"mut",
"pub",
"ref",
"return",
"Self",
"self",
"static",
"struct",
"super",
"trait",
"true",
"type",
"union",
"unsafe",
"use",
"where",
"while",
)
override fun isAnnotation(word: String): Boolean = word.startsWith("#[") && word.endsWith("]")
override fun isComment(line: String): Boolean = line.startsWith("//")
}

View File

@ -0,0 +1,80 @@
package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import com.jetpackduba.gitnuro.extensions.removeLineDelimiters
import com.jetpackduba.gitnuro.theme.diffAnnotation
import com.jetpackduba.gitnuro.theme.diffComment
import com.jetpackduba.gitnuro.theme.diffKeyword
abstract class SyntaxHighlighter {
private val keywords: List<String> by lazy {
loadKeywords()
}
@Composable
fun syntaxHighlight(text: String): AnnotatedString {
val cleanText = text.replace(
"\t",
" "
).removeLineDelimiters()
return if (isComment(cleanText.trimStart())) {
AnnotatedString(cleanText, spanStyle = SpanStyle(color = MaterialTheme.colors.diffComment))
} else {
val words = cleanText.split(" ")
val builder = AnnotatedString.Builder()
words.forEachIndexed { index, word ->
if (keywords.contains(word)) {
builder.append(
AnnotatedString(
word,
spanStyle = SpanStyle(
color = MaterialTheme.colors.diffKeyword
)
)
)
} else if (isAnnotation(word)) {
builder.append(
AnnotatedString(
word,
spanStyle = SpanStyle(
color = MaterialTheme.colors.diffAnnotation
)
)
)
} else {
builder.append(word)
}
if (index < words.lastIndex) {
builder.append(" ")
}
}
builder.toAnnotatedString()
}
}
abstract fun isAnnotation(word: String): Boolean
abstract fun isComment(line: String): Boolean
abstract fun loadKeywords(): List<String>
}
fun getSyntaxHighlighterFromExtension(extension: String?): SyntaxHighlighter {
val matchingHighlightLanguage = HighlightLanguagesSupported.entries.firstOrNull { language ->
language.extensions.contains(extension)
}
return matchingHighlightLanguage?.highlighter?.invoke() ?: DefaultSyntaxHighlighter()
}
private enum class HighlightLanguagesSupported(val extensions: List<String>, val highlighter: () -> SyntaxHighlighter) {
Kotlin(listOf("kt", "kts"), { KotlinSyntaxHighlighter() }),
Rust(listOf("rs"), { RustSyntaxHighlighter() }),
TypeScript(listOf("js", "jsx", "ts", "tsx", "vue", "astro"), { TypeScriptSyntaxHighlighter() }),
}

View File

@ -0,0 +1,69 @@
package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter
class TypeScriptSyntaxHighlighter : SyntaxHighlighter() {
override fun loadKeywords(): List<String> = listOf(
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"import",
"in",
"instanceof",
"new",
"null",
"return",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
"as",
"implements",
"interface",
"let",
"package",
"private",
"protected",
"public",
"static",
"yield",
"any",
"boolean",
"constructor",
"declare",
"get",
"module",
"require",
"number",
"set",
"string",
"symbol",
"type",
"from",
"of",
)
override fun isAnnotation(word: String): Boolean = word.startsWith("@")
override fun isComment(line: String): Boolean = line.startsWith("//")
}