Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.github.komodgn.codeview.core

enum class CodeLanguage {
KOTLIN,
JAVA,
KOTLIN,
PYTHON,
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package io.github.komodgn.codeview.core

import io.github.komodgn.codeview.core.languages.base.LanguageDefinition

data class HighlightToken(
val range: IntRange,
val type: TokenType,
Expand All @@ -26,43 +28,31 @@ object SyntaxParser {
fun parse(code: String, definition: LanguageDefinition): List<HighlightToken> {
val tokens = mutableListOf<HighlightToken>()

val multiLineCommentRegex = Regex("""/\*[\s\S]*?\*/""")
val singleLineCommentRegex = Regex("""//.*""")

multiLineCommentRegex.findAll(code).forEach {
tokens.add(HighlightToken(it.range, TokenType.COMMENT))
}

singleLineCommentRegex.findAll(code).forEach { match ->
if (tokens.none { it.range.contains(match.range.first) }) {
tokens.add(HighlightToken(match.range, TokenType.COMMENT))
}
}

val stringRegex = Regex(""""([^"\\]|\\.)*"""")
stringRegex.findAll(code).forEach { match ->
if (tokens.none { it.range.contains(match.range.first) }) {
tokens.add(HighlightToken(match.range, TokenType.STRING))
definition.getCustomRules().forEach { (type, regex) ->
regex.findAll(code).forEach { match ->
if (tokens.none { it.range.overlaps(match.range) }) {
tokens.add(HighlightToken(match.range, type))
}
}
}

val typeRegex = Regex("""\b[A-Z]\w*\b""")
typeRegex.findAll(code).forEach { match ->
if (tokens.none { it.range.contains(match.range.first) }) {
if (tokens.none { it.range.overlaps(match.range) }) {
tokens.add(HighlightToken(match.range, TokenType.TYPE))
}
}

val functionRegex = Regex("""\b\w+(?=\s*\()""")
functionRegex.findAll(code).forEach { match ->
if (tokens.none { it.range.contains(match.range.first) }) {
if (tokens.none { it.range.overlaps(match.range) }) {
tokens.add(HighlightToken(match.range, TokenType.FUNCTION))
}
}

val wordRegex = Regex("""\b(\w+)\b""")
wordRegex.findAll(code).forEach { match ->
if (tokens.none { it.range.contains(match.range.first) }) {
if (tokens.none { it.range.overlaps(match.range) }) {
if (match.value in definition.keywords) {
tokens.add(HighlightToken(match.range, TokenType.KEYWORD))
}
Expand All @@ -71,4 +61,10 @@ object SyntaxParser {

return tokens.sortedBy { it.range.first }
}

/**
* Checks if this range overlaps with the [other] range.
* @return true if there is at least one common element between the two ranges.
*/
private fun IntRange.overlaps(other: IntRange): Boolean = this.first <= other.last && other.first <= this.last
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
package io.github.komodgn.codeview.core.extension

import io.github.komodgn.codeview.core.CodeLanguage
import io.github.komodgn.codeview.core.JavaDefinition
import io.github.komodgn.codeview.core.KotlinDefinition
import io.github.komodgn.codeview.core.LanguageDefinition
import io.github.komodgn.codeview.core.languages.JavaDefinition
import io.github.komodgn.codeview.core.languages.KotlinDefinition
import io.github.komodgn.codeview.core.languages.PythonDefinition
import io.github.komodgn.codeview.core.languages.base.LanguageDefinition

fun CodeLanguage.toDefinition(): LanguageDefinition = when (this) {
CodeLanguage.KOTLIN -> KotlinDefinition
CodeLanguage.JAVA -> JavaDefinition
CodeLanguage.PYTHON -> PythonDefinition
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 komodgn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.komodgn.codeview.core.languages

import io.github.komodgn.codeview.core.languages.base.CLikeLanguageDefinition
import kotlin.collections.plus

object JavaDefinition : CLikeLanguageDefinition() {
override val name = "java"

override val keywords = commonKeywords + setOf(
"void", "boolean", "char", "byte", "short", "int", "long", "float", "double",
"synchronized", "volatile", "transient", "native", "strictfp",
"enum", "record", "extends", "implements", "throws", "instanceof",
"assert", "yield", "var", "permits", "non-sealed",
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2026 komodgn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.komodgn.codeview.core.languages

import io.github.komodgn.codeview.core.TokenType
import io.github.komodgn.codeview.core.languages.base.CLikeLanguageDefinition

object KotlinDefinition : CLikeLanguageDefinition() {
override val name = "kotlin"

override val keywords = commonKeywords + setOf(
"fun", "val", "var", "object", "typealias", "when", "by",
"internal", "override", "open", "data", "inline", "noinline",
"crossinline", "suspend", "tailrec", "operator", "infix",
"expect", "actual", "external", "reified", "companion",
)

override fun getCustomRules(): Map<TokenType, Regex> {
val rules = super.getCustomRules().toMutableMap()

rules[TokenType.STRING] = Regex("\"\"\"[\\s\\S]*?\"\"\"|\".*?\"")
return rules
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2026 komodgn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.komodgn.codeview.core.languages

import io.github.komodgn.codeview.core.languages.base.ScriptLanguageDefinition

object PythonDefinition : ScriptLanguageDefinition() {
override val name = "python"

override val keywords = commonScriptKeywords + setOf(
"def", "class", "elif", "try", "except", "finally", "raise",
"with", "as", "pass", "import", "from", "lambda", "assert",
"is", "not", "and", "or", "global", "nonlocal", "del",
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2026 komodgn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.komodgn.codeview.core.languages.base

import io.github.komodgn.codeview.core.TokenType

abstract class CLikeLanguageDefinition : LanguageDefinition {
protected val commonKeywords = setOf(
"if", "else", "for", "while", "do", "switch", "case", "default", "break", "continue", "return",
"try", "catch", "finally", "throw",
"class", "interface", "package", "import", "public", "private", "protected", "static", "final", "abstract", "sealed",
"this", "super", "true", "false", "null", "new", "instanceof", "is", "as", "in",
)

override fun getCustomRules(): Map<TokenType, Regex> = mapOf(
TokenType.COMMENT to Regex("//.*|/\\*[\\s\\S]*?\\*/"),
TokenType.STRING to Regex("\".*?\""),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.komodgn.codeview.core
package io.github.komodgn.codeview.core.languages.base

import io.github.komodgn.codeview.core.TokenType

interface LanguageDefinition {
val keywords: Set<String>
val name: String

fun getCustomRules(): Map<TokenType, Regex> = emptyMap()
}

object KotlinDefinition : LanguageDefinition {
override val name = "kotlin"
override val keywords = setOf(
"package", "import", "class", "object", "fun", "val", "var",
"if", "else", "for", "while", "return", "when", "is", "in", "interface",
)
}

object JavaDefinition : LanguageDefinition {
override val name = "java"
override val keywords = setOf(
"public", "private", "protected", "static", "final", "class", "interface",
"if", "else", "for", "while", "return", "try", "catch", "new",
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 komodgn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.komodgn.codeview.core.languages.base

import io.github.komodgn.codeview.core.TokenType

abstract class ScriptLanguageDefinition : LanguageDefinition {
protected val commonScriptKeywords = setOf(
"if", "else", "while", "for", "in", "return", "yield", "break", "continue", "true", "false", "None",
)

override fun getCustomRules(): Map<TokenType, Regex> = mapOf(
TokenType.COMMENT to Regex("#.*"),
TokenType.STRING to Regex("f?\"\"\"[\\s\\S]*?\"\"\"|f?\".*?\"|f?'''.*?'''|f?'.*?'"),
)
}
1 change: 1 addition & 0 deletions example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ kotlin {
implementation(libs.compose.material3)
implementation(libs.compose.ui)
implementation(libs.compose.components.resources)
implementation(compose.materialIconsExtended)

implementation(project(":codeview:compose"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.github.komodgn.AppConfig
import io.github.komodgn.example.component.CodeViewSection
import io.github.komodgn.example.component.EditorSection
import io.github.komodgn.codeview.core.CodeLanguage
import io.github.komodgn.example.section.CodeViewSection
import io.github.komodgn.example.section.EditorSection
import io.github.komodgn.example.theme.CodeViewTheme
import io.github.komodgn.example.util.getInitialCode

@Composable
fun App() {
var userInput by remember { mutableStateOf(getInitialCode(AppConfig.LIBRARY_VERSION)) }
var currentLang by remember { mutableStateOf(CodeLanguage.KOTLIN) }
var userInput by remember { mutableStateOf(getInitialCode(currentLang, AppConfig.LIBRARY_VERSION)) }
val scrollState = rememberScrollState()

CodeViewTheme {
Expand Down Expand Up @@ -75,36 +78,42 @@ fun App() {
verticalArrangement = Arrangement.Center,
) {
if (isCompact) {
CodeViewSection(userInput, modifier = Modifier.fillMaxWidth())
EditorSection(userInput, onValueChange = { userInput = it }, modifier = Modifier.fillMaxWidth())
CodeViewSection(
code = userInput,
language = currentLang,
modifier = Modifier.fillMaxWidth(),
)
EditorSection(
code = userInput,
selectedLanguage = currentLang,
onLanguageChange = { newLang ->
currentLang = newLang
userInput = getInitialCode(currentLang, AppConfig.LIBRARY_VERSION)
},
onValueChange = { userInput = it },
modifier = Modifier.fillMaxWidth(),
)
} else {
Row(modifier = Modifier.fillMaxWidth().padding(16.dp, 0.dp, 16.dp, 0.dp)) {
EditorSection(userInput, onValueChange = { userInput = it }, modifier = Modifier.weight(1f))
CodeViewSection(userInput, modifier = Modifier.weight(1f))
EditorSection(
code = userInput,
selectedLanguage = currentLang,
onLanguageChange = { newLang ->
currentLang = newLang
userInput = getInitialCode(currentLang, AppConfig.LIBRARY_VERSION)
},
onValueChange = { userInput = it },
modifier = Modifier.weight(1f),
)
CodeViewSection(
code = userInput,
language = currentLang,
modifier = Modifier.weight(1f),
)
}
}
}
}
}
}
}

private fun getInitialCode(version: String) = """
package io.github.komodgn.example

/**
* Welcome to Compose CodeView v$version Demo!
*
* This library provides syntax highlighting for Compose Multiplatform.
* Feel free to edit the code on the left to see real-time updates.
*/
@Composable
fun CodeDisplay() {
val greeting = println("Hello, CodeView!")

CodeView(
code = greeting,
language = CodeLanguage.KOTLIN,
)
}
""".trimIndent()
Loading
Loading