1+ import androidx.compose.ui.graphics.Color
2+ import androidx.compose.ui.text.AnnotatedString
3+ import androidx.compose.ui.text.ExperimentalTextApi
4+ import androidx.compose.ui.text.SpanStyle
5+ import androidx.compose.ui.text.buildAnnotatedString
6+ import androidx.compose.ui.text.style.TextDecoration
17import kotlinx.coroutines.*
28import kotlinx.coroutines.flow.MutableStateFlow
39import kotlinx.coroutines.flow.StateFlow
@@ -49,7 +55,7 @@ class MainScreen(
4955 fun runScript (){
5056 scope.launch(Dispatchers .Main ) {
5157 _uiState .update { it.copy(
52- scriptOutput = " "
58+ scriptOutput = AnnotatedString ( " " )
5359 )}
5460 if (! File (scriptFile).exists()){
5561 showUiMessage(" Script file not found" )
@@ -89,9 +95,10 @@ class MainScreen(
8995 }
9096
9197 _uiState .update { it.copy(
92- scriptOutput = it.scriptOutput + line + " \n "
98+ scriptOutput = it.scriptOutput + AnnotatedString ( line + " \n " )
9399 )}
94100 println (" Current line is: $line " )
101+ highlightedOutput()
95102 }
96103 } else {
97104 delay(100 )
@@ -113,9 +120,10 @@ class MainScreen(
113120 val line = results.readLine()
114121 if (line != null ) {
115122 _uiState .update { it.copy(
116- scriptOutput = it.scriptOutput + line + " \n "
123+ scriptOutput = it.scriptOutput + AnnotatedString ( line + " \n " )
117124 )}
118125 println (" Final line: $line " )
126+ highlightedOutput()
119127 }
120128 }
121129 _uiState .update { it.copy(isProcessRunning = process.isAlive) }
@@ -216,7 +224,7 @@ class MainScreen(
216224
217225 fun copyOutput (){
218226 val clipboard = Toolkit .getDefaultToolkit().systemClipboard
219- val selection = StringSelection (_uiState .value.scriptOutput)
227+ val selection = StringSelection (_uiState .value.scriptOutput.text )
220228 scope.launch(Dispatchers .IO ) {
221229 clipboard.setContents(
222230 selection,
@@ -242,6 +250,30 @@ class MainScreen(
242250 _uiState .update { it.copy(userInput = " " )}
243251 }
244252
253+ @OptIn(ExperimentalTextApi ::class )
254+ fun highlightedOutput () {
255+ val errorLocationRegex = Regex (" ([\\ w/.-]+\\ .\\ w+):(\\ d+):(\\ d+)" )
256+
257+ val originalAnnotatedString = _uiState .value.scriptOutput
258+ val newAnnotatedString = buildAnnotatedString {
259+ append(originalAnnotatedString)
260+
261+ errorLocationRegex.findAll(originalAnnotatedString.text).forEach { matchResult ->
262+ val matchRange = matchResult.range
263+ println (" Found match '${matchResult.value} ' at range $matchRange " )
264+ addStyle(
265+ style = SpanStyle (
266+ color = if (_uiState .value.darkMode) Color (0xFF77bbd1 ) else Color .Blue ,
267+ textDecoration = TextDecoration .Underline
268+ ),
269+ start = matchRange.first,
270+ end = matchRange.last + 1
271+ )
272+ }
273+ }
274+ _uiState .update { it.copy(scriptOutput = newAnnotatedString) }
275+ }
276+
245277 fun toggleDarkMode () {
246278 val newDarkMode = ! _uiState .value.darkMode
247279 _uiState .value = _uiState .value.copy(darkMode = newDarkMode)
@@ -255,7 +287,7 @@ class MainScreen(
255287 data class UiState (
256288 val darkMode : Boolean = false ,
257289 val scriptInput : String = " " ,
258- val scriptOutput : String = " " ,
290+ val scriptOutput : AnnotatedString = AnnotatedString ("") ,
259291 val uiMessage : String = " " ,
260292 val showReadLineField : Boolean = false ,
261293 val userInput : String = " " ,
0 commit comments