Repository navigation
Commit 6d07c55
ADFA-5069: Replace in project (#2082)
* style: spotless reformat, no functional change
* ADFA-5069: Search options: match case, whole word, buffer overrides, scoped walk
RecursiveFileSearcher takes a ProjectSearchOptions:
- matchCase drops the case-insensitive flags.
- wholeWord rejects a match that abuts a word character on an edge where
the query itself is a word character, so server3 skips server33 while
/server33/ still matches inside a path.
- bufferOverrides lets open tabs be searched as they are in memory, not
as they are on disk.
- excludedDirNames / excludedDirs skip directories before descending,
which the project-root scope needs (build/, .gradle/, .git/, and the
module dirs already covered by their src entries).
The four-argument entry point delegates with defaults, so existing callers
behave as before. A synchronous search() backs the async one for tests.
* ADFA-5069: Refresh the Kotlin server's copy of files rewritten on disk
KtSymbolIndex caches closed files in ktFileCache and nothing ever evicted
an entry, so a closed .kt file rewritten on disk kept being analysed as
its old text. DiskCopyEvictionTest reproduces that before the fix.
New FileContentChangedEvent means "the IDE rewrote this file on disk".
The Kotlin server handles it for closed files by evicting the cached
KtFile, dropping the PSI view provider, publishing the same modification
events file creation uses, and re-indexing.
setViewProvider(vf, null) is used because reloadFromDisk and dropPsiCaches
both fail in the standalone Analysis API environment (missing tree-change
extension point, EDT-only access).
* ADFA-5069: Verified multi-file text replacement with disk undo
- TextReplacement checks every match still holds its original text, then
applies replacements last-to-first so earlier edits never shift later
ones. One stale match skips the whole file rather than guessing.
- ProjectReplacer writes closed files atomically (temp file + move) and
keeps the original bytes for undo. A failed file is reported and the
rest still go through.
- Undo restores a file only if it still holds exactly the bytes the
replace wrote. Content, not lastModified: on /sdcard a rename-over
write did not update the modification time, and a timestamp also
misses edits within the same tick.
- ReplaceSession keys exclusions by file + position, because SearchResult
inherits value equality from Range and would otherwise conflate the
same position in two files.
* ADFA-5069: Replace in project: dialog, preview, apply and undo
- "Replace in project" in the Find menu and on Ctrl+Shift+R opens the
project search dialog in replace mode: Replace with, Match case (on),
Whole word, and Project files outside modules (on). Find in project
gains the same options, off by default, and now searches open tabs'
unsaved text.
- Results show a checkbox per match and a tri-state one per file, the
matched text struck through with the replacement beside it, and a bar
with the included count, Replace and Cancel.
- Open tabs are edited in the buffer as one batch edit and stay unsaved;
closed files go through ProjectReplacer. Results are cleared after a
replace so no stale match can be opened.
- A bar reports the outcome with Undo; skipped and failed files are
listed with their reason.
- Match rows set baselineAligned=false: with the checkbox beside it, the
preview was baseline-shifted and its last line clipped.
* fix: address review on ADFA-5069
- Replace ran twice on a double tap: the button now disables on the
first tap and the results clear before the replace starts.
- Undo restored a file on disk under a tab opened after the replace,
so the next save reverted the undo. A reopened, unchanged tab now
gets its buffer restored (left unsaved, like replace itself); an
edited one is skipped as changed since replace.
- build/.git/... name exclusions applied at every depth, hiding source
packages such as com/acme/build. They now apply only to the project
root walk, still at any depth, so build-logic/build stays excluded.
- The preview strikethrough could mark an earlier occurrence of the
match. SearchResult now carries the match offset in its preview.
- writeAtomically re-checks the target just before the move and copies
POSIX permissions onto the temp file when the file system has them,
so gradlew keeps its executable bit.
Verified on the arm64 emulator: double tap replaces once, undo under a
reopened tab restores the buffer, a com/example/build package is found,
and each row strikes its own occurrence.
* fix: open Find/Replace in project with a fresh dialog, ADFA-5069
Both dialogs were built once and reused until the project re-initialised,
so a dismissed dialog came back with the previous query, replacement,
filter and checkboxes, and a module list that could be stale after a
sync. Each open now builds a new dialog.
* fix: address second review on ADFA-5069
- Module build scripts (app/build.gradle.kts, proguard-rules.pro) were
searched by neither walk. The project-root walk now excludes each
module's src instead of the whole module; app/build stays excluded by
the root name exclusions.
- A one-character file name crashed replace and undo:
File.createTempFile rejects a prefix shorter than 3 characters. The
temp prefix is now ".<name>.".
- Undo of a file opened in a tab after the replace restored only the
buffer, so closing the tab without saving kept the replaced text on
disk. It now restores the disk as well, then the buffer, and marks the
tab unmodified; if the disk restore is skipped the buffer is untouched.
- The replace report's Undo flashbar outlived discardUndo(). It is now
kept and dismissed whenever undo data is discarded, and a new report
replaces the old one.
- Recursive search followed symlinks, which could reach files outside
the project or loop. Symlinked files and directories are skipped.
- Replace and undo are exclusive: a request while one is running is
refused with a message, and the Replace button is re-enabled.
Verified on the arm64 emulator: app/build.gradle.kts appears in results,
a file named x is replaced without a crash, and undo under a reopened tab
restores disk and buffer with the tab left clean.
* style: spotless reformat, no functional change
Enrolls the four editor-treesitter files the next commits touch.
LineSpansGenerator's two license headers become one comment: ktlint
rejects the sora-editor header as a dangling KDoc, and two consecutive
block comments. The license text is unchanged.
* ADFA-5069: Compute tree-sitter spans from the text the tree was parsed from
LineSpansGenerator computes spans on a background thread from a tree
copy but read the editor's live Content, which the main thread keeps
editing. A span job running while edits landed (replace-all in a large
open file) read the new text at the old tree's offsets:
StringIndexOutOfBoundsException from MatchPredicate. Introduced by
#1032 (ADFA-3099).
Each generator now reads a snapshot of the worker's text at its tree's
version. An edit marks it stale, which is what the deferred tree.edit
achieved through hasChanges(), and the generator no longer applies the
pooled TSInputEdit after the worker has recycled it.
editor androidTest: exclude testing:common from testing:android. It
shares :common's coordinates, so the test APK shipped without the
production :common classes.
* ADFA-5069: Stop analyzer workers without freeing state they still use
TsAnalyzeWorker.stop() runs on the main thread and closed the document,
parser and tree while the worker thread could still be applying an edit
or parsing: IllegalStateException "Cannot access native object" in
doMod, and a native abort (Scudo invalid chunk state in
ts_parser_delete). It also blocked the main thread until the parse
returned. The log panes restarted their analyzer every two seconds and
hit this on each restart.
stop() now requests cancellation without waiting and wakes the loop
with a Stop message; the worker thread releases the document after its
current message.
* ADFA-5069: Re-sync the log panes once the editor is laid out
While a log editor is not laid out, for example with the bottom sheet
collapsed after returning to the app, every append timed out after two
seconds and re-synced: the whole buffer was rewritten and the analyzer
restarted every two seconds while the app kept logging. The re-sync
came with #1674 (ADFA-4808).
Wait for the layout, then re-sync once, and skip the trim when a re-sync
is about to replace the text anyway.
* ADFA-5069: Keep a stopped analyzer worker from publishing styles
A worker already inside updateStyles() when stop() ran kept going after
the manager tore its styles down: it destroyed the current span
generator, which rerun() then destroyed again (RejectedExecutionException
on the calling thread), leaked the generator it built and its bracket
pairs, and could publish stale styles over the new worker's.
The generator, bracket pairs and code blocks are still built without a
lock; publishing them now happens under the worker's lock and re-checks
isDestroyed, disposing of them when the worker has stopped. stop() sets
the flag under the same lock, which only ever covers assignments and
posts, so the main thread still never waits on a parse or a query.
stop() also requests cancellation before setting isDestroyed, so the
worker cannot close the parser during that native call.
* style: spotless reformat, no functional change
---------
Co-authored-by: Daniel Alome <astrocoder007@gmail.com>1 parent e684049 commit 6d07c55
44 files changed
Lines changed: 3224 additions & 1218 deletions
File tree
- app/src
- main
- java/com/itsaky/androidide
- actions/etc
- activities/editor
- adapters
- fragments
- output
- models
- search/replace
- shortcuts/groups
- utils
- viewmodel
- res/layout
- test/java/com/itsaky/androidide
- search/replace
- utils
- editor-treesitter/src/main/java/io/github/rosemoe/sora/editor/ts
- editor
- src/androidTest/java/com/itsaky/androidide/editor/language/treesitter
- eventbus-events/src/main/java/com/itsaky/androidide/eventbus/events/file
- lsp/kotlin/src
- main/java/com/itsaky/androidide/lsp/kotlin
- compiler
- index
- test/java/com/itsaky/androidide/lsp/kotlin/compiler/index
- resources/src/main/res/values
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 58 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
994 | 994 | | |
995 | 995 | | |
996 | 996 | | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
997 | 1001 | | |
998 | 1002 | | |
999 | 1003 | | |
| |||
0 commit comments