Skip to content

ADFA-936: Filter and search logs#1533

Open
dara-abijo-adfa wants to merge 19 commits into
stagefrom
ADFA-936-filter-and-search-ide-logs
Open

ADFA-936: Filter and search logs#1533
dara-abijo-adfa wants to merge 19 commits into
stagefrom
ADFA-936-filter-and-search-ide-logs

Conversation

@dara-abijo-adfa

@dara-abijo-adfa dara-abijo-adfa commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Jira tickets - ADFA-936, ADFA-937

Implement Filter and Search within logs - Build Output, IDE Logs and App Logs

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).
@dara-abijo-adfa dara-abijo-adfa self-assigned this Jul 16, 2026
@dara-abijo-adfa
dara-abijo-adfa marked this pull request as ready for review July 17, 2026 11:32

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe879552-bd6c-4539-b1b2-0540d90e99bc

📥 Commits

Reviewing files that changed from the base of the PR and between d8aadd2 and 9b27ae8.

📒 Files selected for processing (1)
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • resources/src/main/res/values/strings.xml

📝 Walkthrough
  • Added search and line filtering for Build Output, IDE Logs, and App Logs output tabs.

  • Introduced LogFilter and LogBuffer to support level chips + case-insensitive text matching, with bounded in-memory history and snapshot/filtered rendering.

  • Refactored LogViewModel to a snapshot-then-live-tail pipeline: emit a filtered SetText snapshot, then append only matching incremental Append chunks; generation-based updates prevent duplicates/gaps during filter changes.

  • Implemented LogFilterBarController with debounced text filtering and hide/toggle behavior for the log filter UI (including optional level-chip row).

  • Added SearchableOutputFragment contract and wired EditorSearchLayout into output fragments so search can be started/toggled from the bottom sheet and refreshed when content changes.

  • Updated editor rendering for Build Output filtering to be concurrency-safe using a new editor re-render mutex; filtered editor contents refresh on filter-text changes while session text remains intact.

  • Simplified IDELogFragment by moving log consumption responsibility into IDELogsViewModel (removed GlobalBufferAppender.Consumer wiring from the fragment).

  • Updated sharing/copy behavior to share the retained unfiltered history (to avoid sharing filtered editor contents).

  • Refreshed output bottom sheet UI: replaced prior output FABs with Search/Filter/Share/Clear “output action” buttons wired to the new fragment search/filter contracts.

  • Added/updated UI resources and layouts (filter bar layouts, view stubs, strings/tooltips, editor/button styling) to support the new controls.

  • Added unit tests for LogBuffer, LogFilter, LogViewModel snapshot/live-tail correctness (including filter changes and clear), and BuildOutputViewModel filtering.

  • Risk: GlobalBufferAppender.Consumer.consume is now level-aware (consume(level, message)), which may impact any custom consumers relying on the old signature.

  • Risk: The snapshot+live-tail stitching and generation-based re-render logic is complex; regressions could occur around filter changes during active log streaming (covered by tests, but still warrants targeted integration verification).

  • Risk: Buffering and snapshotting filtered/unfiltered history can increase memory usage under heavy logging; monitor buffer sizing/trim behavior.

Walkthrough

The 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.

Changes

Filtered output pipeline

