diff options
| author | 2020-07-21 15:18:12 +0200 | |
|---|---|---|
| committer | 2020-07-21 15:18:12 +0200 | |
| commit | e43c7a80ea1cc6c8a264bee6f521e38f71da4dfc (patch) | |
| tree | ca2a38ba6e60bf29c2dd0c2d9a305778b0241231 | |
| parent | Bump version (diff) | |
| download | enigma-e43c7a80ea1cc6c8a264bee6f521e38f71da4dfc.tar.gz enigma-e43c7a80ea1cc6c8a264bee6f521e38f71da4dfc.tar.xz enigma-e43c7a80ea1cc6c8a264bee6f521e38f71da4dfc.zip | |
Fix losing current cursor position when renaming entries (#297)
* Fix losing current cursor position when renaming entries
* Set nextReference to null after applying it
* Extract token map into wrapper type
5 files changed, 146 insertions, 39 deletions
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java index 9bf5fa72..3409fc14 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java | |||
| @@ -69,6 +69,7 @@ public class EditorPanel { | |||
| 69 | private final Gui gui; | 69 | private final Gui gui; |
| 70 | 70 | ||
| 71 | private EntryReference<Entry<?>, Entry<?>> cursorReference; | 71 | private EntryReference<Entry<?>, Entry<?>> cursorReference; |
| 72 | private EntryReference<Entry<?>, Entry<?>> nextReference; | ||
| 72 | private boolean mouseIsPressed = false; | 73 | private boolean mouseIsPressed = false; |
| 73 | private boolean shouldNavigateOnClick; | 74 | private boolean shouldNavigateOnClick; |
| 74 | 75 | ||
| @@ -332,13 +333,14 @@ public class EditorPanel { | |||
| 332 | } else { | 333 | } else { |
| 333 | this.displayError(res.unwrapErr()); | 334 | this.displayError(res.unwrapErr()); |
| 334 | } | 335 | } |
| 336 | this.nextReference = null; | ||
| 335 | }); | 337 | }); |
| 336 | } | 338 | } |
| 337 | 339 | ||
| 338 | public void displayError(ClassHandleError t) { | 340 | public void displayError(ClassHandleError t) { |
| 339 | this.setDisplayMode(DisplayMode.ERRORED); | 341 | this.setDisplayMode(DisplayMode.ERRORED); |
| 340 | String str; | 342 | String str; |
| 341 | switch(t.type) { | 343 | switch (t.type) { |
| 342 | case DECOMPILE: | 344 | case DECOMPILE: |
| 343 | str = "editor.decompile_error"; | 345 | str = "editor.decompile_error"; |
| 344 | break; | 346 | break; |
| @@ -404,21 +406,13 @@ public class EditorPanel { | |||
| 404 | } | 406 | } |
| 405 | 407 | ||
| 406 | public void onCaretMove(int pos, boolean fromClick) { | 408 | public void onCaretMove(int pos, boolean fromClick) { |
| 409 | if (this.settingSource) return; | ||
| 407 | if (this.controller.project == null) return; | 410 | if (this.controller.project == null) return; |
| 408 | 411 | ||
| 409 | EntryRemapper mapper = this.controller.project.getMapper(); | 412 | EntryRemapper mapper = this.controller.project.getMapper(); |
| 410 | Token token = getToken(pos); | 413 | Token token = getToken(pos); |
| 411 | 414 | ||
| 412 | if (this.settingSource) { | 415 | setCursorReference(getReference(token)); |
| 413 | EntryReference<Entry<?>, Entry<?>> ref = getCursorReference(); | ||
| 414 | EntryReference<Entry<?>, Entry<?>> refAtCursor = getReference(token); | ||
| 415 | if (this.editor.getDocument().getLength() != 0 && ref != null && !ref.equals(refAtCursor)) { | ||
| 416 | showReference0(ref); | ||
| 417 | } | ||
| 418 | return; | ||
| 419 | } else { | ||
| 420 | setCursorReference(getReference(token)); | ||
| 421 | } | ||
| 422 | 416 | ||
| 423 | Entry<?> referenceEntry = this.cursorReference != null ? this.cursorReference.entry : null; | 417 | Entry<?> referenceEntry = this.cursorReference != null ? this.cursorReference.entry : null; |
| 424 | 418 | ||
| @@ -484,17 +478,49 @@ public class EditorPanel { | |||
| 484 | if (source == null) return; | 478 | if (source == null) return; |
| 485 | try { | 479 | try { |
| 486 | this.settingSource = true; | 480 | this.settingSource = true; |
| 481 | |||
| 482 | int newCaretPos = 0; | ||
| 483 | if (this.source != null && this.source.getEntry().equals(source.getEntry())) { | ||
| 484 | int caretPos = this.editor.getCaretPosition(); | ||
| 485 | |||
| 486 | if (this.source.getTokenStore().isCompatible(source.getTokenStore())) { | ||
| 487 | newCaretPos = this.source.getTokenStore().mapPosition(source.getTokenStore(), caretPos); | ||
| 488 | } else { | ||
| 489 | // if the class is the same but the token stores aren't | ||
| 490 | // compatible, then the user probably switched decompilers | ||
| 491 | |||
| 492 | // check if there's a selected reference we can navigate to, | ||
| 493 | // but only if there's none already queued up for being selected | ||
| 494 | if (this.getCursorReference() != null && this.nextReference == null) { | ||
| 495 | this.nextReference = this.getCursorReference(); | ||
| 496 | } | ||
| 497 | |||
| 498 | // otherwise fall back to just using the same average | ||
| 499 | // position in the file | ||
| 500 | float scale = (float) source.toString().length() / this.source.toString().length(); | ||
| 501 | newCaretPos = (int) (caretPos * scale); | ||
| 502 | } | ||
| 503 | } | ||
| 504 | |||
| 487 | this.source = source; | 505 | this.source = source; |
| 488 | this.editor.getHighlighter().removeAllHighlights(); | 506 | this.editor.getHighlighter().removeAllHighlights(); |
| 489 | this.editor.setText(source.toString()); | 507 | this.editor.setText(source.toString()); |
| 508 | if (this.source != null) { | ||
| 509 | this.editor.setCaretPosition(newCaretPos); | ||
| 510 | } | ||
| 490 | setHighlightedTokens(source.getHighlightedTokens()); | 511 | setHighlightedTokens(source.getHighlightedTokens()); |
| 512 | setCursorReference(getReference(getToken(this.editor.getCaretPosition()))); | ||
| 491 | } finally { | 513 | } finally { |
| 492 | this.settingSource = false; | 514 | this.settingSource = false; |
| 493 | } | 515 | } |
| 494 | showReference0(getCursorReference()); | 516 | |
| 517 | if (this.nextReference != null) { | ||
| 518 | this.showReference0(this.nextReference); | ||
| 519 | this.nextReference = null; | ||
| 520 | } | ||
| 495 | } | 521 | } |
| 496 | 522 | ||
| 497 | public void setHighlightedTokens(Map<RenamableTokenType, Collection<Token>> tokens) { | 523 | public void setHighlightedTokens(Map<RenamableTokenType, ? extends Collection<Token>> tokens) { |
| 498 | // remove any old highlighters | 524 | // remove any old highlighters |
| 499 | this.editor.getHighlighter().removeAllHighlights(); | 525 | this.editor.getHighlighter().removeAllHighlights(); |
| 500 | 526 | ||
| @@ -526,8 +552,11 @@ public class EditorPanel { | |||
| 526 | } | 552 | } |
| 527 | 553 | ||
| 528 | public void showReference(EntryReference<Entry<?>, Entry<?>> reference) { | 554 | public void showReference(EntryReference<Entry<?>, Entry<?>> reference) { |
| 529 | setCursorReference(reference); | 555 | if (this.mode == DisplayMode.SUCCESS) { |
| 530 | showReference0(reference); | 556 | showReference0(reference); |
| 557 | } else if (this.mode != DisplayMode.ERRORED) { | ||
| 558 | this.nextReference = reference; | ||
| 559 | } | ||
| 531 | } | 560 | } |
| 532 | 561 | ||
| 533 | /** | 562 | /** |
diff --git a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java index bc38e61b..22722075 100644 --- a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java +++ b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java | |||
| @@ -11,10 +11,9 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; | |||
| 11 | 11 | ||
| 12 | import javax.annotation.Nullable; | 12 | import javax.annotation.Nullable; |
| 13 | 13 | ||
| 14 | import cuchaz.enigma.EnigmaProject; | ||
| 14 | import cuchaz.enigma.classprovider.CachingClassProvider; | 15 | import cuchaz.enigma.classprovider.CachingClassProvider; |
| 15 | import cuchaz.enigma.classprovider.ObfuscationFixClassProvider; | 16 | import cuchaz.enigma.classprovider.ObfuscationFixClassProvider; |
| 16 | |||
| 17 | import cuchaz.enigma.EnigmaProject; | ||
| 18 | import cuchaz.enigma.events.ClassHandleListener; | 17 | import cuchaz.enigma.events.ClassHandleListener; |
| 19 | import cuchaz.enigma.events.ClassHandleListener.InvalidationType; | 18 | import cuchaz.enigma.events.ClassHandleListener.InvalidationType; |
| 20 | import cuchaz.enigma.source.*; | 19 | import cuchaz.enigma.source.*; |
| @@ -277,8 +276,8 @@ public final class ClassHandleProvider { | |||
| 277 | if (res == null || mappedVersion.get() != v) return; | 276 | if (res == null || mappedVersion.get() != v) return; |
| 278 | res = res.andThen(source -> { | 277 | res = res.andThen(source -> { |
| 279 | try { | 278 | try { |
| 280 | source.remapSource(p.project, p.project.getMapper().getDeobfuscator()); | 279 | DecompiledClassSource remappedSource = source.remapSource(p.project, p.project.getMapper().getDeobfuscator()); |
| 281 | return Result.ok(source); | 280 | return Result.ok(remappedSource); |
| 282 | } catch (Throwable e) { | 281 | } catch (Throwable e) { |
| 283 | return Result.err(ClassHandleError.remap(e)); | 282 | return Result.err(ClassHandleError.remap(e)); |
| 284 | } | 283 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java b/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java index e4a71150..9ac611f5 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java | |||
| @@ -22,30 +22,35 @@ public class DecompiledClassSource { | |||
| 22 | private final ClassEntry classEntry; | 22 | private final ClassEntry classEntry; |
| 23 | 23 | ||
| 24 | private final SourceIndex obfuscatedIndex; | 24 | private final SourceIndex obfuscatedIndex; |
| 25 | private SourceIndex remappedIndex; | 25 | private final SourceIndex remappedIndex; |
| 26 | 26 | ||
| 27 | private final Map<RenamableTokenType, Collection<Token>> highlightedTokens = new EnumMap<>(RenamableTokenType.class); | 27 | private final TokenStore highlightedTokens; |
| 28 | 28 | ||
| 29 | public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) { | 29 | private DecompiledClassSource(ClassEntry classEntry, SourceIndex obfuscatedIndex, SourceIndex remappedIndex, TokenStore highlightedTokens) { |
| 30 | this.classEntry = classEntry; | 30 | this.classEntry = classEntry; |
| 31 | this.obfuscatedIndex = index; | 31 | this.obfuscatedIndex = obfuscatedIndex; |
| 32 | this.remappedIndex = index; | 32 | this.remappedIndex = remappedIndex; |
| 33 | this.highlightedTokens = highlightedTokens; | ||
| 34 | } | ||
| 35 | |||
| 36 | public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) { | ||
| 37 | this(classEntry, index, index, TokenStore.empty()); | ||
| 33 | } | 38 | } |
| 34 | 39 | ||
| 35 | public static DecompiledClassSource text(ClassEntry classEntry, String text) { | 40 | public static DecompiledClassSource text(ClassEntry classEntry, String text) { |
| 36 | return new DecompiledClassSource(classEntry, new SourceIndex(text)); | 41 | return new DecompiledClassSource(classEntry, new SourceIndex(text)); |
| 37 | } | 42 | } |
| 38 | 43 | ||
| 39 | public void remapSource(EnigmaProject project, Translator translator) { | 44 | public DecompiledClassSource remapSource(EnigmaProject project, Translator translator) { |
| 40 | highlightedTokens.clear(); | ||
| 41 | |||
| 42 | SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); | 45 | SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); |
| 43 | 46 | ||
| 44 | SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator)); | 47 | TokenStore tokenStore = TokenStore.create(this.obfuscatedIndex); |
| 45 | remappedIndex = obfuscatedIndex.remapTo(remapResult); | 48 | SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(tokenStore, project, token, movedToken, translator)); |
| 49 | SourceIndex remappedIndex = obfuscatedIndex.remapTo(remapResult); | ||
| 50 | return new DecompiledClassSource(this.classEntry, this.obfuscatedIndex, remappedIndex, tokenStore); | ||
| 46 | } | 51 | } |
| 47 | 52 | ||
| 48 | private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) { | 53 | private String remapToken(TokenStore target, EnigmaProject project, Token token, Token movedToken, Translator translator) { |
| 49 | EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token); | 54 | EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token); |
| 50 | 55 | ||
| 51 | Entry<?> entry = reference.getNameableEntry(); | 56 | Entry<?> entry = reference.getNameableEntry(); |
| @@ -53,16 +58,16 @@ public class DecompiledClassSource { | |||
| 53 | 58 | ||
| 54 | if (project.isRenamable(reference)) { | 59 | if (project.isRenamable(reference)) { |
| 55 | if (!translatedEntry.isObfuscated()) { | 60 | if (!translatedEntry.isObfuscated()) { |
| 56 | highlightToken(movedToken, translatedEntry.getType()); | 61 | target.add(translatedEntry.getType(), movedToken); |
| 57 | return translatedEntry.getValue().getSourceRemapName(); | 62 | return translatedEntry.getValue().getSourceRemapName(); |
| 58 | } else { | 63 | } else { |
| 59 | Optional<String> proposedName = proposeName(project, entry); | 64 | Optional<String> proposedName = proposeName(project, entry); |
| 60 | if (proposedName.isPresent()) { | 65 | if (proposedName.isPresent()) { |
| 61 | highlightToken(movedToken, RenamableTokenType.PROPOSED); | 66 | target.add(RenamableTokenType.PROPOSED, movedToken); |
| 62 | return proposedName.get(); | 67 | return proposedName.get(); |
| 63 | } | 68 | } |
| 64 | 69 | ||
| 65 | highlightToken(movedToken, RenamableTokenType.OBFUSCATED); | 70 | target.add(RenamableTokenType.OBFUSCATED, movedToken); |
| 66 | } | 71 | } |
| 67 | } | 72 | } |
| 68 | 73 | ||
| @@ -113,12 +118,12 @@ public class DecompiledClassSource { | |||
| 113 | return remappedIndex; | 118 | return remappedIndex; |
| 114 | } | 119 | } |
| 115 | 120 | ||
| 116 | public Map<RenamableTokenType, Collection<Token>> getHighlightedTokens() { | 121 | public TokenStore getTokenStore() { |
| 117 | return highlightedTokens; | 122 | return this.highlightedTokens; |
| 118 | } | 123 | } |
| 119 | 124 | ||
| 120 | private void highlightToken(Token token, RenamableTokenType highlightType) { | 125 | public Map<RenamableTokenType, ? extends Collection<Token>> getHighlightedTokens() { |
| 121 | highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); | 126 | return this.highlightedTokens.getByType(); |
| 122 | } | 127 | } |
| 123 | 128 | ||
| 124 | public int getObfuscatedOffset(int deobfOffset) { | 129 | public int getObfuscatedOffset(int deobfOffset) { |
diff --git a/enigma/src/main/java/cuchaz/enigma/source/Token.java b/enigma/src/main/java/cuchaz/enigma/source/Token.java index fad733fa..7d729abb 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/Token.java +++ b/enigma/src/main/java/cuchaz/enigma/source/Token.java | |||
| @@ -23,9 +23,12 @@ public class Token implements Comparable<Token> { | |||
| 23 | this.text = text; | 23 | this.text = text; |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | public int length() { | ||
| 27 | return this.end - this.start; | ||
| 28 | } | ||
| 29 | |||
| 26 | public int getRenameOffset(String to) { | 30 | public int getRenameOffset(String to) { |
| 27 | int length = this.end - this.start; | 31 | return to.length() - this.length(); |
| 28 | return to.length() - length; | ||
| 29 | } | 32 | } |
| 30 | 33 | ||
| 31 | public void rename(StringBuffer source, String to) { | 34 | public void rename(StringBuffer source, String to) { |
diff --git a/enigma/src/main/java/cuchaz/enigma/source/TokenStore.java b/enigma/src/main/java/cuchaz/enigma/source/TokenStore.java new file mode 100644 index 00000000..f32d918c --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/TokenStore.java | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | import java.util.*; | ||
| 4 | |||
| 5 | public final class TokenStore { | ||
| 6 | |||
| 7 | private static final TokenStore EMPTY = new TokenStore(Collections.emptyNavigableSet(), Collections.emptyMap(), null); | ||
| 8 | |||
| 9 | private final NavigableSet<Token> tokens; | ||
| 10 | private final Map<RenamableTokenType, NavigableSet<Token>> byType; | ||
| 11 | private final String obfSource; | ||
| 12 | |||
| 13 | private TokenStore(NavigableSet<Token> tokens, Map<RenamableTokenType, NavigableSet<Token>> byType, String obfSource) { | ||
| 14 | this.tokens = tokens; | ||
| 15 | this.byType = byType; | ||
| 16 | this.obfSource = obfSource; | ||
| 17 | } | ||
| 18 | |||
| 19 | public static TokenStore create(SourceIndex obfuscatedIndex) { | ||
| 20 | EnumMap<RenamableTokenType, NavigableSet<Token>> map = new EnumMap<>(RenamableTokenType.class); | ||
| 21 | for (RenamableTokenType value : RenamableTokenType.values()) { | ||
| 22 | map.put(value, new TreeSet<>(Comparator.comparing(t -> t.start))); | ||
| 23 | } | ||
| 24 | return new TokenStore(new TreeSet<>(Comparator.comparing(t -> t.start)), Collections.unmodifiableMap(map), obfuscatedIndex.getSource()); | ||
| 25 | } | ||
| 26 | |||
| 27 | public static TokenStore empty() { | ||
| 28 | return TokenStore.EMPTY; | ||
| 29 | } | ||
| 30 | |||
| 31 | public void add(RenamableTokenType type, Token token) { | ||
| 32 | this.tokens.add(token); | ||
| 33 | this.byType.get(type).add(token); | ||
| 34 | } | ||
| 35 | |||
| 36 | public boolean isCompatible(TokenStore other) { | ||
| 37 | return this.obfSource != null && other.obfSource != null && | ||
| 38 | this.obfSource.equals(other.obfSource) && | ||
| 39 | this.tokens.size() == other.tokens.size(); | ||
| 40 | } | ||
| 41 | |||
| 42 | public int mapPosition(TokenStore to, int position) { | ||
| 43 | if (!this.isCompatible(to)) return 0; | ||
| 44 | |||
| 45 | int newPos = position; | ||
| 46 | Iterator<Token> thisIter = this.tokens.iterator(); | ||
| 47 | Iterator<Token> toIter = to.tokens.iterator(); | ||
| 48 | while (thisIter.hasNext()) { | ||
| 49 | Token token = thisIter.next(); | ||
| 50 | Token newToken = toIter.next(); | ||
| 51 | |||
| 52 | if (position < token.start) break; | ||
| 53 | |||
| 54 | // if we're inside the token and the text changed, | ||
| 55 | // snap the cursor to the beginning | ||
| 56 | if (!token.text.equals(newToken.text) && position < token.end) { | ||
| 57 | newPos = newToken.start; | ||
| 58 | break; | ||
| 59 | } | ||
| 60 | |||
| 61 | newPos += newToken.length() - token.length(); | ||
| 62 | } | ||
| 63 | |||
| 64 | return newPos; | ||
| 65 | } | ||
| 66 | |||
| 67 | public Map<RenamableTokenType, NavigableSet<Token>> getByType() { | ||
| 68 | return byType; | ||
| 69 | } | ||
| 70 | |||
| 71 | } | ||