Skip to content

Commit f016a6d

Browse files
committed
support 代码高亮
1 parent 74616eb commit f016a6d

File tree

4 files changed

+98
-28
lines changed

4 files changed

+98
-28
lines changed

composeApp/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ kotlin {
7777

7878
implementation(libs.kotlin.serialization.core)
7979
implementation(libs.kotlin.serialization.json)
80+
81+
implementation(libs.highlights)
8082
}
8183

8284
commonTest.dependencies {

composeApp/src/wasmJsMain/kotlin/love/forte/simbot/codegen/gen/view/preview/FileContentComponents.kt

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
11
package love.forte.simbot.codegen.gen.view.preview
22

3-
import androidx.compose.foundation.background
4-
import androidx.compose.foundation.horizontalScroll
3+
import androidx.compose.foundation.*
54
import androidx.compose.foundation.layout.*
6-
import androidx.compose.foundation.rememberScrollState
7-
import androidx.compose.foundation.rememberScrollbarAdapter
85
import androidx.compose.foundation.text.selection.SelectionContainer
9-
import androidx.compose.foundation.verticalScroll
10-
import androidx.compose.foundation.VerticalScrollbar
11-
import androidx.compose.foundation.HorizontalScrollbar
126
import androidx.compose.material.icons.Icons
137
import androidx.compose.material.icons.filled.ContentCopy
148
import androidx.compose.material.icons.filled.Description
159
import androidx.compose.material3.*
1610
import androidx.compose.runtime.*
1711
import androidx.compose.ui.Alignment
1812
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.graphics.Color
1914
import androidx.compose.ui.platform.ClipEntry
2015
import androidx.compose.ui.platform.LocalClipboard
16+
import androidx.compose.ui.text.AnnotatedString
17+
import androidx.compose.ui.text.SpanStyle
18+
import androidx.compose.ui.text.buildAnnotatedString
2119
import androidx.compose.ui.text.font.FontFamily
2220
import androidx.compose.ui.text.font.FontWeight
2321
import androidx.compose.ui.text.style.TextAlign
24-
import androidx.compose.ui.unit.TextUnit
2522
import androidx.compose.ui.unit.dp
2623
import androidx.compose.ui.unit.sp
24+
import dev.snipme.highlights.Highlights
25+
import dev.snipme.highlights.model.BoldHighlight
26+
import dev.snipme.highlights.model.ColorHighlight
27+
import dev.snipme.highlights.model.SyntaxLanguage
2728
import kotlinx.coroutines.launch
2829
import org.jetbrains.compose.resources.Font
2930
import simbot_codegen.composeapp.generated.resources.JetBrainsMono_Medium
3031
import simbot_codegen.composeapp.generated.resources.Res
32+
import web.cssom.CSS.highlights
3133

3234
/**
3335
* 文件内容预览组件
@@ -246,7 +248,8 @@ private fun FileContentBody(content: FileContent) {
246248
.verticalScroll(verticalScrollState)
247249
) {
248250
CodeContent(
249-
content = content.content
251+
content = content.content,
252+
mimeType = content.mimeType
250253
)
251254
}
252255
}
@@ -304,9 +307,11 @@ private fun LineNumbers(content: String) {
304307

305308
/**
306309
* 代码内容显示
310+
*
311+
* @param mimeType see [FileContent.inferMimeType]
307312
*/
308313
@Composable
309-
private fun CodeContent(content: String, lineHeight: TextUnit = 24.sp) {
314+
private fun CodeContent(content: String, mimeType: String? = null) {
310315
val jetBrainsMonoFontFamily = FontFamily(
311316
Font(Res.font.JetBrainsMono_Medium, FontWeight.Medium)
312317
)
@@ -317,19 +322,80 @@ private fun CodeContent(content: String, lineHeight: TextUnit = 24.sp) {
317322
lineHeight = 24.sp // 与行号保持一致的行高
318323
)
319324

325+
val lang: SyntaxLanguage = remember(mimeType) {
326+
when {
327+
mimeType == null -> SyntaxLanguage.DEFAULT
328+
mimeType.endsWith("java", true) -> SyntaxLanguage.JAVA
329+
mimeType.endsWith("js", true) -> SyntaxLanguage.JAVASCRIPT
330+
mimeType.endsWith("kt", true) -> SyntaxLanguage.KOTLIN
331+
mimeType.endsWith("kts", true) -> SyntaxLanguage.KOTLIN
332+
mimeType.endsWith("sh", true) -> SyntaxLanguage.SHELL
333+
else -> SyntaxLanguage.DEFAULT
334+
}
335+
}
336+
337+
val contentString = remember(content) {
338+
val highlights = Highlights.Builder()
339+
.language(lang)
340+
.code(content)
341+
.build()
342+
343+
buildAnnotatedString {
344+
append(content)
345+
for (highlight in highlights.getHighlights()) {
346+
val location = highlight.location
347+
when (highlight) {
348+
is ColorHighlight -> {
349+
val rgb: Int = highlight.rgb
350+
val color = Color(
351+
red = rgb shr 16 and 0xFF,
352+
green = rgb shr 8 and 0xFF00,
353+
blue = rgb and 0xFF,
354+
)
355+
addStyle(SpanStyle(color = color), location.start, location.end)
356+
}
357+
358+
is BoldHighlight -> {
359+
addStyle(SpanStyle(fontWeight = FontWeight.Bold), location.start, location.end)
360+
}
361+
}
362+
}
363+
}
364+
}
365+
320366
SelectionContainer {
321367
Column {
322-
content.split('\n').forEach { line ->
368+
@Composable
369+
fun lineContent(line: AnnotatedString) {
323370
Box(
324371
modifier = Modifier.height(24.dp), // 固定高度确保对齐
325372
contentAlignment = Alignment.CenterStart
326373
) {
327-
Text(
328-
text = if (line.isEmpty()) " " else line, // 空行显示空格以保持高度
329-
style = textStyle,
330-
)
374+
Row {
375+
Text(
376+
text = line.ifEmpty { AnnotatedString(" ") }, // 空行显示空格以保持高度
377+
style = textStyle,
378+
maxLines = 1,
379+
)
380+
Text(text = "\n", maxLines = 1)
381+
}
331382
}
332383
}
384+
385+
386+
var nextStart = 0
387+
var nextEnd = content.indexOf('\n')
388+
while (nextEnd != -1) {
389+
val line = contentString.subSequence(nextStart, nextEnd)
390+
lineContent(line)
391+
nextStart = nextEnd + 1
392+
nextEnd = content.indexOf('\n', nextStart)
393+
}
394+
395+
if (nextStart < content.length) {
396+
val line = contentString.subSequence(nextStart, content.length)
397+
lineContent(line)
398+
}
333399
}
334400
}
335401
}
@@ -344,4 +410,4 @@ private fun formatFileSize(bytes: Long): String {
344410
bytes < 1024 * 1024 -> "${bytes / 1024}KB"
345411
else -> "${bytes / (1024 * 1024)}MB"
346412
}
347-
}
413+
}

composeApp/src/wasmJsMain/kotlin/love/forte/simbot/codegen/gen/view/preview/FileTreeComponents.kt

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
package love.forte.simbot.codegen.gen.view.preview
22

3-
import androidx.compose.animation.AnimatedVisibility
4-
import androidx.compose.animation.expandVertically
5-
import androidx.compose.animation.fadeIn
6-
import androidx.compose.animation.fadeOut
7-
import androidx.compose.animation.shrinkVertically
3+
import androidx.compose.animation.*
84
import androidx.compose.foundation.background
95
import androidx.compose.foundation.clickable
10-
import androidx.compose.foundation.hoverable
116
import androidx.compose.foundation.interaction.MutableInteractionSource
127
import androidx.compose.foundation.layout.*
138
import androidx.compose.foundation.lazy.LazyColumn
149
import androidx.compose.foundation.lazy.items
1510
import androidx.compose.foundation.shape.RoundedCornerShape
1611
import androidx.compose.material.icons.Icons
12+
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
1713
import androidx.compose.material.icons.filled.Description
1814
import androidx.compose.material.icons.filled.Folder
1915
import androidx.compose.material.icons.filled.FolderOpen
2016
import androidx.compose.material.icons.filled.KeyboardArrowDown
21-
import androidx.compose.material.icons.filled.KeyboardArrowRight
22-
import androidx.compose.material3.*
23-
import androidx.compose.runtime.*
17+
import androidx.compose.material3.Icon
18+
import androidx.compose.material3.MaterialTheme
19+
import androidx.compose.material3.Text
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.mutableStateOf
22+
import androidx.compose.runtime.remember
2423
import androidx.compose.ui.Alignment
2524
import androidx.compose.ui.Modifier
2625
import androidx.compose.ui.draw.clip
@@ -137,7 +136,7 @@ private fun FileTreeNode(
137136
imageVector = if (isExpanded) {
138137
Icons.Default.KeyboardArrowDown
139138
} else {
140-
Icons.Default.KeyboardArrowRight
139+
Icons.AutoMirrored.Filled.KeyboardArrowRight
141140
},
142141
contentDescription = if (isExpanded) "折叠" else "展开",
143142
modifier = Modifier.size(18.dp),
@@ -297,4 +296,4 @@ private fun formatFileSize(bytes: Long): String {
297296
bytes < 1024 * 1024 -> "${bytes / 1024}KB"
298297
else -> "${bytes / (1024 * 1024)}MB"
299298
}
300-
}
299+
}

gradle/libs.versions.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ kotlin-serialization = "1.9.0"
1111
kotlinpoet = "1.19.0-KMP-SNAPSHOT"
1212
codegentle = "0.0.1-SNAPSHOT"
1313

14-
# https://github.com/NeoUtils/Highlight
1514
# 代码高亮
15+
## https://github.com/SnipMeDev/Highlights
16+
highlights = "1.0.0"
1617

1718
[libraries]
1819
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
@@ -44,6 +45,8 @@ kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializat
4445
codegentle-kotlin = { module = "love.forte.codegentle:codegentle-kotlin", version.ref = "codegentle" }
4546
codegentle-java = { module = "love.forte.codegentle:codegentle-java", version.ref = "codegentle" }
4647

48+
highlights = { module = "dev.snipme:highlights", version.ref = "highlights" }
49+
4750
[plugins]
4851
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
4952
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

0 commit comments

Comments
 (0)