Layer / File(s) Summary
Retained level-aware log stream
app/.../LogBuffer.kt, app/.../LogFilter.kt, app/.../LogViewModel.kt, app/.../IDELogsViewModel.kt, logger/..., app/src/test/...
Logs retain level metadata in a bounded buffer and emit filtered snapshots followed by matching live entries.
Build output filtering and serialized rendering
app/.../BuildOutputFragment.kt, app/.../BuildOutputViewModel.kt, app/src/test/.../BuildOutputFilterTest.kt
Build output applies case-insensitive filters to restored and appended content while serializing editor updates.
Output fragment search and filter controls
app/.../LogViewFragment.kt, app/.../LogFilterBarController.kt, app/.../SearchableOutputFragment.kt, app/src/main/res/layout/*log*.xml
Output fragments expose search/filter actions, update filters from the UI, refresh search after content replacement, and share unfiltered history.
Editor search lifecycle integration
editor/.../EditorSearchLayout.kt
Search mode supports configurable controls and refreshes or stops searches when editor content changes.
Bottom-sheet output actions and presentation
app/.../EditorBottomSheet.kt, app/src/main/res/layout/layout_editor_bottom_sheet.xml, resources/..., idetooltips/...
Output FABs are replaced by search, filter, share, and clear action buttons with supporting resources and tooltip tags.

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
Loading

Possibly related PRs

Suggested reviewers: daniel-adfa, jomen-adfa

Poem

I hopped through logs from dusk till dawn,
Filtering bright lines as they spawn.
Search buttons gleam, chips softly glow,
Old history stays ready to show.
A mutex guards each editor scroll—
This bunny approves the whole control!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding log filtering and search.
Description check ✅ Passed The description is on-topic and matches the log filtering/search changes across output, IDE logs, and app logs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ADFA-936-filter-and-search-ide-logs

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 lift

Make 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 win

Remove unused optionsMenu initialization.

This PopupMenu block is dead code. The moreOptions button click listener directly calls showPopupMenu(...) (which inflates a custom PopupWindow), meaning optionsMenu is never displayed or utilized.
You can safely remove this block along with the optionsMenu property 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

📥 Commits

Reviewing files that changed from the base of the PR and between 87083e0 and 2313573.

📒 Files selected for processing (26)
  • app/src/main/java/com/itsaky/androidide/fragments/output/BuildOutputFragment.kt
  • app/src/main/java/com/itsaky/androidide/fragments/output/IDELogFragment.kt
  • app/src/main/java/com/itsaky/androidide/fragments/output/LogFilterBarController.kt
  • app/src/main/java/com/itsaky/androidide/fragments/output/LogViewFragment.kt
  • app/src/main/java/com/itsaky/androidide/fragments/output/NonEditableEditorFragment.java
  • app/src/main/java/com/itsaky/androidide/fragments/output/SearchableOutputFragment.kt
  • app/src/main/java/com/itsaky/androidide/logs/LogBuffer.kt
  • app/src/main/java/com/itsaky/androidide/models/LogFilter.kt
  • app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/BuildOutputViewModel.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/IDELogsViewModel.kt
  • app/src/main/java/com/itsaky/androidide/viewmodel/LogViewModel.kt
  • app/src/main/res/layout/fragment_log.xml
  • app/src/main/res/layout/fragment_non_editable_editor.xml
  • app/src/main/res/layout/layout_editor_bottom_sheet.xml
  • app/src/main/res/layout/layout_log_filter_bar.xml
  • app/src/test/java/com/itsaky/androidide/logs/LogBufferTest.kt
  • app/src/test/java/com/itsaky/androidide/models/LogFilterTest.kt
  • app/src/test/java/com/itsaky/androidide/viewmodel/BuildOutputFilterTest.kt
  • app/src/test/java/com/itsaky/androidide/viewmodel/LogViewModelTest.kt
  • editor/src/main/java/com/itsaky/androidide/editor/ui/EditorSearchLayout.kt
  • idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt
  • logger/src/main/java/com/itsaky/androidide/logging/GlobalBufferAppender.kt
  • resources/src/main/res/drawable/ic_keyboard_arrow_up.xml
  • resources/src/main/res/values/strings.xml
  • resources/src/main/res/values/styles.xml

Comment thread app/src/main/java/com/itsaky/androidide/viewmodel/LogViewModel.kt
Comment thread app/src/main/java/com/itsaky/androidide/viewmodel/LogViewModel.kt Outdated
Comment thread logger/src/main/java/com/itsaky/androidide/logging/GlobalBufferAppender.kt Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants