Skip to content

Commit f5ac0fd

Browse files
Merge pull request #121 from snappdevelopment/support-search-alternative
Support searching the JsonTree
2 parents d34f641 + efa5c62 commit f5ac0fd

File tree

14 files changed

+1486
-166
lines changed

14 files changed

+1486
-166
lines changed

.github/workflows/check-pr.yaml

+5-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ jobs:
7777
java-version: '17'
7878

7979
- name: Run tests
80-
run: ./gradlew :jsontree:jvmTest --tests com.sebastianneubauer.jsontree.JsonTreeParserTest --stacktrace
80+
run: |
81+
./gradlew :jsontree:jvmTest --tests com.sebastianneubauer.jsontree.JsonTreeParserTest --stacktrace
82+
./gradlew :jsontree:jvmTest --tests com.sebastianneubauer.jsontree.JsonTreeSearchTest --stacktrace
83+
./gradlew :jsontree:jvmTest --tests com.sebastianneubauer.jsontree.SearchStateTest --stacktrace
84+
./gradlew :jsontree:jvmTest --tests com.sebastianneubauer.jsontree.ExtensionsTest --stacktrace
8185
8286
detekt:
8387
runs-on: macos-latest

build.gradle.kts

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ val kotlinFiles = "**/*.kt"
2929
val sampleModuleFiles = "**/sample/**"
3030
val resourceFiles = "**/resources/**"
3131
val buildFiles = "**/build/**"
32+
val testFiles = "**/commonTest/**"
3233

