Skip to content

Commit a90b3c7

Browse files
committed
implemented clickable error location navigation
included syntax highlighting and click-to-navigate functionality for error locations in script output. Error locations (file:line:col) are highlighted in blue with underlines and clicking navigates directly to the corresponding position in the code editor(left side).
1 parent 612a996 commit a90b3c7

File tree

3 files changed

+91
-11
lines changed

3 files changed

+91
-11
lines changed

src/main/kotlin/MainScreen.kt

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import androidx.compose.ui.graphics.Color
22
import androidx.compose.ui.text.AnnotatedString
33
import androidx.compose.ui.text.ExperimentalTextApi
4+
import androidx.compose.ui.text.LinkAnnotation
45
import androidx.compose.ui.text.SpanStyle
6+
import androidx.compose.ui.text.TextRange
57
import androidx.compose.ui.text.buildAnnotatedString
8+
import androidx.compose.ui.text.input.TextFieldValue
69
import androidx.compose.ui.text.style.TextDecoration
710
import kotlinx.coroutines.*
811
import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,7 +42,12 @@ class MainScreen(
3942
showUiMessage("Loading file content...")
4043
delay(1600)
4144
withContext(Dispatchers.Main){
42-
_uiState.update { it.copy(scriptInput = fileContent) }
45+
_uiState.update { it.copy(
46+
scriptInputValue = TextFieldValue(
47+
text = fileContent,
48+
selection = TextRange.Zero
49+
)
50+
)}
4351
}
4452
showUiMessage("")
4553
}
@@ -48,8 +56,8 @@ class MainScreen(
4856
}
4957
}
5058

