From 074604094246aa7a76dafd76162a3f585f9e73f0 Mon Sep 17 00:00:00 2001 From: tkadziolka Date: Sat, 27 Sep 2025 22:56:32 +0200 Subject: [PATCH 1/8] Shown line numbers in examples and readme --- README.md | 8 ++- androidExample/build.gradle.kts | 1 + .../dev/snipme/androidexample/Dropdown.kt | 58 ------------------- .../dev/snipme/androidexample/MainActivity.kt | 10 +++- .../snipme/androidexample/ThemeSwitcher.kt | 40 ------------- desktopExample/build.gradle.kts | 1 + desktopExample/src/main/kotlin/Main.kt | 24 ++++++-- exampleCommon/build.gradle.kts | 27 +++++++++ .../src/commonMain}/kotlin/Dropdown.kt | 0 .../commonMain/kotlin}/LineNumberSwitcher.kt | 14 ++--- .../src/commonMain}/kotlin/ThemeSwitcher.kt | 0 kodeview/build.gradle.kts | 2 +- .../kodeview/view/material3/CodeEditText.kt | 14 ++--- settings.gradle.kts | 3 +- webExample/build.gradle.kts | 1 + webExample/src/jsMain/kotlin/Dropdown.kt | 56 ------------------ webExample/src/jsMain/kotlin/ThemeSwitcher.kt | 38 ------------ webExample/src/jsMain/kotlin/main.js.kt | 15 ++++- 18 files changed, 89 insertions(+), 223 deletions(-) delete mode 100644 androidExample/src/main/java/dev/snipme/androidexample/Dropdown.kt delete mode 100644 androidExample/src/main/java/dev/snipme/androidexample/ThemeSwitcher.kt create mode 100644 exampleCommon/build.gradle.kts rename {desktopExample/src/main => exampleCommon/src/commonMain}/kotlin/Dropdown.kt (100%) rename {androidExample/src/main/java/dev/snipme/androidexample => exampleCommon/src/commonMain/kotlin}/LineNumberSwitcher.kt (81%) rename {desktopExample/src/main => exampleCommon/src/commonMain}/kotlin/ThemeSwitcher.kt (100%) delete mode 100644 webExample/src/jsMain/kotlin/Dropdown.kt delete mode 100644 webExample/src/jsMain/kotlin/ThemeSwitcher.kt diff --git a/README.md b/README.md index 979d39f..5ce821c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ repositories { ``` ```shell -implementation("dev.snipme:kodeview:0.9.0") +implementation("dev.snipme:kodeview:0.10.0") ``` ## Features ✨ @@ -57,7 +57,10 @@ fun MyApp() { MaterialTheme { Column { - CodeTextView(highlights = highlights.value) + CodeTextView( + highlights = highlights.value, + showLineNumbers = true, + ) } } } @@ -83,6 +86,7 @@ fun MyApp() { Column { CodeEditText( highlights = highlights.value, + showLineNumbers = true, onValueChange = { textValue -> highlights.value = highlights.value.getBuilder() .code(textValue) diff --git a/androidExample/build.gradle.kts b/androidExample/build.gradle.kts index 06405c8..86e114a 100644 --- a/androidExample/build.gradle.kts +++ b/androidExample/build.gradle.kts @@ -60,4 +60,5 @@ dependencies { implementation(compose.ui) implementation(compose.materialIconsExtended) implementation(project(":kodeview")) + implementation(project(":exampleCommon")) } \ No newline at end of file diff --git a/androidExample/src/main/java/dev/snipme/androidexample/Dropdown.kt b/androidExample/src/main/java/dev/snipme/androidexample/Dropdown.kt deleted file mode 100644 index 7afbf6b..0000000 --- a/androidExample/src/main/java/dev/snipme/androidexample/Dropdown.kt +++ /dev/null @@ -1,58 +0,0 @@ -package dev.snipme.androidexample - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp - -@Composable -fun Dropdown( - options: List, - selected: Int, - modifier: Modifier = Modifier, - onSelect: (String) -> Unit, -) { - var selectedOption by remember { - mutableStateOf(options[selected]) - } - - var isExpanded by remember { mutableStateOf(false) } - - DropdownMenu( - expanded = isExpanded, - onDismissRequest = { isExpanded = false }, - ) { - options.forEach { option -> - DropdownMenuItem( - text = { Text(text = option) }, - onClick = { - selectedOption = option - onSelect(option) - isExpanded = !isExpanded - }, - ) - } - } - - Text( - text = selectedOption, - textAlign = TextAlign.Center, - modifier = modifier - .clip(RoundedCornerShape(16.dp)) - .fillMaxWidth() - .clickable { isExpanded = !isExpanded } - .padding(8.dp), - ) -} \ No newline at end of file diff --git a/androidExample/src/main/java/dev/snipme/androidexample/MainActivity.kt b/androidExample/src/main/java/dev/snipme/androidexample/MainActivity.kt index 754947d..46aa9dd 100644 --- a/androidExample/src/main/java/dev/snipme/androidexample/MainActivity.kt +++ b/androidExample/src/main/java/dev/snipme/androidexample/MainActivity.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material3.Divider import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -28,6 +27,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import dev.snipme.desktopexample.Dropdown +import dev.snipme.desktopexample.ThemeSwitcher import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxTheme @@ -104,7 +105,10 @@ fun App() { Spacer(Modifier.height(16.dp)) - LineNumberSwitcher(lineNumbersEnabled, modifier = Modifier.fillMaxWidth()) { lineNumberEnabled -> + LineNumberSwitcher( + lineNumbersEnabled, + modifier = Modifier.fillMaxWidth() + ) { lineNumberEnabled -> areLineNumbersEnabled.value = lineNumberEnabled } @@ -128,6 +132,7 @@ fun App() { Text("Edit this...") CodeEditText( highlights = highlights, + showLineNumbers = lineNumbersEnabled, onValueChange = { textValue -> highlightsState.value = highlights.getBuilder() .code(textValue) @@ -141,7 +146,6 @@ fun App() { disabledIndicatorColor = Color.Transparent, errorIndicatorColor = Color.Transparent, ), - showLineNumbers = lineNumbersEnabled, ) Spacer(modifier = Modifier.size(16.dp)) diff --git a/androidExample/src/main/java/dev/snipme/androidexample/ThemeSwitcher.kt b/androidExample/src/main/java/dev/snipme/androidexample/ThemeSwitcher.kt deleted file mode 100644 index e453f24..0000000 --- a/androidExample/src/main/java/dev/snipme/androidexample/ThemeSwitcher.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.snipme.androidexample - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DarkMode -import androidx.compose.material.icons.outlined.LightMode -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun ThemeSwitcher( - isDarkMode: Boolean, - modifier: Modifier = Modifier, - onChange: (Boolean) -> Unit, -) { - Surface { - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - Spacer(Modifier.width(8.dp)) - Icon(Icons.Outlined.LightMode, contentDescription = "Light mode") - Spacer(Modifier.width(16.dp)) - Switch(checked = isDarkMode, onCheckedChange = onChange) - Spacer(Modifier.width(16.dp)) - Icon(Icons.Default.DarkMode, contentDescription = "Dark mode") - Spacer(Modifier.width(8.dp)) - } - } -} \ No newline at end of file diff --git a/desktopExample/build.gradle.kts b/desktopExample/build.gradle.kts index 3b39176..49318dd 100644 --- a/desktopExample/build.gradle.kts +++ b/desktopExample/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(compose.components.resources) implementation(compose.desktop.currentOs) implementation(project(":kodeview")) + implementation(project(":exampleCommon")) } compose.desktop { diff --git a/desktopExample/src/main/kotlin/Main.kt b/desktopExample/src/main/kotlin/Main.kt index e7e757a..9f101d3 100644 --- a/desktopExample/src/main/kotlin/Main.kt +++ b/desktopExample/src/main/kotlin/Main.kt @@ -1,5 +1,6 @@ package dev.snipme.desktopexample +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -26,6 +28,7 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState +import dev.snipme.androidexample.LineNumberSwitcher import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxTheme @@ -45,7 +48,9 @@ private val sampleCode = fun main() = application { val isDarkModeState = remember { mutableStateOf(false) } + val areLineNumbersEnabled = remember { mutableStateOf(true) } val isDarkMode = isDarkModeState.value + val lineNumbersEnabled = areLineNumbersEnabled.value val highlightsState = remember { mutableStateOf( @@ -89,9 +94,15 @@ fun main() = application { updateSyntaxTheme(highlights.getTheme().useDark(setToDarkMode)!!) } - Spacer(Modifier.height(16.dp)) + LineNumberSwitcher( + lineNumbersEnabled, + modifier = Modifier.fillMaxWidth() + ) { lineNumberEnabled -> + areLineNumbersEnabled.value = lineNumberEnabled + } + Text( modifier = Modifier.fillMaxWidth(), text = "KodeView", @@ -109,19 +120,24 @@ fun main() = application { CodeTextView( modifier = Modifier.weight(1f), highlights = highlights, + showLineNumbers = lineNumbersEnabled, + textStyle = MaterialTheme.typography.bodyMedium, ) VerticalDivider(Modifier.padding(8.dp)) CodeEditText( - modifier = Modifier.weight(1f), - label = { Text("Edit code") }, + modifier = Modifier + .horizontalScroll(rememberScrollState()) + .weight(1f), highlights = highlights, + showLineNumbers = lineNumbersEnabled, + label = { Text("Edit code") }, onValueChange = { textValue -> highlightsState.value = highlights.getBuilder() .code(textValue) .build() }, colors = TextFieldDefaults.colors( - unfocusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Gray, focusedContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, diff --git a/exampleCommon/build.gradle.kts b/exampleCommon/build.gradle.kts new file mode 100644 index 0000000..a2c4955 --- /dev/null +++ b/exampleCommon/build.gradle.kts @@ -0,0 +1,27 @@ +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + alias(libs.plugins.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.compose.compiler) +} + +kotlin { + jvm() + js(IR) { + browser() + binaries.executable() + } + + sourceSets { + val commonMain by getting { + dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.materialIconsExtended) + } + } + } +} \ No newline at end of file diff --git a/desktopExample/src/main/kotlin/Dropdown.kt b/exampleCommon/src/commonMain/kotlin/Dropdown.kt similarity index 100% rename from desktopExample/src/main/kotlin/Dropdown.kt rename to exampleCommon/src/commonMain/kotlin/Dropdown.kt diff --git a/androidExample/src/main/java/dev/snipme/androidexample/LineNumberSwitcher.kt b/exampleCommon/src/commonMain/kotlin/LineNumberSwitcher.kt similarity index 81% rename from androidExample/src/main/java/dev/snipme/androidexample/LineNumberSwitcher.kt rename to exampleCommon/src/commonMain/kotlin/LineNumberSwitcher.kt index 207a0cc..a8a5d90 100644 --- a/androidExample/src/main/java/dev/snipme/androidexample/LineNumberSwitcher.kt +++ b/exampleCommon/src/commonMain/kotlin/LineNumberSwitcher.kt @@ -5,16 +5,12 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.FormatAlignLeft -import androidx.compose.material.icons.filled.DarkMode -import androidx.compose.material.icons.outlined.FormatAlignLeft import androidx.compose.material.icons.outlined.FormatListNumbered -import androidx.compose.material.icons.outlined.LightMode import androidx.compose.material.icons.outlined.Reorder +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,11 +29,11 @@ fun LineNumberSwitcher( verticalAlignment = Alignment.CenterVertically, ) { Spacer(Modifier.width(8.dp)) - Icon(Icons.Outlined.FormatListNumbered, contentDescription = "Line numbers enabled") + Icon(Icons.Outlined.Reorder, contentDescription = "Line numbers disabled") Spacer(Modifier.width(16.dp)) Switch(checked = lineNumbersEnabled, onCheckedChange = onChange) Spacer(Modifier.width(16.dp)) - Icon(Icons.Outlined.Reorder, contentDescription = "Dark mode") + Icon(Icons.Outlined.FormatListNumbered, contentDescription = "Line numbers enabled") Spacer(Modifier.width(8.dp)) } } diff --git a/desktopExample/src/main/kotlin/ThemeSwitcher.kt b/exampleCommon/src/commonMain/kotlin/ThemeSwitcher.kt similarity index 100% rename from desktopExample/src/main/kotlin/ThemeSwitcher.kt rename to exampleCommon/src/commonMain/kotlin/ThemeSwitcher.kt diff --git a/kodeview/build.gradle.kts b/kodeview/build.gradle.kts index 2e20552..12d97c3 100644 --- a/kodeview/build.gradle.kts +++ b/kodeview/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.snipme" -version = "0.9.0" +version = "0.10.0" android { namespace = "dev.snipme.kodeview" diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt index 470525f..f3d10eb 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt @@ -3,19 +3,15 @@ package dev.snipme.kodeview.view.material3 import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.Divider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape @@ -70,7 +66,6 @@ fun CodeEditText( ) } - LaunchedEffect(highlights) { highlights.getHighlightsAsync(object : DefaultHighlightsResultListener() { override fun onSuccess(result: List) { @@ -95,10 +90,9 @@ fun CodeEditText( Row(modifier = modifier.fillMaxWidth()) { if (showLineNumbers) { val lines = currentText.value.text.lines().size.coerceAtLeast(minLines) + val labelPadding = label?.run { 8.dp } ?: 0.dp Column( - horizontalAlignment = Alignment.End, - modifier = Modifier - .padding(top = 16.dp, end = 8.dp) + modifier = Modifier.padding(top = 16.dp + labelPadding) ) { (1..lines).forEach { i -> Text( @@ -199,9 +193,9 @@ fun CodeEditTextSwiftUi( Row(modifier = modifier.fillMaxWidth()) { if (showLineNumbers) { val lines = currentText.value.text.lines().size.coerceAtLeast(minLines) + // Align with TextField's internal padding Column( - modifier = Modifier - .padding(top = 16.dp, end = 8.dp) // Align with TextField's internal padding + modifier ) { (1..lines).forEach { i -> Text( diff --git a/settings.gradle.kts b/settings.gradle.kts index a509564..8e0d666 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,4 +43,5 @@ rootProject.name = "KodeView" include(":kodeview") include(":androidExample") include(":desktopExample") -include(":webExample") \ No newline at end of file +include(":webExample") +include(":exampleCommon") \ No newline at end of file diff --git a/webExample/build.gradle.kts b/webExample/build.gradle.kts index 7606433..7c850d8 100644 --- a/webExample/build.gradle.kts +++ b/webExample/build.gradle.kts @@ -20,6 +20,7 @@ kotlin { implementation(compose.material3) implementation(compose.materialIconsExtended) implementation(project(":kodeview")) + implementation(project(":exampleCommon")) } } } diff --git a/webExample/src/jsMain/kotlin/Dropdown.kt b/webExample/src/jsMain/kotlin/Dropdown.kt deleted file mode 100644 index 1bdf446..0000000 --- a/webExample/src/jsMain/kotlin/Dropdown.kt +++ /dev/null @@ -1,56 +0,0 @@ -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp - -@Composable -fun Dropdown( - options: List, - selected: Int, - modifier: Modifier = Modifier, - onSelect: (String) -> Unit, -) { - var selectedOption by remember { - mutableStateOf(options[selected]) - } - - var isExpanded by remember { mutableStateOf(false) } - - DropdownMenu( - expanded = isExpanded, - onDismissRequest = { isExpanded = false }, - ) { - options.forEach { option -> - DropdownMenuItem( - text = { Text(text = option) }, - onClick = { - selectedOption = option - onSelect(option) - isExpanded = !isExpanded - }, - ) - } - } - - Text( - text = selectedOption, - textAlign = TextAlign.Center, - modifier = modifier - .clip(RoundedCornerShape(16.dp)) - .fillMaxWidth() - .clickable { isExpanded = !isExpanded } - .padding(8.dp), - ) -} \ No newline at end of file diff --git a/webExample/src/jsMain/kotlin/ThemeSwitcher.kt b/webExample/src/jsMain/kotlin/ThemeSwitcher.kt deleted file mode 100644 index 520c26d..0000000 --- a/webExample/src/jsMain/kotlin/ThemeSwitcher.kt +++ /dev/null @@ -1,38 +0,0 @@ -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DarkMode -import androidx.compose.material.icons.outlined.LightMode -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun ThemeSwitcher( - isDarkMode: Boolean, - modifier: Modifier = Modifier, - onChange: (Boolean) -> Unit, -) { - Surface { - Row( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - Spacer(Modifier.width(8.dp)) - Icon(Icons.Outlined.LightMode, contentDescription = "Light mode") - Spacer(Modifier.width(16.dp)) - Switch(checked = isDarkMode, onCheckedChange = onChange) - Spacer(Modifier.width(16.dp)) - Icon(Icons.Default.DarkMode, contentDescription = "Dark mode") - Spacer(Modifier.width(8.dp)) - } - } -} \ No newline at end of file diff --git a/webExample/src/jsMain/kotlin/main.js.kt b/webExample/src/jsMain/kotlin/main.js.kt index 725b20c..69c8999 100644 --- a/webExample/src/jsMain/kotlin/main.js.kt +++ b/webExample/src/jsMain/kotlin/main.js.kt @@ -1,3 +1,4 @@ + import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -23,13 +24,16 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.CanvasBasedWindow +import dev.snipme.androidexample.LineNumberSwitcher +import dev.snipme.desktopexample.Dropdown +import dev.snipme.desktopexample.ThemeSwitcher import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxTheme import dev.snipme.highlights.model.SyntaxThemes import dev.snipme.highlights.model.SyntaxThemes.useDark -import dev.snipme.kodeview.view.material3.CodeEditText import dev.snipme.kodeview.view.CodeTextView +import dev.snipme.kodeview.view.material3.CodeEditText import org.jetbrains.skiko.wasm.onWasmReady private val sampleCode = @@ -50,7 +54,9 @@ fun main() { applyDefaultStyles = true, ) { val isDarkModeState = remember { mutableStateOf(false) } + val areLineNumbersEnabled = remember { mutableStateOf(true) } val isDarkMode = isDarkModeState.value + val lineNumbersEnabled = areLineNumbersEnabled.value val highlightsState = remember { mutableStateOf( @@ -92,6 +98,13 @@ fun main() { Spacer(Modifier.height(16.dp)) + LineNumberSwitcher( + lineNumbersEnabled, + modifier = Modifier.fillMaxWidth() + ) { lineNumberEnabled -> + areLineNumbersEnabled.value = lineNumberEnabled + } + Text( modifier = Modifier.fillMaxWidth(), text = "KodeView", From 073dc9f069a4dc31a49a20cea64c4f7bca8a3b0f Mon Sep 17 00:00:00 2001 From: tkadziolka Date: Sun, 28 Sep 2025 18:11:58 +0200 Subject: [PATCH 2/8] Extracted line number wrapper --- desktopExample/src/main/kotlin/Main.kt | 20 +-- .../src/commonMain/kotlin/Samples.kt | 144 ++++++++++++++++++ .../dev/snipme/kodeview/view/CodeTextView.kt | 49 ++---- .../kotlin/dev/snipme/kodeview/view/Common.kt | 87 +++++++++++ .../kodeview/view/material3/CodeEditText.kt | 19 ++- 5 files changed, 260 insertions(+), 59 deletions(-) create mode 100644 exampleCommon/src/commonMain/kotlin/Samples.kt create mode 100644 kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt diff --git a/desktopExample/src/main/kotlin/Main.kt b/desktopExample/src/main/kotlin/Main.kt index 9f101d3..64112ae 100644 --- a/desktopExample/src/main/kotlin/Main.kt +++ b/desktopExample/src/main/kotlin/Main.kt @@ -1,6 +1,6 @@ package dev.snipme.desktopexample -import androidx.compose.foundation.horizontalScroll +import Samples import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -37,15 +36,6 @@ import dev.snipme.highlights.model.SyntaxThemes.useDark import dev.snipme.kodeview.view.CodeTextView import dev.snipme.kodeview.view.material3.CodeEditText -private val sampleCode = - """ - class Main { - public static void main(String[] args) { - int abcd = 100; - } - } - """.trimIndent() - fun main() = application { val isDarkModeState = remember { mutableStateOf(false) } val areLineNumbersEnabled = remember { mutableStateOf(true) } @@ -54,7 +44,7 @@ fun main() = application { val highlightsState = remember { mutableStateOf( - Highlights.Builder(code = sampleCode).build() + Highlights.Builder(code = Samples.kotlin).build() ) } val highlights = highlightsState.value @@ -125,9 +115,7 @@ fun main() = application { ) VerticalDivider(Modifier.padding(8.dp)) CodeEditText( - modifier = Modifier - .horizontalScroll(rememberScrollState()) - .weight(1f), + modifier = Modifier.weight(1f), highlights = highlights, showLineNumbers = lineNumbersEnabled, label = { Text("Edit code") }, @@ -137,7 +125,7 @@ fun main() = application { .build() }, colors = TextFieldDefaults.colors( - unfocusedContainerColor = Color.Gray, + unfocusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, diff --git a/exampleCommon/src/commonMain/kotlin/Samples.kt b/exampleCommon/src/commonMain/kotlin/Samples.kt new file mode 100644 index 0000000..d656f41 --- /dev/null +++ b/exampleCommon/src/commonMain/kotlin/Samples.kt @@ -0,0 +1,144 @@ +class Samples { + companion object { + val javaStarter = + """ + class Main { + public static void main(String[] args) { + int abcd = 100; + } + } + """.trimIndent() + + // TODO Fix dolars in strings + val kotlin = """ + package dev.example + + import kotlin.random.Random + + // Class declaration with primary constructor + class SampleClass(private val name: String, var age: Int) { + + // Companion object + companion object { + const val CONSTANT = "This is a constant" + fun staticMethod() = println("This is a static method") + } + + // Properties + val isAdult: Boolean + get() = age >= 18 + + // Secondary constructor + constructor(name: String) : this(name, 0) + + // Enum class + enum class Status { + ACTIVE, INACTIVE, PENDING + } + + // Nested class + class Nested { + fun nestedMethod() = "Nested class method" + } + + // Inner class + inner class Inner { + fun innerMethod() = "Inner class method, name: "\$"name" + } + + // Data class + data class Data(val id: Int, val value: String) + + // Sealed class + sealed class Result { + data class Success(val data: String) : Result() + data class Failure(val error: String) : Result() + } + + // Object declaration + object Singleton { + fun doSomething() = "Singleton object method" + } + + // Function with default arguments + fun greet(greeting: String = "Hello") { + println('"\$"greeting, "\$"name!') + } + + // Inline function + inline fun inlineFunction(action: () -> Unit) { + action() + } + + // Higher-order function + fun higherOrderFunction(operation: (Int, Int) -> Int): Int { + return operation(5, 10) + } + + // Lambda expression + val lambda: (Int, Int) -> Int = { a, b -> a + b } + + // Extension function + fun String.addExclamation(): String = "$this!" + + // Operator overloading + operator fun plus(other: SampleClass): SampleClass { + return SampleClass(this.name + other.name, this.age + other.age) + } + + // Inline class + @JvmInline + value class InlineClass(val value: String) + + // Try-catch-finally + fun riskyOperation() { + try { + val result = 10 / Random.nextInt(0, 2) + println("Result: "\$"result") + } catch (e: ArithmeticException) { + println("Caught exception: "\$"{e.message}") + } finally { + println("Finally block executed") + } + } + + // When expression + fun checkStatus(status: Status): String { + return when (status) { + Status.ACTIVE -> "Active" + Status.INACTIVE -> "Inactive" + Status.PENDING -> "Pending" + } + } + + // Loops + fun printNumbers() { + for (i in 1..5) { + println(i) + } + var count = 0 + while (count < 3) { + println("Count: "\$"count") + count++ + } + } + + // Annotations + @Deprecated("This method is deprecated") + fun deprecatedMethod() { + println("Deprecated method") + } + + // Generics + fun genericMethod(item: T): T { + return item + } + + // Vararg + fun printAll(vararg items: String) { + items.forEach { println(it) } + } + } + """.trimIndent() + } +} \ No newline at end of file diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt index 34ac50d..6a61c16 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt @@ -1,30 +1,16 @@ package dev.snipme.kodeview.view import androidx.compose.foundation.background -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.dp import dev.snipme.highlights.Highlights -import generateAnnotatedString @Composable fun CodeTextView( @@ -32,40 +18,23 @@ fun CodeTextView( highlights: Highlights, textStyle: TextStyle = LocalTextStyle.current, showLineNumbers: Boolean = false, - lineNumberTextStyle: TextStyle = textStyle.copy() + lineNumberTextStyle: TextStyle = textStyle.copy(), + async: Boolean = false, ) { - var textState by remember { - mutableStateOf(AnnotatedString(highlights.getCode())) - } - - LaunchedEffect(highlights) { - textState = highlights - .getHighlights() - .generateAnnotatedString(highlights.getCode()) - } + val textState = rememberTextStateWithHighlights(highlights, async = async) Surface( modifier = modifier, color = Color.Transparent ) { - Row(modifier = Modifier - .verticalScroll(rememberScrollState()) - .horizontalScroll(rememberScrollState()) + LineNumberWrapper( + text = textState.text, + showLineNumbers = showLineNumbers, + lineNumberTextStyle = lineNumberTextStyle ) { - if (showLineNumbers) { - val lines = textState.text.lines().size - Column(horizontalAlignment = Alignment.End) { - for (i in 1..lines) { - Text( - text = i.toString(), - style = lineNumberTextStyle, - ) - } - } - Spacer(modifier = Modifier.width(8.dp)) - } Text( - text = textState, + modifier = Modifier.verticalScroll(rememberScrollState()), + text = textState.annotatedString, style = textStyle ) } diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt new file mode 100644 index 0000000..b599d56 --- /dev/null +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt @@ -0,0 +1,87 @@ +package dev.snipme.kodeview.view + +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import dev.snipme.highlights.DefaultHighlightsResultListener +import dev.snipme.highlights.Highlights +import dev.snipme.highlights.model.CodeHighlight +import generateAnnotatedString + +@Composable +internal fun rememberTextStateWithHighlights( + highlights: Highlights, + async: Boolean = true, +): TextFieldValue { + val textState = remember { + mutableStateOf( + TextFieldValue( + AnnotatedString(highlights.getCode()) + ) + ) + } + + LaunchedEffect(highlights) { + if (async) { + highlights.getHighlightsAsync(object : DefaultHighlightsResultListener() { + override fun onSuccess(result: List) { + textState.value = textState.value.copy( + annotatedString = result.generateAnnotatedString(textState.value.text), + ) + } + }) + return@LaunchedEffect + } + + val result = highlights.getHighlights() + textState.value = textState.value.copy( + annotatedString = result.generateAnnotatedString(textState.value.text), + ) + } + + return textState.value +} + +@Composable +internal fun LineNumberWrapper( + modifier: Modifier = Modifier, + text: String, + showLineNumbers: Boolean, + lineNumberTextStyle: androidx.compose.ui.text.TextStyle, + content: @Composable () -> Unit +) { + Row(modifier = Modifier.fillMaxWidth()) { + if (showLineNumbers) { + val lines = text.lines().size + Column(horizontalAlignment = Alignment.End) { + for (i in 1..lines) { + Text( + text = i.toString(), + style = lineNumberTextStyle, + ) + } + } + Spacer(modifier = Modifier.width(8.dp)) + Box(modifier = modifier.horizontalScroll(rememberScrollState())) { + content() + } + } else { + content() + } + } +} \ No newline at end of file diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt index f3d10eb..1ac40b5 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt @@ -1,10 +1,12 @@ package dev.snipme.kodeview.view.material3 +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Text @@ -103,8 +105,13 @@ fun CodeEditText( } } + val lineNumberAwareModifier = if (showLineNumbers) + modifier.horizontalScroll(rememberScrollState()) + else + modifier.padding(start = 8.dp) + TextField3( - modifier = Modifier.weight(1f), + modifier = lineNumberAwareModifier, onValueChange = ::updateNewValue, value = currentText.value, enabled = enabled, @@ -193,7 +200,6 @@ fun CodeEditTextSwiftUi( Row(modifier = modifier.fillMaxWidth()) { if (showLineNumbers) { val lines = currentText.value.text.lines().size.coerceAtLeast(minLines) - // Align with TextField's internal padding Column( modifier ) { @@ -206,8 +212,15 @@ fun CodeEditTextSwiftUi( } } + // TODO Extract common logic to a wrapper + + val lineNumberAwareModifier = if (showLineNumbers) + modifier.horizontalScroll(rememberScrollState()) + else + modifier + TextField3( - modifier = Modifier.weight(1f), + modifier = lineNumberAwareModifier.weight(1f), value = currentText.value, onValueChange = ::updateNewValue, enabled = enabled, From 476c33b462e0e072a9bf3b7275da15b6a228f483 Mon Sep 17 00:00:00 2001 From: tkadziolka Date: Sun, 28 Sep 2025 20:00:11 +0200 Subject: [PATCH 3/8] Updating state with one function --- CHANGELOG.md | 7 +- README.md | 4 + desktopExample/src/main/kotlin/Main.kt | 23 ++-- .../dev/snipme/kodeview/view/CodeEditText.kt | 17 +-- .../dev/snipme/kodeview/view/CodeTextView.kt | 7 +- .../kotlin/dev/snipme/kodeview/view/Common.kt | 46 ++++++- .../kodeview/view/material3/CodeEditText.kt | 115 +++++++----------- 7 files changed, 119 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d05a72e..b4e4d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ ## [0.10.0] ### Added -- Add toggleable line numbers to `CodeTextView` and `CodeEditText` -- Updated sample app to include a toggle for line numbers -- Added `textStyle` param to `CodeTextView` and `CodeEditText` for custom text styling +- toggleable line numbers to `CodeTextView` and `CodeEditText` +- line numbers toggle to sample app +- `textStyle` param to `CodeTextView` and `CodeEditText` for custom text styling +- `hasHorizontalScroll` to support text next to lines scrolling ## [0.9.0] diff --git a/README.md b/README.md index 5ce821c..05d4e36 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ fun MyApp() { CodeTextView( highlights = highlights.value, showLineNumbers = true, + hasVerticalScroll = true, + hasHorizontalScroll = true, ) } } @@ -87,6 +89,8 @@ fun MyApp() { CodeEditText( highlights = highlights.value, showLineNumbers = true, + hasVerticalScroll = true, + hasHorizontalScroll = true, onValueChange = { textValue -> highlights.value = highlights.value.getBuilder() .code(textValue) diff --git a/desktopExample/src/main/kotlin/Main.kt b/desktopExample/src/main/kotlin/Main.kt index 64112ae..3a1613f 100644 --- a/desktopExample/src/main/kotlin/Main.kt +++ b/desktopExample/src/main/kotlin/Main.kt @@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -33,12 +35,11 @@ import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxTheme import dev.snipme.highlights.model.SyntaxThemes import dev.snipme.highlights.model.SyntaxThemes.useDark -import dev.snipme.kodeview.view.CodeTextView import dev.snipme.kodeview.view.material3.CodeEditText fun main() = application { val isDarkModeState = remember { mutableStateOf(false) } - val areLineNumbersEnabled = remember { mutableStateOf(true) } + val areLineNumbersEnabled = remember { mutableStateOf(false) } val isDarkMode = isDarkModeState.value val lineNumbersEnabled = areLineNumbersEnabled.value @@ -107,17 +108,21 @@ fun main() = application { .weight(1f) .fillMaxWidth(), ) { - CodeTextView( - modifier = Modifier.weight(1f), - highlights = highlights, - showLineNumbers = lineNumbersEnabled, - textStyle = MaterialTheme.typography.bodyMedium, - ) +// CodeTextView( +// modifier = Modifier.weight(1f) +// .verticalScroll(rememberScrollState()), +// highlights = highlights, +// showLineNumbers = lineNumbersEnabled, +// hasHorizontalScroll = true, +// textStyle = MaterialTheme.typography.bodyMedium, +// ) VerticalDivider(Modifier.padding(8.dp)) CodeEditText( - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f) + .verticalScroll(rememberScrollState()), highlights = highlights, showLineNumbers = lineNumbersEnabled, + hasHorizontalScroll = true, label = { Text("Edit code") }, onValueChange = { textValue -> highlightsState.value = highlights.getBuilder() diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt index 62ad281..831a1dc 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt @@ -1,34 +1,36 @@ package dev.snipme.kodeview.view import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.LocalTextStyle -import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.material.TextFieldColors import androidx.compose.material.TextFieldDefaults -import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp import copySpanStyles import dev.snipme.highlights.DefaultHighlightsResultListener -import updateIndentations import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.CodeHighlight import generateAnnotatedString -import androidx.compose.ui.unit.dp +import updateIndentations @Composable fun CodeEditText( @@ -54,6 +56,7 @@ fun CodeEditText( shape: Shape = TextFieldDefaults.TextFieldShape, colors: TextFieldColors = TextFieldDefaults.textFieldColors(), showLineNumbers: Boolean = false, + hasHorizontalScroll: Boolean = false, lineNumberTextStyle: TextStyle = textStyle.copy() ) { val currentText = remember { @@ -90,7 +93,7 @@ fun CodeEditText( val lines = currentText.value.text.lines().size Column(horizontalAlignment = Alignment.End,) { for (i in 1..lines) { - androidx.compose.material.Text( + Text( text = i.toString(), style = lineNumberTextStyle ) diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt index 6a61c16..3474ba4 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt @@ -1,8 +1,6 @@ package dev.snipme.kodeview.view import androidx.compose.foundation.background -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -17,11 +15,12 @@ fun CodeTextView( modifier: Modifier = Modifier.background(Color.Transparent), highlights: Highlights, textStyle: TextStyle = LocalTextStyle.current, + hasHorizontalScroll: Boolean = false, showLineNumbers: Boolean = false, lineNumberTextStyle: TextStyle = textStyle.copy(), async: Boolean = false, ) { - val textState = rememberTextStateWithHighlights(highlights, async = async) + val (textState, _) = rememberTextStateWithHighlights(highlights, async = async) Surface( modifier = modifier, @@ -29,11 +28,11 @@ fun CodeTextView( ) { LineNumberWrapper( text = textState.text, + hasHorizontalScroll = hasHorizontalScroll, showLineNumbers = showLineNumbers, lineNumberTextStyle = lineNumberTextStyle ) { Text( - modifier = Modifier.verticalScroll(rememberScrollState()), text = textState.annotatedString, style = textStyle ) diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt index b599d56..9d41a76 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt @@ -3,9 +3,11 @@ package dev.snipme.kodeview.view import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.Text @@ -18,16 +20,20 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp +import copySpanStyles import dev.snipme.highlights.DefaultHighlightsResultListener import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.CodeHighlight import generateAnnotatedString +import updateIndentations @Composable internal fun rememberTextStateWithHighlights( highlights: Highlights, + onValueChange: (String) -> Unit = {}, + handleIndentations: Boolean = true, async: Boolean = true, -): TextFieldValue { +): Pair Unit> { val textState = remember { mutableStateOf( TextFieldValue( @@ -36,6 +42,15 @@ internal fun rememberTextStateWithHighlights( ) } + val updateCallback: (TextFieldValue) -> Unit = { change -> + val updated = change.updateIndentations(handleIndentations) + if (updated.text != textState.value.text) { + onValueChange(updated.text) + } + + textState.value = updated.copySpanStyles(textState.value) + } + LaunchedEffect(highlights) { if (async) { highlights.getHighlightsAsync(object : DefaultHighlightsResultListener() { @@ -54,21 +69,38 @@ internal fun rememberTextStateWithHighlights( ) } - return textState.value + + + return Pair(textState.value, updateCallback) } @Composable internal fun LineNumberWrapper( modifier: Modifier = Modifier, text: String, + hasHorizontalScroll: Boolean, showLineNumbers: Boolean, lineNumberTextStyle: androidx.compose.ui.text.TextStyle, + numbersPadding: PaddingValues = PaddingValues(), content: @Composable () -> Unit ) { - Row(modifier = Modifier.fillMaxWidth()) { + val modifierWithScroll = modifier + .then( + if (showLineNumbers || hasHorizontalScroll) { + Modifier.horizontalScroll(rememberScrollState()) + } else { + Modifier + } + ) + + Row(modifier = modifier.fillMaxWidth()) { if (showLineNumbers) { val lines = text.lines().size - Column(horizontalAlignment = Alignment.End) { + Column( + modifier = Modifier + .padding(numbersPadding), + horizontalAlignment = Alignment.End + ) { for (i in 1..lines) { Text( text = i.toString(), @@ -77,11 +109,13 @@ internal fun LineNumberWrapper( } } Spacer(modifier = Modifier.width(8.dp)) - Box(modifier = modifier.horizontalScroll(rememberScrollState())) { + Box(modifier = modifierWithScroll) { content() } } else { - content() + Box(modifier = modifierWithScroll) { + content() + } } } } \ No newline at end of file diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt index 1ac40b5..c628360 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt @@ -3,12 +3,13 @@ package dev.snipme.kodeview.view.material3 import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -26,6 +27,8 @@ import copySpanStyles import dev.snipme.highlights.DefaultHighlightsResultListener import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.CodeHighlight +import dev.snipme.kodeview.view.LineNumberWrapper +import dev.snipme.kodeview.view.rememberTextStateWithHighlights import generateAnnotatedString import updateIndentations import androidx.compose.material3.LocalTextStyle as LocalTextStyle3 @@ -58,80 +61,50 @@ fun CodeEditText( shape: Shape = TextFieldDefaults3.shape, colors: TextFieldColors3 = TextFieldDefaults3.colors(), showLineNumbers: Boolean = false, + hasHorizontalScroll: Boolean = false, lineNumberTextStyle: TextStyle = textStyle.copy() ) { - val currentText = remember { - mutableStateOf( - TextFieldValue( - AnnotatedString(highlights.getCode()) + val (currentText, updateCallback) = rememberTextStateWithHighlights( + highlights, + onValueChange = onValueChange, + handleIndentations = handleIndentations, + ) + + val labelPadding = label?.run { 8.dp } ?: 0.dp + val numbersPadding = PaddingValues(top = 16.dp + labelPadding) + + Surface( + modifier = modifier.fillMaxWidth(), + ) { + LineNumberWrapper( + text = currentText.text, + showLineNumbers = showLineNumbers, + hasHorizontalScroll = hasHorizontalScroll, + lineNumberTextStyle = lineNumberTextStyle, + numbersPadding = numbersPadding, + ) { + TextField3( + onValueChange = updateCallback, + value = currentText, + enabled = enabled, + readOnly = readOnly, + textStyle = textStyle, + label = label, + placeholder = placeholder, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + isError = isError, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + singleLine = singleLine, + maxLines = maxLines, + minLines = minLines, + interactionSource = interactionSource, + shape = shape, + colors = colors, ) - ) - } - - LaunchedEffect(highlights) { - highlights.getHighlightsAsync(object : DefaultHighlightsResultListener() { - override fun onSuccess(result: List) { - currentText.value = currentText.value.copy( - annotatedString = result.generateAnnotatedString(currentText.value.text), - ) - } - }) - } - - fun updateNewValue(change: TextFieldValue) { - val updated = change.updateIndentations(handleIndentations) - if (updated.text != currentText.value.text) { - onValueChange(updated.text) } - - currentText.value = updated.copySpanStyles( - currentText.value - ) - } - - Row(modifier = modifier.fillMaxWidth()) { - if (showLineNumbers) { - val lines = currentText.value.text.lines().size.coerceAtLeast(minLines) - val labelPadding = label?.run { 8.dp } ?: 0.dp - Column( - modifier = Modifier.padding(top = 16.dp + labelPadding) - ) { - (1..lines).forEach { i -> - Text( - text = i.toString(), - style = lineNumberTextStyle, - ) - } - } - } - - val lineNumberAwareModifier = if (showLineNumbers) - modifier.horizontalScroll(rememberScrollState()) - else - modifier.padding(start = 8.dp) - - TextField3( - modifier = lineNumberAwareModifier, - onValueChange = ::updateNewValue, - value = currentText.value, - enabled = enabled, - readOnly = readOnly, - textStyle = textStyle, - label = label, - placeholder = placeholder, - leadingIcon = leadingIcon, - trailingIcon = trailingIcon, - isError = isError, - visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, - keyboardActions = keyboardActions, - singleLine = singleLine, - maxLines = maxLines, - minLines = minLines, - interactionSource = interactionSource, - shape = shape, - colors = colors, - ) } } From 07eae690791e13d54b9916205c99fa2a1be81f6b Mon Sep 17 00:00:00 2001 From: tkadziolka Date: Sun, 28 Sep 2025 20:08:14 +0200 Subject: [PATCH 4/8] Added separate highlights instances --- desktopExample/src/main/kotlin/Main.kt | 19 +++++++++++-------- .../dev/snipme/kodeview/view/CodeTextView.kt | 3 +-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/desktopExample/src/main/kotlin/Main.kt b/desktopExample/src/main/kotlin/Main.kt index 3a1613f..451dafc 100644 --- a/desktopExample/src/main/kotlin/Main.kt +++ b/desktopExample/src/main/kotlin/Main.kt @@ -35,6 +35,7 @@ import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxTheme import dev.snipme.highlights.model.SyntaxThemes import dev.snipme.highlights.model.SyntaxThemes.useDark +import dev.snipme.kodeview.view.CodeTextView import dev.snipme.kodeview.view.material3.CodeEditText fun main() = application { @@ -108,14 +109,16 @@ fun main() = application { .weight(1f) .fillMaxWidth(), ) { -// CodeTextView( -// modifier = Modifier.weight(1f) -// .verticalScroll(rememberScrollState()), -// highlights = highlights, -// showLineNumbers = lineNumbersEnabled, -// hasHorizontalScroll = true, -// textStyle = MaterialTheme.typography.bodyMedium, -// ) + CodeTextView( + modifier = Modifier.weight(1f) + .verticalScroll(rememberScrollState()), + highlights = Highlights.Builder() + .code(Samples.kotlin) + .build(), + showLineNumbers = lineNumbersEnabled, + hasHorizontalScroll = true, + textStyle = MaterialTheme.typography.bodyMedium, + ) VerticalDivider(Modifier.padding(8.dp)) CodeEditText( modifier = Modifier.weight(1f) diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt index 3474ba4..3da5295 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeTextView.kt @@ -18,9 +18,8 @@ fun CodeTextView( hasHorizontalScroll: Boolean = false, showLineNumbers: Boolean = false, lineNumberTextStyle: TextStyle = textStyle.copy(), - async: Boolean = false, ) { - val (textState, _) = rememberTextStateWithHighlights(highlights, async = async) + val (textState, _) = rememberTextStateWithHighlights(highlights) Surface( modifier = modifier, From 7bb9a8c4ce96fb33773414fbb9561a0c06e0af48 Mon Sep 17 00:00:00 2001 From: tkadziolka Date: Sun, 28 Sep 2025 21:59:01 +0200 Subject: [PATCH 5/8] Documented changes --- README.md | 1 + .../commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt | 3 ++- .../kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 05d4e36..6bfede3 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ implementation("dev.snipme:kodeview:0.10.0") - Multiple syntax languages (Java, Swift, Kotlin, C, ...) - Themes - Text bolding (emphasis) +- Line numbers - Written in pure Kotlin, so available for many platforms 📱 💻 🖥️ ## Support ☕ diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt index 831a1dc..94e2ab4 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/CodeEditText.kt @@ -32,6 +32,7 @@ import dev.snipme.highlights.model.CodeHighlight import generateAnnotatedString import updateIndentations +// TODO Refactor and test @Composable fun CodeEditText( highlights: Highlights, @@ -91,7 +92,7 @@ fun CodeEditText( Row(modifier = modifier) { if (showLineNumbers) { val lines = currentText.value.text.lines().size - Column(horizontalAlignment = Alignment.End,) { + Column(horizontalAlignment = Alignment.End) { for (i in 1..lines) { Text( text = i.toString(), diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt index c628360..ee566f2 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/material3/CodeEditText.kt @@ -108,6 +108,7 @@ fun CodeEditText( } } +// TODO Refactor and test @Composable fun CodeEditTextSwiftUi( highlights: Highlights, From a0bd50abb7d8566a88b654eea39cb230fe20e440 Mon Sep 17 00:00:00 2001 From: xJac0b Date: Thu, 2 Oct 2025 00:16:16 +0200 Subject: [PATCH 6/8] Fix $ signs in kotlin example code --- exampleCommon/src/commonMain/kotlin/Samples.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/exampleCommon/src/commonMain/kotlin/Samples.kt b/exampleCommon/src/commonMain/kotlin/Samples.kt index d656f41..9e80c93 100644 --- a/exampleCommon/src/commonMain/kotlin/Samples.kt +++ b/exampleCommon/src/commonMain/kotlin/Samples.kt @@ -9,7 +9,6 @@ class Samples { } """.trimIndent() - // TODO Fix dolars in strings val kotlin = """ package dev.example @@ -43,7 +42,7 @@ class Samples { // Inner class inner class Inner { - fun innerMethod() = "Inner class method, name: "\$"name" + fun innerMethod() = "Inner class method, name: ${'$'}name" } // Data class @@ -62,7 +61,7 @@ class Samples { // Function with default arguments fun greet(greeting: String = "Hello") { - println('"\$"greeting, "\$"name!') + println('${'$'}greeting, ${'$'}name!') } // Inline function @@ -79,7 +78,7 @@ class Samples { val lambda: (Int, Int) -> Int = { a, b -> a + b } // Extension function - fun String.addExclamation(): String = "$this!" + fun String.addExclamation(): String = "${'$'}this!" // Operator overloading operator fun plus(other: SampleClass): SampleClass { @@ -94,9 +93,9 @@ class Samples { fun riskyOperation() { try { val result = 10 / Random.nextInt(0, 2) - println("Result: "\$"result") + println("Result: ${'$'}result") } catch (e: ArithmeticException) { - println("Caught exception: "\$"{e.message}") + println("Caught exception: ${'$'}{e.message}") } finally { println("Finally block executed") } @@ -118,7 +117,7 @@ class Samples { } var count = 0 while (count < 3) { - println("Count: "\$"count") + println("Count: ${'$'}count") count++ } } From 7a295a3195ec4d9d15906a15183302ea6e7c7a3f Mon Sep 17 00:00:00 2001 From: xJac0b Date: Sun, 5 Oct 2025 20:05:18 +0200 Subject: [PATCH 7/8] Use monospace font for line numbers --- .../kotlin/dev/snipme/kodeview/view/Common.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt index 9d41a76..91b7fe7 100644 --- a/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt +++ b/kodeview/src/commonMain/kotlin/dev/snipme/kodeview/view/Common.kt @@ -18,6 +18,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import copySpanStyles @@ -84,27 +85,26 @@ internal fun LineNumberWrapper( numbersPadding: PaddingValues = PaddingValues(), content: @Composable () -> Unit ) { - val modifierWithScroll = modifier - .then( - if (showLineNumbers || hasHorizontalScroll) { - Modifier.horizontalScroll(rememberScrollState()) - } else { - Modifier - } - ) + val modifierWithScroll = modifier.then( + if (showLineNumbers || hasHorizontalScroll) { + Modifier.horizontalScroll(rememberScrollState()) + } else { + Modifier + } + ) Row(modifier = modifier.fillMaxWidth()) { if (showLineNumbers) { val lines = text.lines().size Column( - modifier = Modifier - .padding(numbersPadding), - horizontalAlignment = Alignment.End + modifier = Modifier.padding(numbersPadding), horizontalAlignment = Alignment.End ) { for (i in 1..lines) { Text( text = i.toString(), - style = lineNumberTextStyle, + style = lineNumberTextStyle.copy( + fontFamily = FontFamily.Monospace + ), ) } } From 3f0be950b7c7113e50a409112ba6bb491b2ba5a8 Mon Sep 17 00:00:00 2001 From: xJac0b Date: Mon, 6 Oct 2025 23:20:22 +0200 Subject: [PATCH 8/8] Fix and update examples for desktop, web and android --- androidExample/build.gradle.kts | 1 - .../dev/snipme/androidexample/MainActivity.kt | 153 +++++----- desktopExample/src/main/kotlin/Main.kt | 151 +++++----- exampleCommon/build.gradle.kts | 32 ++- .../src/commonMain/kotlin/Dropdown.kt | 2 - .../commonMain/kotlin/LineNumberSwitcher.kt | 2 - .../src/commonMain/kotlin/Samples.kt | 268 +++++++++--------- .../src/commonMain/kotlin/ThemeSwitcher.kt | 2 - webExample/src/jsMain/kotlin/main.js.kt | 149 +++++----- 9 files changed, 394 insertions(+), 366 deletions(-) diff --git a/androidExample/build.gradle.kts b/androidExample/build.gradle.kts index 86e114a..dd6abfc 100644 --- a/androidExample/build.gradle.kts +++ b/androidExample/build.gradle.kts @@ -55,7 +55,6 @@ dependencies { implementation(libs.activity.compose) implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material) implementation(compose.material3) implementation(compose.ui) implementation(compose.materialIconsExtended) diff --git a/androidExample/src/main/java/dev/snipme/androidexample/MainActivity.kt b/androidExample/src/main/java/dev/snipme/androidexample/MainActivity.kt index 46aa9dd..dd14a2a 100644 --- a/androidExample/src/main/java/dev/snipme/androidexample/MainActivity.kt +++ b/androidExample/src/main/java/dev/snipme/androidexample/MainActivity.kt @@ -1,16 +1,26 @@ package dev.snipme.androidexample +import Dropdown +import LineNumberSwitcher +import ThemeSwitcher import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -19,16 +29,18 @@ import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import dev.snipme.desktopexample.Dropdown -import dev.snipme.desktopexample.ThemeSwitcher import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxTheme @@ -48,59 +60,45 @@ class MainActivity : AppCompatActivity() { } } -private val sampleCode = - """ - class Main { - public static void main(String[] args) { - int abcd = 100; - } - } - """.trimIndent() - @Composable fun App() { - val isDarkModeState = remember { mutableStateOf(false) } - val areLineNumbersEnabled = remember { mutableStateOf(true) } - val isDarkMode = isDarkModeState.value - val lineNumbersEnabled = areLineNumbersEnabled.value - - val highlightsState = remember { - mutableStateOf( - Highlights.Builder(code = sampleCode).build() - ) - } + var isDarkMode by remember { mutableStateOf(false) } + var lineNumbersEnabled by remember { mutableStateOf(true) } + var currentLanguage by remember { mutableStateOf(SyntaxLanguage.DEFAULT) } - val highlights = highlightsState.value + var highlights by remember { + mutableStateOf(Highlights.Builder(code = Samples.kotlin).build()) + } fun updateSyntaxTheme(theme: SyntaxTheme) { - highlightsState.value = highlights.getBuilder() - .theme(theme) - .build() + highlights = highlights.getBuilder().theme(theme).build() } fun updateSyntaxLanguage(language: SyntaxLanguage) { - highlightsState.value = highlights.getBuilder() + highlights = highlights.getBuilder() .language(language) + .code(Samples.getSampleCode(language)) .build() + currentLanguage = language } MaterialTheme(colorScheme = if (isDarkMode) darkColorScheme() else lightColorScheme()) { - Surface { + Surface( + modifier = Modifier + .fillMaxSize() + ) { Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), + modifier = Modifier.fillMaxSize().padding(16.dp), + horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.SpaceBetween + verticalArrangement = Arrangement.Top ) { - Spacer(Modifier.height(8.dp)) - ThemeSwitcher( isDarkMode, - modifier = Modifier.fillMaxWidth(), - ) { setToDarkMode -> - isDarkModeState.value = setToDarkMode - updateSyntaxTheme(highlights.getTheme().useDark(setToDarkMode)!!) + modifier = Modifier.fillMaxWidth() + ) { setToDark -> + isDarkMode = setToDark + updateSyntaxTheme(highlights.getTheme().useDark(setToDark)!!) } Spacer(Modifier.height(16.dp)) @@ -108,69 +106,84 @@ fun App() { LineNumberSwitcher( lineNumbersEnabled, modifier = Modifier.fillMaxWidth() - ) { lineNumberEnabled -> - areLineNumbersEnabled.value = lineNumberEnabled + ) { enabled -> + lineNumbersEnabled = enabled } + Spacer(Modifier.height(16.dp)) + Text( - modifier = Modifier.fillMaxWidth(), text = "KodeView", fontSize = 18.sp, - textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center ) - Spacer(modifier = Modifier.size(16.dp)) + Spacer(Modifier.height(16.dp)) - CodeTextView(highlights = highlights, showLineNumbers = lineNumbersEnabled) + key(currentLanguage) { + CodeTextView( + modifier = Modifier.weight(1f) + .verticalScroll(rememberScrollState()), + highlights = highlights.getBuilder() + .code(Samples.getSampleCode(highlights.getLanguage())).build(), + showLineNumbers = lineNumbersEnabled, + hasHorizontalScroll = true, + textStyle = MaterialTheme.typography.bodyLarge, + ) + } - Spacer(modifier = Modifier.size(16.dp)) + Spacer(Modifier.height(16.dp)) HorizontalDivider() - Spacer(modifier = Modifier.size(16.dp)) - - Text("Edit this...") - CodeEditText( - highlights = highlights, - showLineNumbers = lineNumbersEnabled, - onValueChange = { textValue -> - highlightsState.value = highlights.getBuilder() - .code(textValue) - .build() - }, - colors = TextFieldDefaults.colors( - unfocusedContainerColor = Color.Transparent, - focusedContainerColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - errorIndicatorColor = Color.Transparent, - ), - ) + Spacer(Modifier.height(16.dp)) - Spacer(modifier = Modifier.size(16.dp)) + key(highlights.getLanguage()) { + CodeEditText( + modifier = Modifier.weight(1f) + .verticalScroll(rememberScrollState()), + highlights = highlights, + showLineNumbers = lineNumbersEnabled, + hasHorizontalScroll = true, + textStyle = MaterialTheme.typography.bodyLarge, + label = { Text("Edit code") }, + onValueChange = { newText -> + highlights = highlights.getBuilder().code(newText).build() + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent, + ), + ) + } - Spacer(modifier = Modifier.weight(1f)) + Spacer(Modifier.height(16.dp)) Dropdown( options = SyntaxThemes.getNames(), - selected = SyntaxThemes.themes().keys.indexOf(highlights.getTheme().key), + selected = SyntaxThemes.themes().keys.indexOf(highlights.getTheme().key) ) { selectedThemeName -> updateSyntaxTheme( SyntaxThemes.themes(isDarkMode)[selectedThemeName.lowercase()]!! ) } - Spacer(modifier = Modifier.size(16.dp)) + Spacer(Modifier.height(16.dp)) Dropdown( options = SyntaxLanguage.getNames(), selected = SyntaxLanguage.getNames().indexOfFirst { it.equals(highlights.getLanguage().name, ignoreCase = true) - }) { selectedLanguage -> + } + ) { selectedLanguage -> updateSyntaxLanguage(SyntaxLanguage.getByName(selectedLanguage)!!) } } } } -} \ No newline at end of file +} diff --git a/desktopExample/src/main/kotlin/Main.kt b/desktopExample/src/main/kotlin/Main.kt index 451dafc..e424c4b 100644 --- a/desktopExample/src/main/kotlin/Main.kt +++ b/desktopExample/src/main/kotlin/Main.kt @@ -1,26 +1,14 @@ package dev.snipme.desktopexample +import Dropdown +import LineNumberSwitcher import Samples -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import ThemeSwitcher +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.VerticalDivider -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign @@ -29,7 +17,6 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState -import dev.snipme.androidexample.LineNumberSwitcher import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxTheme @@ -39,51 +26,39 @@ import dev.snipme.kodeview.view.CodeTextView import dev.snipme.kodeview.view.material3.CodeEditText fun main() = application { - val isDarkModeState = remember { mutableStateOf(false) } - val areLineNumbersEnabled = remember { mutableStateOf(false) } - val isDarkMode = isDarkModeState.value - val lineNumbersEnabled = areLineNumbersEnabled.value + var isDarkMode by remember { mutableStateOf(false) } + var lineNumbersEnabled by remember { mutableStateOf(false) } + var currentLanguage by remember { mutableStateOf(SyntaxLanguage.DEFAULT) } - val highlightsState = remember { + var highlights by remember { mutableStateOf( Highlights.Builder(code = Samples.kotlin).build() ) } - val highlights = highlightsState.value fun updateSyntaxTheme(theme: SyntaxTheme) { - highlightsState.value = highlights.getBuilder() - .theme(theme) - .build() - } - - fun updateSyntaxLanguage(language: SyntaxLanguage) { - highlightsState.value = highlights.getBuilder() - .language(language) - .build() + highlights = highlights.getBuilder().theme(theme).build() } MaterialTheme(colorScheme = if (isDarkMode) darkColorScheme() else lightColorScheme()) { Window( onCloseRequest = ::exitApplication, - title = "KodeView example", + title = "KodeView Example", state = rememberWindowState() ) { Surface { Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), + modifier = Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.Center, ) { Spacer(Modifier.height(8.dp)) ThemeSwitcher( isDarkMode, - modifier = Modifier.fillMaxWidth(), - ) { setToDarkMode -> - isDarkModeState.value = setToDarkMode - updateSyntaxTheme(highlights.getTheme().useDark(setToDarkMode)!!) + modifier = Modifier.fillMaxWidth() + ) { dark -> + isDarkMode = dark + updateSyntaxTheme(highlights.getTheme().useDark(dark)!!) } Spacer(Modifier.height(16.dp)) @@ -91,8 +66,8 @@ fun main() = application { LineNumberSwitcher( lineNumbersEnabled, modifier = Modifier.fillMaxWidth() - ) { lineNumberEnabled -> - areLineNumbersEnabled.value = lineNumberEnabled + ) { enabled -> + lineNumbersEnabled = enabled } Text( @@ -102,66 +77,74 @@ fun main() = application { textAlign = TextAlign.Center, ) - Spacer(modifier = Modifier.size(16.dp)) + Spacer(Modifier.size(16.dp)) Row( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), + modifier = Modifier.weight(1f).fillMaxWidth() ) { - CodeTextView( - modifier = Modifier.weight(1f) - .verticalScroll(rememberScrollState()), - highlights = Highlights.Builder() - .code(Samples.kotlin) - .build(), - showLineNumbers = lineNumbersEnabled, - hasHorizontalScroll = true, - textStyle = MaterialTheme.typography.bodyMedium, - ) + key(currentLanguage) { + CodeTextView( + modifier = Modifier.weight(1f) + .verticalScroll(rememberScrollState()), + highlights = highlights.getBuilder() + .code(Samples.getSampleCode(highlights.getLanguage())).build(), + showLineNumbers = lineNumbersEnabled, + hasHorizontalScroll = true, + textStyle = MaterialTheme.typography.bodyMedium, + ) + } + VerticalDivider(Modifier.padding(8.dp)) - CodeEditText( - modifier = Modifier.weight(1f) - .verticalScroll(rememberScrollState()), - highlights = highlights, - showLineNumbers = lineNumbersEnabled, - hasHorizontalScroll = true, - label = { Text("Edit code") }, - onValueChange = { textValue -> - highlightsState.value = highlights.getBuilder() - .code(textValue) - .build() - }, - colors = TextFieldDefaults.colors( - unfocusedContainerColor = Color.Transparent, - focusedContainerColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - errorIndicatorColor = Color.Transparent, - ), - ) + key(highlights.getLanguage()) { + CodeEditText( + modifier = Modifier.weight(1f) + .verticalScroll(rememberScrollState()), + highlights = highlights, + showLineNumbers = lineNumbersEnabled, + hasHorizontalScroll = true, + textStyle = MaterialTheme.typography.bodyMedium, + label = { Text("Edit code") }, + onValueChange = { newText -> + highlights = highlights.getBuilder().code(newText).build() + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent, + ), + ) + } } - Spacer(modifier = Modifier.size(16.dp)) + Spacer(Modifier.size(16.dp)) Dropdown( options = SyntaxThemes.getNames(), - selected = SyntaxThemes.themes().keys.indexOf(highlights.getTheme().key), + selected = SyntaxThemes.themes().keys.indexOf(highlights.getTheme().key) ) { selectedThemeName -> updateSyntaxTheme( SyntaxThemes.themes(isDarkMode)[selectedThemeName.lowercase()]!! ) } - Spacer(modifier = Modifier.size(16.dp)) + Spacer(Modifier.size(16.dp)) Dropdown( options = SyntaxLanguage.getNames(), selected = SyntaxLanguage.getNames().indexOfFirst { it.equals(highlights.getLanguage().name, ignoreCase = true) - }) { selectedLanguage -> - updateSyntaxLanguage(SyntaxLanguage.getByName(selectedLanguage)!!) + } + ) { selectedLanguage -> + val language = SyntaxLanguage.getByName(selectedLanguage)!! + if (language == highlights.getLanguage()) return@Dropdown + currentLanguage = language + highlights = highlights.getBuilder() + .language(language) + .code(Samples.getSampleCode(language)) + .build() } } } diff --git a/exampleCommon/build.gradle.kts b/exampleCommon/build.gradle.kts index a2c4955..47c0164 100644 --- a/exampleCommon/build.gradle.kts +++ b/exampleCommon/build.gradle.kts @@ -1,11 +1,12 @@ -@Suppress("DSL_SCOPE_VIOLATION") -plugins { +@Suppress("DSL_SCOPE_VIOLATION") plugins { alias(libs.plugins.multiplatform) alias(libs.plugins.compose) alias(libs.plugins.compose.compiler) + alias(libs.plugins.android.library) } kotlin { + androidTarget() jvm() js(IR) { browser() @@ -17,11 +18,34 @@ kotlin { dependencies { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material) implementation(compose.material3) implementation(compose.ui) implementation(compose.materialIconsExtended) + implementation(project(":kodeview")) } } + + val androidMain by getting + + val jvmMain by getting + val jsMain by getting + } +} + +android { + namespace = "dev.snipme.examplecommon" + compileSdk = 34 + + defaultConfig { + minSdk = 21 } -} \ No newline at end of file + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlin { + jvmToolchain(17) // Ensure Kotlin matches Java version + } +} diff --git a/exampleCommon/src/commonMain/kotlin/Dropdown.kt b/exampleCommon/src/commonMain/kotlin/Dropdown.kt index 8a12574..1bdf446 100644 --- a/exampleCommon/src/commonMain/kotlin/Dropdown.kt +++ b/exampleCommon/src/commonMain/kotlin/Dropdown.kt @@ -1,5 +1,3 @@ -package dev.snipme.desktopexample - import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding diff --git a/exampleCommon/src/commonMain/kotlin/LineNumberSwitcher.kt b/exampleCommon/src/commonMain/kotlin/LineNumberSwitcher.kt index a8a5d90..4486c2b 100644 --- a/exampleCommon/src/commonMain/kotlin/LineNumberSwitcher.kt +++ b/exampleCommon/src/commonMain/kotlin/LineNumberSwitcher.kt @@ -1,5 +1,3 @@ -package dev.snipme.androidexample - import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer diff --git a/exampleCommon/src/commonMain/kotlin/Samples.kt b/exampleCommon/src/commonMain/kotlin/Samples.kt index 9e80c93..11dec42 100644 --- a/exampleCommon/src/commonMain/kotlin/Samples.kt +++ b/exampleCommon/src/commonMain/kotlin/Samples.kt @@ -1,143 +1,157 @@ +import dev.snipme.highlights.model.SyntaxLanguage + class Samples { companion object { - val javaStarter = - """ - class Main { - public static void main(String[] args) { + val c = """ + #include + + int main() { int abcd = 100; + printf("Value: %d\n", abcd); + return 0; } - } - """.trimIndent() - - val kotlin = """ - package dev.example - - import kotlin.random.Random - - // Class declaration with primary constructor - class SampleClass(private val name: String, var age: Int) { - - // Companion object - companion object { - const val CONSTANT = "This is a constant" - fun staticMethod() = println("This is a static method") - } - - // Properties - val isAdult: Boolean - get() = age >= 18 - - // Secondary constructor - constructor(name: String) : this(name, 0) - - // Enum class - enum class Status { - ACTIVE, INACTIVE, PENDING - } - - // Nested class - class Nested { - fun nestedMethod() = "Nested class method" - } - - // Inner class - inner class Inner { - fun innerMethod() = "Inner class method, name: ${'$'}name" - } - - // Data class - data class Data(val id: Int, val value: String) + """.trimIndent() - // Sealed class - sealed class Result { - data class Success(val data: String) : Result() - data class Failure(val error: String) : Result() - } - - // Object declaration - object Singleton { - fun doSomething() = "Singleton object method" - } - - // Function with default arguments - fun greet(greeting: String = "Hello") { - println('${'$'}greeting, ${'$'}name!') - } - - // Inline function - inline fun inlineFunction(action: () -> Unit) { - action() - } - - // Higher-order function - fun higherOrderFunction(operation: (Int, Int) -> Int): Int { - return operation(5, 10) - } - - // Lambda expression - val lambda: (Int, Int) -> Int = { a, b -> a + b } - - // Extension function - fun String.addExclamation(): String = "${'$'}this!" - - // Operator overloading - operator fun plus(other: SampleClass): SampleClass { - return SampleClass(this.name + other.name, this.age + other.age) - } + val cpp = """ + #include + using namespace std; + + int main() { + int abcd = 100; + cout << "Value: " << abcd << endl; + return 0; + } + """.trimIndent() - // Inline class - @JvmInline - value class InlineClass(val value: String) - - // Try-catch-finally - fun riskyOperation() { - try { - val result = 10 / Random.nextInt(0, 2) - println("Result: ${'$'}result") - } catch (e: ArithmeticException) { - println("Caught exception: ${'$'}{e.message}") - } finally { - println("Finally block executed") - } - } + val dart = """ + void main() { + int abcd = 100; + print("Value: ${'$'}abcd"); + } + """.trimIndent() - // When expression - fun checkStatus(status: Status): String { - return when (status) { - Status.ACTIVE -> "Active" - Status.INACTIVE -> "Inactive" - Status.PENDING -> "Pending" - } + val java = """ + class Main { + public static void main(String[] args) { + int abcd = 100; + System.out.println("Value: " + abcd); } + } + """.trimIndent() - // Loops - fun printNumbers() { - for (i in 1..5) { - println(i) - } - var count = 0 - while (count < 3) { - println("Count: ${'$'}count") - count++ - } + val javaStarter = """ + class Main { + public static void main(String[] args) { + int abcd = 100; + System.out.println("Value: " + abcd); } + } + """.trimIndent() - // Annotations - @Deprecated("This method is deprecated") - fun deprecatedMethod() { - println("Deprecated method") - } + val kotlin = """ + fun main() { + val abcd = 100 + println("Value: ${'$'}abcd") + } + """.trimIndent() - // Generics - fun genericMethod(item: T): T { - return item - } + val rust = """ + fn main() { + let abcd: i32 = 100; + println!("Value: {}", abcd); + } + """.trimIndent() - // Vararg - fun printAll(vararg items: String) { - items.forEach { println(it) } + val csharp = """ + using System; + + class Program { + static void Main() { + int abcd = 100; + Console.WriteLine("Value: " + abcd); } } - """.trimIndent() + """.trimIndent() + + val coffeescript = """ + abcd = 100 + console.log "Value: #{abcd}" + """.trimIndent() + + val javascript = """ + let abcd = 100; + console.log("Value: " + abcd); + """.trimIndent() + + val perl = """ + my \${'$'}abcd = 100; + print "Value: ${'$'}abcd\n"; + """.trimIndent() + + val python = """ + abcd = 100 + print("Value:", abcd) + """.trimIndent() + + val ruby = """ + abcd = 100 + puts "Value: #{abcd}" + """.trimIndent() + + val shell = """ + #!/bin/bash + abcd=100 + echo "Value: ${'$'}abcd" + """.trimIndent() + + val swift = """ + import Foundation + + let abcd = 100 + print("Value: \(abcd)") + """.trimIndent() + + val typescript = """ + let abcd: number = 100; + console.log("Value: " + abcd); + """.trimIndent() + + val go = """ + package main + import "fmt" + + func main() { + abcd := 100 + fmt.Println("Value:", abcd) + } + """.trimIndent() + + val php = """ + + """.trimIndent() + + fun getSampleCode(language: SyntaxLanguage): String = when (language) { + SyntaxLanguage.DEFAULT -> kotlin + SyntaxLanguage.C -> c + SyntaxLanguage.CPP -> cpp + SyntaxLanguage.DART -> dart + SyntaxLanguage.JAVA -> java + SyntaxLanguage.KOTLIN -> kotlin + SyntaxLanguage.RUST -> rust + SyntaxLanguage.CSHARP -> csharp + SyntaxLanguage.COFFEESCRIPT -> coffeescript + SyntaxLanguage.JAVASCRIPT -> javascript + SyntaxLanguage.PERL -> perl + SyntaxLanguage.PYTHON -> python + SyntaxLanguage.RUBY -> ruby + SyntaxLanguage.SHELL -> shell + SyntaxLanguage.SWIFT -> swift + SyntaxLanguage.TYPESCRIPT -> typescript + SyntaxLanguage.GO -> go + SyntaxLanguage.PHP -> php + } } -} \ No newline at end of file +} diff --git a/exampleCommon/src/commonMain/kotlin/ThemeSwitcher.kt b/exampleCommon/src/commonMain/kotlin/ThemeSwitcher.kt index f44c69f..520c26d 100644 --- a/exampleCommon/src/commonMain/kotlin/ThemeSwitcher.kt +++ b/exampleCommon/src/commonMain/kotlin/ThemeSwitcher.kt @@ -1,5 +1,3 @@ -package dev.snipme.desktopexample - import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer diff --git a/webExample/src/jsMain/kotlin/main.js.kt b/webExample/src/jsMain/kotlin/main.js.kt index 69c8999..5d89d48 100644 --- a/webExample/src/jsMain/kotlin/main.js.kt +++ b/webExample/src/jsMain/kotlin/main.js.kt @@ -1,21 +1,27 @@ - import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.VerticalDivider import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -24,9 +30,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.CanvasBasedWindow -import dev.snipme.androidexample.LineNumberSwitcher -import dev.snipme.desktopexample.Dropdown -import dev.snipme.desktopexample.ThemeSwitcher import dev.snipme.highlights.Highlights import dev.snipme.highlights.model.SyntaxLanguage import dev.snipme.highlights.model.SyntaxTheme @@ -36,15 +39,6 @@ import dev.snipme.kodeview.view.CodeTextView import dev.snipme.kodeview.view.material3.CodeEditText import org.jetbrains.skiko.wasm.onWasmReady -private val sampleCode = - """ - class Main { - public static void main(String[] args) { - int abcd = 100; - } - } - """.trimIndent() - @ExperimentalComposeUiApi fun main() { onWasmReady { @@ -53,47 +47,34 @@ fun main() { canvasElementId = "ComposeTarget", applyDefaultStyles = true, ) { - val isDarkModeState = remember { mutableStateOf(false) } - val areLineNumbersEnabled = remember { mutableStateOf(true) } - val isDarkMode = isDarkModeState.value - val lineNumbersEnabled = areLineNumbersEnabled.value + var isDarkMode by remember { mutableStateOf(false) } + var lineNumbersEnabled by remember { mutableStateOf(false) } + var currentLanguage by remember { mutableStateOf(SyntaxLanguage.DEFAULT) } - val highlightsState = remember { + var highlights by remember { mutableStateOf( - Highlights.Builder(code = sampleCode).build() + Highlights.Builder(code = Samples.kotlin).build() ) } - val highlights = highlightsState.value fun updateSyntaxTheme(theme: SyntaxTheme) { - highlightsState.value = highlights.getBuilder() - .theme(theme) - .build() - } - - fun updateSyntaxLanguage(language: SyntaxLanguage) { - highlightsState.value = highlights.getBuilder() - .language(language) - .build() + highlights = highlights.getBuilder().theme(theme).build() } MaterialTheme(colorScheme = if (isDarkMode) darkColorScheme() else lightColorScheme()) { Surface { Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.SpaceBetween + modifier = Modifier.fillMaxSize().padding(16.dp), + verticalArrangement = Arrangement.Center, ) { Spacer(Modifier.height(8.dp)) ThemeSwitcher( isDarkMode, - modifier = Modifier.fillMaxWidth(), - ) { setToDarkMode -> - isDarkModeState.value = setToDarkMode - updateSyntaxTheme(highlights.getTheme().useDark(setToDarkMode)!!) + modifier = Modifier.fillMaxWidth() + ) { dark -> + isDarkMode = dark + updateSyntaxTheme(highlights.getTheme().useDark(dark)!!) } Spacer(Modifier.height(16.dp)) @@ -101,8 +82,8 @@ fun main() { LineNumberSwitcher( lineNumbersEnabled, modifier = Modifier.fillMaxWidth() - ) { lineNumberEnabled -> - areLineNumbersEnabled.value = lineNumberEnabled + ) { enabled -> + lineNumbersEnabled = enabled } Text( @@ -112,55 +93,75 @@ fun main() { textAlign = TextAlign.Center, ) - Spacer(modifier = Modifier.size(16.dp)) - - CodeTextView(highlights = highlights) - - Spacer(modifier = Modifier.size(16.dp)) - - Divider() - - Spacer(modifier = Modifier.size(16.dp)) - - Text("Edit this...") - CodeEditText( - highlights = highlights, - onValueChange = { textValue -> - highlightsState.value = highlights.getBuilder() - .code(textValue) - .build() - }, - colors = TextFieldDefaults.colors( - unfocusedContainerColor = Color.Transparent, - focusedContainerColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - errorIndicatorColor = Color.Transparent, - ), - ) - - Spacer(modifier = Modifier.size(16.dp)) + Spacer(Modifier.size(16.dp)) + + Row( + modifier = Modifier.weight(1f).fillMaxWidth() + ) { + key(currentLanguage) { + CodeTextView( + modifier = Modifier.weight(1f) + .verticalScroll(rememberScrollState()), + highlights = highlights.getBuilder() + .code(Samples.getSampleCode(highlights.getLanguage())) + .build(), + showLineNumbers = lineNumbersEnabled, + hasHorizontalScroll = true, + textStyle = MaterialTheme.typography.bodyMedium, + ) + } + + VerticalDivider(Modifier.padding(8.dp)) + key(highlights.getLanguage()) { + CodeEditText( + modifier = Modifier.weight(1f) + .verticalScroll(rememberScrollState()), + highlights = highlights, + showLineNumbers = lineNumbersEnabled, + hasHorizontalScroll = true, + textStyle = MaterialTheme.typography.bodyMedium, + label = { Text("Edit code") }, + onValueChange = { newText -> + highlights = highlights.getBuilder().code(newText).build() + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent, + ), + ) + } + } - Spacer(modifier = Modifier.weight(1f)) + Spacer(Modifier.size(16.dp)) Dropdown( options = SyntaxThemes.getNames(), - selected = SyntaxThemes.themes().keys.indexOf(highlights.getTheme().key), + selected = SyntaxThemes.themes().keys.indexOf(highlights.getTheme().key) ) { selectedThemeName -> updateSyntaxTheme( SyntaxThemes.themes(isDarkMode)[selectedThemeName.lowercase()]!! ) } - Spacer(modifier = Modifier.size(16.dp)) + Spacer(Modifier.size(16.dp)) Dropdown( options = SyntaxLanguage.getNames(), selected = SyntaxLanguage.getNames().indexOfFirst { it.equals(highlights.getLanguage().name, ignoreCase = true) - }) { selectedLanguage -> - updateSyntaxLanguage(SyntaxLanguage.getByName(selectedLanguage)!!) + } + ) { selectedLanguage -> + val language = SyntaxLanguage.getByName(selectedLanguage)!! + if (language == highlights.getLanguage()) return@Dropdown + currentLanguage = language + highlights = highlights.getBuilder() + .language(language) + .code(Samples.getSampleCode(language)) + .build() } } }