From 284597cdeb40a4f056ed7efe8db6ad821831ff81 Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Mon, 15 Jul 2024 02:40:23 +0200 Subject: [PATCH] Refactored syntax highlight to support multiple languages --- .../com/jetpackduba/gitnuro/theme/Color.kt | 9 ++ .../jetpackduba/gitnuro/theme/ColorsScheme.kt | 3 + .../com/jetpackduba/gitnuro/theme/Theme.kt | 9 ++ .../com/jetpackduba/gitnuro/ui/diff/Diff.kt | 139 +----------------- .../DefaultSyntaxHighlighter.kt | 7 + .../KotlinSyntaxHighlighter.kt | 74 ++++++++++ .../RustSyntaxHighlighter.kt | 49 ++++++ .../syntax_highlighter/SyntaxHighlighter.kt | 80 ++++++++++ .../TypeScriptSyntaxHighlighter.kt | 69 +++++++++ 9 files changed, 304 insertions(+), 135 deletions(-) create mode 100644 src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/DefaultSyntaxHighlighter.kt create mode 100644 src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/KotlinSyntaxHighlighter.kt create mode 100644 src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/RustSyntaxHighlighter.kt create mode 100644 src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/SyntaxHighlighter.kt create mode 100644 src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/TypeScriptSyntaxHighlighter.kt diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/theme/Color.kt b/src/main/kotlin/com/jetpackduba/gitnuro/theme/Color.kt index 076bb59..ea95c76 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/theme/Color.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/theme/Color.kt @@ -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, ) \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/theme/ColorsScheme.kt b/src/main/kotlin/com/jetpackduba/gitnuro/theme/ColorsScheme.kt index 8241925..f5636ce 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/theme/ColorsScheme.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/theme/ColorsScheme.kt @@ -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 { diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/theme/Theme.kt b/src/main/kotlin/com/jetpackduba/gitnuro/theme/Theme.kt index f3098a6..54f0ff0 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/theme/Theme.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/theme/Theme.kt @@ -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 diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt index fad9352..54ae8cb 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/Diff.kt @@ -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( diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/DefaultSyntaxHighlighter.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/DefaultSyntaxHighlighter.kt new file mode 100644 index 0000000..4292464 --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/DefaultSyntaxHighlighter.kt @@ -0,0 +1,7 @@ +package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter + +class DefaultSyntaxHighlighter : SyntaxHighlighter() { + override fun loadKeywords(): List = emptyList() + override fun isAnnotation(word: String): Boolean = false + override fun isComment(line: String): Boolean = false +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/KotlinSyntaxHighlighter.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/KotlinSyntaxHighlighter.kt new file mode 100644 index 0000000..632298d --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/KotlinSyntaxHighlighter.kt @@ -0,0 +1,74 @@ +package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter + +class KotlinSyntaxHighlighter : SyntaxHighlighter() { + override fun loadKeywords(): List = 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("//") +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/RustSyntaxHighlighter.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/RustSyntaxHighlighter.kt new file mode 100644 index 0000000..b6cf24a --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/RustSyntaxHighlighter.kt @@ -0,0 +1,49 @@ +package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter + +class RustSyntaxHighlighter : SyntaxHighlighter() { + override fun loadKeywords(): List = 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("//") +} diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/SyntaxHighlighter.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/SyntaxHighlighter.kt new file mode 100644 index 0000000..98878ca --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/SyntaxHighlighter.kt @@ -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 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 +} + +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, val highlighter: () -> SyntaxHighlighter) { + Kotlin(listOf("kt", "kts"), { KotlinSyntaxHighlighter() }), + Rust(listOf("rs"), { RustSyntaxHighlighter() }), + TypeScript(listOf("js", "jsx", "ts", "tsx", "vue", "astro"), { TypeScriptSyntaxHighlighter() }), +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/TypeScriptSyntaxHighlighter.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/TypeScriptSyntaxHighlighter.kt new file mode 100644 index 0000000..94c303d --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ui/diff/syntax_highlighter/TypeScriptSyntaxHighlighter.kt @@ -0,0 +1,69 @@ +package com.jetpackduba.gitnuro.ui.diff.syntax_highlighter + +class TypeScriptSyntaxHighlighter : SyntaxHighlighter() { + override fun loadKeywords(): List = 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("//") +}