51-
fun onEditScript(text: String){
52-
_uiState.update { it.copy(scriptInput = text) }
59+
fun onEditScript(textFieldValue: TextFieldValue){
60+
_uiState.update { it.copy(scriptInputValue = textFieldValue) }
5361
}
5462

5563
fun runScript(){
@@ -195,7 +203,7 @@ class MainScreen(
195203

196204
scope.launch(Dispatchers.IO) {
197205
database.saveScriptToAFile(
198-
script = currentUiState.scriptInput,
206+
script = currentUiState.scriptInputValue.text,
199207
scope = scope
200208
)
201209
}
@@ -260,7 +268,29 @@ class MainScreen(
260268

261269
errorLocationRegex.findAll(originalAnnotatedString.text).forEach { matchResult ->
262270
val matchRange = matchResult.range
263-
println("Found match '${matchResult.value}' at range $matchRange")
271+
val filename = matchResult.groupValues[1]
272+
val lineNumber = matchResult.groupValues[2].toInt()
273+
val columnNumber = matchResult.groupValues[3].toInt()
274+
275+
println("Found error location: $filename:$lineNumber:$columnNumber")
276+
277+
addLink(
278+
url = LinkAnnotation.Url(
279+
url = "error://$lineNumber:$columnNumber",
280+
linkInteractionListener = { annotation ->
281+
val url = (annotation as LinkAnnotation.Url).url
282+
val locationData = url.removePrefix("error://")
283+
val parts = locationData.split(":")
284+
if (parts.size == 2) {
285+
val line = parts[0].toInt()
286+
val column = parts[1].toInt()
287+
navigateToLocation(line, column)
288+
}
289+
}
290+
),
291+
start = matchRange.first,
292+
end = matchRange.last + 1
293+
)
264294
addStyle(
265295
style = SpanStyle(
266296
color = if(_uiState.value.darkMode) Color(0xFF77bbd1) else Color.Blue,
@@ -274,6 +304,36 @@ class MainScreen(
274304
_uiState.update { it.copy(scriptOutput = newAnnotatedString) }
275305
}
276306

307+
private fun navigateToLocation(lineNumber: Int, columnNumber: Int) {
308+
val currentText = _uiState.value.scriptInputValue.text
309+
val lines = currentText.split('\n')
310+
311+
if (lineNumber <= lines.size && lineNumber > 0) {
312+
var position = 0
313+
for (i in 0 until lineNumber - 1) {
314+
position += lines[i].length + 1
315+
}
316+
317+
position += (columnNumber - 1).coerceAtMost(lines[lineNumber - 1].length)
318+
319+
_uiState.update { currentState ->
320+
currentState.copy(
321+
scriptInputValue = currentState.scriptInputValue.copy(
322+
selection = TextRange(position, position)
323+
),
324+
shouldFocusEditor = true
325+
)
326+
}
327+
328+
showUiMessage("Navigated to line $lineNumber, column $columnNumber")
329+
scope.launch {
330+
delay(2000)
331+
showUiMessage("")
332+
_uiState.update { it.copy(shouldFocusEditor = false) }
333+
}
334+
}
335+
}
336+
277337
fun toggleDarkMode() {
278338
val newDarkMode = !_uiState.value.darkMode
279339
_uiState.value = _uiState.value.copy(darkMode = newDarkMode)
@@ -286,13 +346,14 @@ class MainScreen(
286346

287347
data class UiState(
288348
val darkMode: Boolean = false,
289-
val scriptInput: String = "",
349+
val scriptInputValue: TextFieldValue = TextFieldValue(""),
290350
val scriptOutput: AnnotatedString = AnnotatedString(""),
291351
val uiMessage: String = "",
292352
val showReadLineField: Boolean = false,
293353
val userInput: String = "",
294354
val isSubmitInputClicked: Boolean? = null,
295355
val isWaitingForInput: Boolean = false,
296-
val isProcessRunning: Boolean = false
356+
val isProcessRunning: Boolean = false,
357+
val shouldFocusEditor: Boolean = false
297358
)
298359
}

src/main/kotlin/ui/ScriptEntry.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import androidx.compose.material3.OutlinedTextField
88
import androidx.compose.material3.OutlinedTextFieldDefaults
99
import androidx.compose.material3.Text
1010
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.LaunchedEffect
12+
import androidx.compose.runtime.remember
1113
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.focus.FocusRequester
15+
import androidx.compose.ui.focus.focusRequester
1216
import androidx.compose.ui.text.TextStyle
1317
import theme.getJetbrainsMonoFamily
1418

@@ -18,9 +22,17 @@ fun ScriptEntry(
1822
uiState: MainScreen.UiState,
1923
viewModel: MainScreen
2024
){
25+
val focusRequester = remember { FocusRequester() }
26+
27+
LaunchedEffect(uiState.shouldFocusEditor) {
28+
if (uiState.shouldFocusEditor) {
29+
focusRequester.requestFocus()
30+
}
31+
}
32+
2133
OutlinedTextField(
22-
value = uiState.scriptInput,
23-
onValueChange = { text -> viewModel.onEditScript(text) },
34+
value = uiState.scriptInputValue,
35+
onValueChange = { textFieldValue -> viewModel.onEditScript(textFieldValue) },
2436
textStyle = TextStyle(
2537
fontFamily = getJetbrainsMonoFamily(),
2638
fontStyle = MaterialTheme.typography.bodyMedium.fontStyle
@@ -38,7 +50,8 @@ fun ScriptEntry(
3850
},
3951
modifier = modifier
4052
.fillMaxSize()
41-
.animateContentSize(),
53+
.animateContentSize()
54+
.focusRequester(focusRequester),
4255
shape = MaterialTheme.shapes.large
4356
)
4457
}

src/main/kotlin/ui/ScriptOutput.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import androidx.compose.ui.draw.clip
2121
import androidx.compose.ui.graphics.graphicsLayer
2222
import androidx.compose.ui.input.pointer.PointerIcon
2323
import androidx.compose.ui.input.pointer.pointerHoverIcon
24+
import androidx.compose.ui.text.TextStyle
2425
import androidx.compose.ui.unit.dp
26+
import theme.getJetbrainsMonoFamily
2527

2628
@OptIn(ExperimentalMaterial3Api::class)
2729
@Composable
@@ -52,7 +54,11 @@ fun ScriptOutput(
5254
SelectionContainer {
5355
Text(
5456
text = uiState.scriptOutput,
55-
color = MaterialTheme.colorScheme.onSecondaryContainer,
57+
style = TextStyle(
58+
color = MaterialTheme.colorScheme.onSecondaryContainer,
59+
fontFamily = getJetbrainsMonoFamily(),
60+
fontSize = MaterialTheme.typography.bodyMedium.fontSize
61+
),
5662
modifier = Modifier.verticalScroll(outputScrollState)
5763
)
5864
}

0 commit comments

Comments
 (0)