11package 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.*
54import androidx.compose.foundation.layout.*
6- import androidx.compose.foundation.rememberScrollState
7- import androidx.compose.foundation.rememberScrollbarAdapter
85import androidx.compose.foundation.text.selection.SelectionContainer
9- import androidx.compose.foundation.verticalScroll
10- import androidx.compose.foundation.VerticalScrollbar
11- import androidx.compose.foundation.HorizontalScrollbar
126import androidx.compose.material.icons.Icons
137import androidx.compose.material.icons.filled.ContentCopy
148import androidx.compose.material.icons.filled.Description
159import androidx.compose.material3.*
1610import androidx.compose.runtime.*
1711import androidx.compose.ui.Alignment
1812import androidx.compose.ui.Modifier
13+ import androidx.compose.ui.graphics.Color
1914import androidx.compose.ui.platform.ClipEntry
2015import 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
2119import androidx.compose.ui.text.font.FontFamily
2220import androidx.compose.ui.text.font.FontWeight
2321import androidx.compose.ui.text.style.TextAlign
24- import androidx.compose.ui.unit.TextUnit
2522import androidx.compose.ui.unit.dp
2623import 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
2728import kotlinx.coroutines.launch
2829import org.jetbrains.compose.resources.Font
2930import simbot_codegen.composeapp.generated.resources.JetBrainsMono_Medium
3031import 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+ }
0 commit comments