ADFA-936: Filter and search logs#1533
Conversation
Mechanical prep for search & filter in the output tabs: - Wrap the log/build-output editors in a vertical LinearLayout with a ViewStub slot for the upcoming filter bar; the editor keeps its id. - NonEditableEditorFragment now resolves the editor via binding.editor instead of the (formerly editor-typed) layout root. - EditorSearchLayout gains opt-out flags for read-only hosts: hide the replace action and skip the collapsed-sheet bottom margin. Also adds refreshSearch()/isSearchModeActive() so hosts can re-run a query after replacing editor content. Existing call site is unchanged (defaults). - New layout_log_filter_bar.xml (text filter input + level filter chips) and its strings.
…ering LogLine level/tag metadata used to be discarded (and the pooled instance recycled) when a line was flattened to a string in LogViewModel.submit(), which made level filtering impossible and relied on SharedFlow replay to restore logs on re-collection. - New LogFilter (enabled-levels set + case-insensitive text match) and LogBuffer (bounded, thread-safe entry history with sequence numbers, trimmed like the editor: 5000 -> 4700). - LogViewModel now emits snapshot-then-tail UiEvents: on every (re)collection or filter change, a SetText snapshot of the filtered history, then Appends for matching live lines. The snapshot is taken in the live stream's onSubscription and stitched by sequence number, so no line is missed or duplicated. This replaces replay-as-history and also fixes cleared logs resurrecting after rotation. - GlobalBufferAppender.Consumer now receives the logback Level (the appender already stored it); IDELogFragment maps it to ILogger.Level so IDE log lines carry level metadata. - LogViewFragment handles SetText, clears the ViewModel history in clearOutput(), and shares the unfiltered history instead of the (possibly filtered) editor text.
Build output has no level metadata, so it gets the text filter only. The filter applies to the editor view; the session file always receives the unfiltered text, so clearing the filter (or sharing) restores everything. - BuildOutputViewModel: filterText state + pure filterLines() helper. - BuildOutputFragment: re-renders the 512KB editor window from the session file when the filter changes, applies the filter to live batches in flushToEditor() and to the restore path. A mutex serializes re-renders against batch appends so neither misses nor duplicates concurrently flushed output.
- New SearchableOutputFragment interface (beginSearch/toggleFilterBar), implemented by the log fragments and BuildOutputFragment. - Each output tab attaches the existing EditorSearchLayout below its editor (replace hidden, no collapsed-sheet margin) and lazily inflates the filter bar from the ViewStub. LogFilterBarController wires the bar: level chips apply immediately, text input debounced 250ms; level chips are hidden for Build Output. - Active searches refresh (or stop) after a filter re-render replaces the editor content. - Bottom sheet: new mini search/filter FABs shown on Searchable tabs; both expand the sheet before acting since the UI lives inside it. Tooltip tags OUTPUT_SEARCH/OUTPUT_FILTER added.
…peline - LogFilterTest: level sets, null-level pass-through, case-insensitive text match, combined filters. - LogBufferTest: seq monotonicity, filtered snapshots with lastSeq, trim behavior, clear. - LogViewModelTest: snapshot on collection, gap/duplicate-free live tail, filter re-render, ingestion filtering, clear, re-collection idempotence, LogLine capture-before-recycle. - BuildOutputFilterTest: pure filterLines() cases including a batch without a trailing newline.
Mechanical: gradlew spotlessApply over the files changed on this branch.
log_level_verbose/log_level_debug already exist in termux_shared_strings.xml in the same module; resource merging fails on the duplicates. Prefix ours with log_filter_.
uiEvents is now a cold flow whose collect() completes, so the Nothing return type no longer compiles.
GlobalBufferAppender replays its buffer on every registerConsumer(). With the fragment as consumer, each tab switch (view recreation) re-registered and re-submitted the replayed lines into the retained LogBuffer, duplicating history. The activity-scoped IDELogsViewModel is now the consumer, so the replay happens once per ViewModel lifetime. Found during on-device verification (line duplicated after switching tabs away and back).
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 Walkthrough
WalkthroughThe change adds bounded, level-aware log retention and filtered snapshot/live event delivery, introduces output search and filter controls, updates build-output rendering synchronization, and replaces bottom-sheet output FABs with action buttons. ChangesFiltered output pipeline
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant GlobalBufferAppender
participant IDELogsViewModel
participant LogViewModel
participant LogViewFragment
participant EditorBottomSheet
GlobalBufferAppender->>IDELogsViewModel: deliver level-aware log
IDELogsViewModel->>LogViewModel: submit log entry
LogViewModel-->>LogViewFragment: filtered snapshot or append event
EditorBottomSheet->>LogViewFragment: invoke search or filter action
LogViewFragment->>LogViewModel: update filter state
LogViewModel-->>LogViewFragment: render filtered snapshot
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
logger/src/main/java/com/itsaky/androidide/logging/GlobalBufferAppender.kt (1)
62-70: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftMake buffered replay and live registration atomic.
The consumer is added before
buffer.forEach, so a concurrent append can be dispatched live and then observed again by the weakly consistent queue iterator. It can also arrive before older replayed entries. Introduce an atomic replay-to-live handoff or sequence-based deduplication.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@logger/src/main/java/com/itsaky/androidide/logging/GlobalBufferAppender.kt` around lines 62 - 70, Update registerConsumer so replaying existing buffer entries and enabling live dispatch occur as one atomic handoff, preventing concurrent appends from being delivered twice or ahead of older replayed messages. Coordinate consumer registration with the append/dispatch path, or use sequence-based deduplication, while preserving ordered delivery of buffered entries followed by new entries.
🧹 Nitpick comments (1)
editor/src/main/java/com/itsaky/androidide/editor/ui/EditorSearchLayout.kt (1)
92-121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove unused
optionsMenuinitialization.This
PopupMenublock is dead code. ThemoreOptionsbutton click listener directly callsshowPopupMenu(...)(which inflates a customPopupWindow), meaningoptionsMenuis never displayed or utilized.
You can safely remove this block along with theoptionsMenuproperty declaration on line 72.♻️ Proposed refactor
- private val optionsMenu: PopupMenu ... - optionsMenu = PopupMenu(context, findInFileBinding.moreOptions, Gravity.TOP) - optionsMenu.menu.add(0, 0, 0, R.string.msg_ignore_case).apply { - isCheckable = true - isChecked = true - } - - optionsMenu.menu.add(0, 1, 0, R.string.msg_use_regex).apply { - isCheckable = true - isChecked = false - } - - optionsMenu.setOnMenuItemClickListener { - return@setOnMenuItemClickListener if (it.isCheckable) { - it.isChecked = !it.isChecked - - val caseInsensitive = searchOptions.caseInsensitive - val regex = searchOptions.type == SearchOptions.TYPE_REGULAR_EXPRESSION - searchOptions = - when (it.itemId) { - 0 -> SearchOptions(it.isChecked, regex) - 1 -> SearchOptions(caseInsensitive, it.isChecked) - else -> searchOptions - } - editor.searcher.updateSearchOptions(searchOptions) - - true - } else { - false - } - }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@editor/src/main/java/com/itsaky/androidide/editor/ui/EditorSearchLayout.kt` around lines 92 - 121, Remove the unused optionsMenu property and its PopupMenu initialization/configuration block in EditorSearchLayout, including the menu item click listener. Preserve the moreOptions click flow that displays the custom PopupWindow via showPopupMenu(...).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@app/src/main/java/com/itsaky/androidide/fragments/output/BuildOutputFragment.kt`:
- Around line 59-61: Update the timeout/deferred-append path in the editor
content serialization flow around editorContentMutex so it awaits layout and
performs the append while still holding the mutex, rather than launching a
separate coroutine that outlives the lock. Preserve ordering with later batches
and prevent filter re-renders from replacing the editor before the stale
visibleText append completes.
In `@app/src/main/java/com/itsaky/androidide/viewmodel/BuildOutputViewModel.kt`:
- Around line 89-91: Replace broad Exception catches in BuildOutputViewModel’s
file-operation blocks with narrow handling: catch IOException for the append,
sessionFile.readText(), range extraction readText(), and RandomAccessFile
interactions, and catch IOException and/or SecurityException for file deletion.
Preserve the existing logging and behavior while allowing unexpected exceptions
to propagate. Apply this at
app/src/main/java/com/itsaky/androidide/viewmodel/BuildOutputViewModel.kt ranges
89-91, 115-117, 137-139, 153-155, and 176-178.
In `@app/src/main/java/com/itsaky/androidide/viewmodel/LogViewModel.kt`:
- Around line 83-118: Update the liveEntries handoff and uiEvents collection to
avoid silently losing entries when the collector falls behind: either use
lossless buffering or detect sequence gaps before filtering and emit a fresh
buffer.snapshotFiltered(filter) SetText snapshot. Preserve sequence-based
stitching and ensure entries retained in LogBuffer are eventually reflected in
the editor.
- Around line 160-176: Serialize the compound operations in LogViewModel:
protect buffer.append followed by liveEntries.tryEmit in submit, and
buffer.clear followed by generation.update in clear, with the same lock. Ensure
clear cannot interleave between append and emission, preventing cleared entries
from being re-emitted while preserving submission order.
In `@editor/src/main/java/com/itsaky/androidide/editor/ui/EditorSearchLayout.kt`:
- Around line 320-328: Replace the broad catch in the regex validation near
EditorSearchLayout.kt lines 320-328 with a catch for
java.util.regex.PatternSyntaxException only. Also refactor the runCatching block
near lines 260-265 into explicit try/catch handling that catches only
PatternSyntaxException, preserving the existing fallback behavior for invalid
patterns at both sites.
In `@logger/src/main/java/com/itsaky/androidide/logging/GlobalBufferAppender.kt`:
- Around line 89-95: Update GlobalBufferAppender.dispatchTo so it no longer uses
runCatching around consumer.consume. Catch only the specific expected exception
type, log handled failures through the existing SLF4J logger, and allow
unexpected or fatal Throwables to propagate.
---
Outside diff comments:
In `@logger/src/main/java/com/itsaky/androidide/logging/GlobalBufferAppender.kt`:
- Around line 62-70: Update registerConsumer so replaying existing buffer
entries and enabling live dispatch occur as one atomic handoff, preventing
concurrent appends from being delivered twice or ahead of older replayed
messages. Coordinate consumer registration with the append/dispatch path, or use
sequence-based deduplication, while preserving ordered delivery of buffered
entries followed by new entries.
---
Nitpick comments:
In `@editor/src/main/java/com/itsaky/androidide/editor/ui/EditorSearchLayout.kt`:
- Around line 92-121: Remove the unused optionsMenu property and its PopupMenu
initialization/configuration block in EditorSearchLayout, including the menu
item click listener. Preserve the moreOptions click flow that displays the
custom PopupWindow via showPopupMenu(...).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5cb05267-9564-4699-8d24-e54a44175fb8
📒 Files selected for processing (26)
app/src/main/java/com/itsaky/androidide/fragments/output/BuildOutputFragment.ktapp/src/main/java/com/itsaky/androidide/fragments/output/IDELogFragment.ktapp/src/main/java/com/itsaky/androidide/fragments/output/LogFilterBarController.ktapp/src/main/java/com/itsaky/androidide/fragments/output/LogViewFragment.ktapp/src/main/java/com/itsaky/androidide/fragments/output/NonEditableEditorFragment.javaapp/src/main/java/com/itsaky/androidide/fragments/output/SearchableOutputFragment.ktapp/src/main/java/com/itsaky/androidide/logs/LogBuffer.ktapp/src/main/java/com/itsaky/androidide/models/LogFilter.ktapp/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.ktapp/src/main/java/com/itsaky/androidide/viewmodel/BuildOutputViewModel.ktapp/src/main/java/com/itsaky/androidide/viewmodel/IDELogsViewModel.ktapp/src/main/java/com/itsaky/androidide/viewmodel/LogViewModel.ktapp/src/main/res/layout/fragment_log.xmlapp/src/main/res/layout/fragment_non_editable_editor.xmlapp/src/main/res/layout/layout_editor_bottom_sheet.xmlapp/src/main/res/layout/layout_log_filter_bar.xmlapp/src/test/java/com/itsaky/androidide/logs/LogBufferTest.ktapp/src/test/java/com/itsaky/androidide/models/LogFilterTest.ktapp/src/test/java/com/itsaky/androidide/viewmodel/BuildOutputFilterTest.ktapp/src/test/java/com/itsaky/androidide/viewmodel/LogViewModelTest.kteditor/src/main/java/com/itsaky/androidide/editor/ui/EditorSearchLayout.ktidetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.ktlogger/src/main/java/com/itsaky/androidide/logging/GlobalBufferAppender.ktresources/src/main/res/drawable/ic_keyboard_arrow_up.xmlresources/src/main/res/values/strings.xmlresources/src/main/res/values/styles.xml
Jira tickets - ADFA-936, ADFA-937
Implement Filter and Search within logs -
Build Output,IDE LogsandApp Logs