3334
tasks.register<Detekt>("detektAll") {
3435
val autoFix = project.hasProperty("detektAutoFix")
@@ -57,13 +58,13 @@ tasks.register<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask>("detektGene
5758
baseline.set(baselineFile)
5859
config.setFrom(configFiles)
5960
include(kotlinFiles)
60-
exclude(sampleModuleFiles, resourceFiles, buildFiles)
61+
exclude(sampleModuleFiles, resourceFiles, buildFiles, testFiles)
6162
}
6263

6364

6465
tasks.withType<Detekt>().configureEach {
6566
include(kotlinFiles)
66-
exclude(sampleModuleFiles, resourceFiles, buildFiles)
67+
exclude(sampleModuleFiles, resourceFiles, buildFiles, testFiles)
6768
}
6869

6970
dependencies {

detekt/config.yml

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ config:
66
naming:
77
FunctionNaming:
88
functionPattern: '[a-zA-Z][a-zA-Z0-9]*'
9+
MatchingDeclarationName:
10+
active: false
911

1012
complexity:
1113
TooManyFunctions:

jsontree/src/commonMain/kotlin/com/sebastianneubauer/jsontree/JsonTree.kt

+72-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
99
import androidx.compose.foundation.layout.padding
1010
import androidx.compose.foundation.layout.size
1111
import androidx.compose.foundation.lazy.LazyColumn
12+
import androidx.compose.foundation.lazy.LazyListState
1213
import androidx.compose.foundation.lazy.itemsIndexed
14+
import androidx.compose.foundation.lazy.rememberLazyListState
1315
import androidx.compose.material3.Icon
1416
import androidx.compose.material3.LocalTextStyle
1517
import androidx.compose.material3.Text
@@ -26,6 +28,12 @@ import androidx.compose.ui.text.TextStyle
2628
import androidx.compose.ui.unit.Dp
2729
import androidx.compose.ui.unit.dp
2830
import androidx.compose.ui.unit.times
31+
import com.sebastianneubauer.jsontree.search.JsonTreeSearch
32+
import com.sebastianneubauer.jsontree.search.SearchState
33+
import com.sebastianneubauer.jsontree.search.SearchState.SearchResult
34+
import com.sebastianneubauer.jsontree.search.rememberSearchState
35+
import com.sebastianneubauer.jsontree.util.rememberCollapsableText
36+
import com.sebastianneubauer.jsontree.util.rememberPrimitiveText
2937
import jsontree.jsontree.generated.resources.Res
3038
import jsontree.jsontree.generated.resources.jsontree_arrow_right
3139
import kotlinx.coroutines.Dispatchers
@@ -67,6 +75,7 @@ public fun JsonTree(
6775
showIndices: Boolean = false,
6876
showItemCount: Boolean = true,
6977
expandSingleChildren: Boolean = false,
78+
searchState: SearchState = rememberSearchState(),
7079
onError: (Throwable) -> Unit = {}
7180
) {
7281
val jsonParser = remember(json) {
@@ -76,12 +85,18 @@ public fun JsonTree(
7685
mainDispatcher = Dispatchers.Main
7786
)
7887
}
79-
val coroutineScope = rememberCoroutineScope()
88+
val jsonSearch = remember {
89+
JsonTreeSearch(defaultDispatcher = Dispatchers.Default)
90+
}
8091

8192
LaunchedEffect(jsonParser, initialState) {
93+
searchState.reset()
8294
jsonParser.init(initialState)
8395
}
8496

97+
val lazyListState = rememberLazyListState()
98+
val coroutineScope = rememberCoroutineScope()
99+
85100
when (val state = jsonParser.state.value) {
86101
is JsonTreeParserState.Ready -> {
87102
Box(modifier = modifier) {
@@ -94,15 +109,40 @@ public fun JsonTree(
94109
textStyle = textStyle,
95110
showIndices = showIndices,
96111
showItemCount = showItemCount,
112+
searchResult = searchState.state,
113+
lazyListState = lazyListState,
97114
onClick = {
98115
coroutineScope.launch {
116+
// reset the search state, because the result indices won't match the new list
117+
searchState.reset()
99118
jsonParser.expandOrCollapseItem(
100119
item = it,
101120
expandSingleChildren = expandSingleChildren
102121
)
103122
}
104123
}
105124
)
125+
126+
val searchQuery = searchState.state.query
127+
LaunchedEffect(searchQuery) {
128+
if (searchQuery == null) {
129+
searchState.reset()
130+
} else {
131+
val expandedList = jsonParser.expandAllItems()
132+
val searchResult = jsonSearch.search(
133+
searchQuery = searchQuery,
134+
jsonTreeList = expandedList,
135+
)
136+
searchState.state = searchResult
137+
}
138+
}
139+
140+
val selectedListIndex = searchState.state.selectedOccurrence?.occurrence?.listIndex
141+
LaunchedEffect(selectedListIndex) {
142+
if (selectedListIndex != null && selectedListIndex > -1 && !lazyListState.isScrollInProgress) {
143+
lazyListState.animateScrollToItem(selectedListIndex)
144+
}
145+
}
106146
}
107147
}
108148
is JsonTreeParserState.Loading -> onLoading()
@@ -121,14 +161,24 @@ private fun JsonTreeList(
121161
textStyle: TextStyle,
122162
showIndices: Boolean,
123163
showItemCount: Boolean,
164+
searchResult: SearchResult,
165+
lazyListState: LazyListState,
124166
onClick: (JsonTreeElement) -> Unit,
125167
) {
126168
LazyColumn(
169+
state = lazyListState,
127170
contentPadding = contentPadding
128171
) {
129172
itemsIndexed(items, key = { _, item -> item.id }) { index, item ->
130173
when (item) {
131174
is JsonTreeElement.Collapsable.Array -> {
175+
val searchOccurrence = searchResult.occurrences[index]
176+
val selectedRange = if (searchResult.selectedOccurrence?.occurrence?.listIndex == index) {
177+
searchResult.selectedOccurrence.range
178+
} else {
179+
null
180+
}
181+
132182
val coloredText = rememberCollapsableText(
133183
type = CollapsableType.ARRAY,
134184
key = item.key,
@@ -138,7 +188,9 @@ private fun JsonTreeList(
138188
isLastItem = item.isLastItem,
139189
showIndices = showIndices,
140190
showItemCount = showItemCount,
141-
parentType = item.parentType
191+
searchOccurrence = searchOccurrence,
192+
searchOccurrenceSelectedRange = selectedRange,
193+
parentType = item.parentType,
142194
)
143195

144196
Collapsable(
@@ -157,13 +209,22 @@ private fun JsonTreeList(
157209
)
158210
}
159211
is JsonTreeElement.Collapsable.Object -> {
212+
val searchOccurrence = searchResult.occurrences[index]
213+
val selectedRange = if (searchResult.selectedOccurrence?.occurrence?.listIndex == index) {
214+
searchResult.selectedOccurrence.range
215+
} else {
216+
null
217+
}
218+
160219
val coloredText = rememberCollapsableText(
161220
type = CollapsableType.OBJECT,
162221
key = item.key,
163222
childItemCount = item.children.size,
164223
state = item.state,
165224
colors = colors,
166225
isLastItem = item.isLastItem,
226+
searchOccurrence = searchOccurrence,
227+
searchOccurrenceSelectedRange = selectedRange,
167228
showIndices = showIndices,
168229
showItemCount = showItemCount,
169230
parentType = item.parentType
@@ -185,11 +246,20 @@ private fun JsonTreeList(
185246
)
186247
}
187248
is JsonTreeElement.Primitive -> {
249+
val searchOccurrence = searchResult.occurrences[index]
250+
val selectedRange = if (searchResult.selectedOccurrence?.occurrence?.listIndex == index) {
251+
searchResult.selectedOccurrence.range
252+
} else {
253+
null
254+
}
255+
188256
val coloredText = rememberPrimitiveText(
189257
key = item.key,
190258
value = item.value,
191259
colors = colors,
192260
isLastItem = item.isLastItem,
261+
searchOccurrence = searchOccurrence,
262+
searchOccurrenceSelectedRange = selectedRange,
193263
showIndices = showIndices,
194264
parentType = item.parentType
195265
)

0 commit comments

Comments
 (0)