Skip to content

Commit e561cca

Browse files
committed
editor completions for L;, (), {} and "". show assist popup between brackets, max file size setting, own tree look for important apk files, highlight fixes, class by parent search.
1 parent 1b83214 commit e561cca

9 files changed

Lines changed: 166 additions & 61 deletions

File tree

composeApp/src/commonMain/composeResources/values/strings.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<string name="settings_category_signing">Sign &amp; Deploy settings</string>
4848

4949
<string name="settings_font_size">Font size</string>
50+
<string name="settings_max_file_size">Maximum allowed file size</string>
5051
<string name="settings_api_level">Dalvik Opcodes API level</string>
5152
<string name="settings_decompiler_verbose">Verbose decompiler output</string>
5253
<string name="settings_decompiler_implementation">Decompiler implementation</string>
@@ -108,7 +109,7 @@
108109

109110
<string name="error_password_min_length">Password must be at least 6 characters</string>
110111

111-
<string name="editor_cannot_open">The file is either too large, not a valid ASCII file or has too many lines. Please use a different editor to open it.</string>
112+
<string name="editor_cannot_open">The file is either too large, not a valid ASCII file or has too many lines. Please use a different editor to open it or change the maximum allowed file size.</string>
112113
<string name="editor_search_placeholder">Search text...</string>
113114
<string name="editor_search_no_results">No results</string>
114115
<string name="editor_search_results_found">%1$d results</string>
@@ -118,6 +119,7 @@
118119
<string name="tree_search_result_type_string">String constant</string>
119120
<string name="tree_search_result_type_literal">Literal</string>
120121
<string name="tree_search_result_type_reference">Method or field reference</string>
122+
<string name="tree_search_result_type_class_by_parent">Class by direct parent</string>
121123
<string name="tree_method_count">%1$s methods</string>
122124
<string name="tree_field_count">%1$s fields</string>
123125

composeApp/src/commonMain/kotlin/me/lkl/dalvikus/lexer/SmaliLexerHighlight.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,24 @@ fun getSmaliTokenStyle(tokenType: Int, colors: CodeHighlightColors): SpanStyle?
7777
val tokenName = getSmaliTokenName(tokenType) ?: return null
7878

7979
val color = when {
80+
tokenName.startsWith("INSTRUCTION_") -> colors.senary
8081
tokenName.endsWith("_DIRECTIVE") -> colors.primary
81-
tokenName == "REGISTER" -> colors.secondary
8282
tokenName.endsWith("_LITERAL") -> colors.tertiary
83-
tokenName == "CLASS_DESCRIPTOR" || tokenName == "PRIMITIVE_TYPE" || tokenName == "ARRAY_TYPE_PREFIX" -> colors.quaternary
84-
tokenName == "SIMPLE_NAME" -> colors.quinary
85-
tokenName == "ACCESS_SPEC" -> colors.septenary
86-
tokenName.startsWith("INSTRUCTION_") -> colors.senary
83+
tokenName.endsWith("_TYPE") || tokenName == "CLASS_DESCRIPTOR" || tokenName == "ARRAY_TYPE_PREFIX" -> colors.quaternary
84+
tokenName.endsWith("_NAME") -> colors.quinary
85+
86+
tokenName == "REGISTER" -> colors.secondary
87+
tokenName == "ACCESS_SPEC" || tokenName == "ANNOTATION_VISIBILITY" -> colors.septenary
88+
8789
tokenName in listOf("COLON", "COMMA", "OPEN_PAREN", "CLOSE_PAREN") -> colors.onSurface.copy(alpha = 0.7f)
8890
tokenName == "LINE_COMMENT" -> colors.onSurface.copy(alpha = 0.5f)
89-
tokenName == "ANNOTATION_VISIBILITY" -> colors.primary
9091
else -> colors.onSurface
9192
}
9293

9394
val weight = when {
9495
tokenName == "ARROW" -> FontWeight.Bold
9596
tokenName.startsWith("INSTRUCTION_") -> FontWeight.Medium
96-
tokenName == "ACCESS_SPEC" -> FontWeight.SemiBold
97+
tokenName == "ACCESS_SPEC" || tokenName == "ANNOTATION_VISIBILITY" -> FontWeight.SemiBold
9798
tokenName.endsWith("_DIRECTIVE") -> FontWeight.SemiBold
9899

99100
else -> FontWeight.Normal

composeApp/src/commonMain/kotlin/me/lkl/dalvikus/settings/DalvikusSettings.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ class DalvikusSettings() {
2828
step = 1,
2929
unit = "sp"
3030
),
31+
IntSetting(
32+
key = "max_file_size",
33+
category = SettingsCategory.EDITOR,
34+
nameRes = Res.string.settings_max_file_size,
35+
defaultValue = 64,
36+
min = 0,
37+
max = 1024,
38+
step = 1024,
39+
unit = "kB"
40+
),
3141
BooleanSetting(
3242
key = "check_for_updates",
3343
category = SettingsCategory.GENERAL,

composeApp/src/commonMain/kotlin/me/lkl/dalvikus/theme/FileTypeMeta.kt

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import androidx.compose.material.icons.Icons
44
import androidx.compose.material.icons.automirrored.outlined.Article
55
import androidx.compose.material.icons.filled.Android
66
import androidx.compose.material.icons.filled.Api
7+
import androidx.compose.material.icons.filled.Approval
8+
import androidx.compose.material.icons.filled.Book
79
import androidx.compose.material.icons.filled.Class
810
import androidx.compose.material.icons.filled.Code
911
import androidx.compose.material.icons.filled.Directions
@@ -40,27 +42,49 @@ fun getSuggestionTypeIcon(type: SuggestionType): ImageVector {
4042
val readableImageFormats = listOf("png", "jpg", "jpeg", "bmp", "webp", "gif", "ico")
4143

4244
fun getFileExtensionMeta(fileName: String): FileTypeMeta {
43-
val ext = fileName.substringAfterLast('.', "").lowercase()
45+
when (fileName) {
46+
"AndroidManifest.xml" -> {
47+
return FileTypeMeta(Icons.Filled.Book, CodeBlue)
48+
}
49+
"resources.arsc" -> {
50+
return FileTypeMeta(Icons.Filled.Android, AndroidGreen)
51+
}
52+
"MANIFEST.MF" -> {
53+
return FileTypeMeta(Icons.Filled.Api, PackageOrange)
54+
}
55+
"CERT.RSA", "CERT.SF", "SIGNER.SF", "SIGNER.RSA" -> {
56+
return FileTypeMeta(Icons.Filled.Approval, PackageOrange)
57+
}
58+
else -> {
59+
val ext = fileName.substringAfterLast('.', "").lowercase()
4460

45-
return when (ext) {
46-
"txt", "md", "log" -> FileTypeMeta(Icons.Outlined.Description)
47-
in readableImageFormats ->
48-
FileTypeMeta(Icons.Outlined.Image, ImagePurple)
49-
"mp3", "wav", "ogg", "flac", "aac" ->
50-
FileTypeMeta(Icons.Outlined.MusicNote, AudioTeal)
51-
"mp4", "avi", "mov", "mkv", "webm" ->
52-
FileTypeMeta(Icons.Outlined.Movie, VideoRed)
53-
"pdf" -> FileTypeMeta(Icons.Outlined.PictureAsPdf, PdfRed)
54-
"zip", "jar", "rar", "7z", "tar", "gz" ->
55-
FileTypeMeta(Icons.Filled.FolderZip, ArchiveGray)
56-
"doc", "docx" -> FileTypeMeta(Icons.AutoMirrored.Outlined.Article, WordBlue)
57-
"xls", "xlsx" -> FileTypeMeta(Icons.Outlined.TableChart, ExcelGreen)
58-
"ppt", "pptx" -> FileTypeMeta(Icons.Outlined.Slideshow, PowerPointOrange)
59-
"html", "xml", "json", "yaml", "yml" ->
60-
FileTypeMeta(Icons.Outlined.Code, CodeBlue)
61-
"apk", "apks", "aab", "xapk", "dex", "odex" ->
62-
FileTypeMeta(Icons.Filled.Android, AndroidGreen)
63-
else -> FileTypeMeta(Icons.Outlined.Description)
61+
return when (ext) {
62+
"txt", "md", "log" -> FileTypeMeta(Icons.Outlined.Description)
63+
in readableImageFormats ->
64+
FileTypeMeta(Icons.Outlined.Image, ImagePurple)
65+
66+
"mp3", "wav", "ogg", "flac", "aac" ->
67+
FileTypeMeta(Icons.Outlined.MusicNote, AudioTeal)
68+
69+
"mp4", "avi", "mov", "mkv", "webm" ->
70+
FileTypeMeta(Icons.Outlined.Movie, VideoRed)
71+
72+
"pdf" -> FileTypeMeta(Icons.Outlined.PictureAsPdf, PdfRed)
73+
"zip", "jar", "rar", "7z", "tar", "gz" ->
74+
FileTypeMeta(Icons.Filled.FolderZip, ArchiveGray)
75+
76+
"doc", "docx" -> FileTypeMeta(Icons.AutoMirrored.Outlined.Article, WordBlue)
77+
"xls", "xlsx" -> FileTypeMeta(Icons.Outlined.TableChart, ExcelGreen)
78+
"ppt", "pptx" -> FileTypeMeta(Icons.Outlined.Slideshow, PowerPointOrange)
79+
"html", "xml", "json", "yaml", "yml" ->
80+
FileTypeMeta(Icons.Outlined.Code, CodeBlue)
81+
82+
"apk", "apks", "aab", "xapk", "dex", "odex" ->
83+
FileTypeMeta(Icons.Filled.Android, AndroidGreen)
84+
85+
else -> FileTypeMeta(Icons.Outlined.Description)
86+
}
87+
}
6488
}
6589
}
6690

composeApp/src/commonMain/kotlin/me/lkl/dalvikus/tree/TreeSearch.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package me.lkl.dalvikus.tree
22

33
import androidx.compose.material.icons.Icons
44
import androidx.compose.material.icons.filled.Abc
5+
import androidx.compose.material.icons.filled.EscalatorWarning
56
import androidx.compose.material.icons.filled.Route
67
import androidx.compose.ui.graphics.vector.ImageVector
78
import com.android.tools.smali.dexlib2.iface.Annotation
@@ -24,14 +25,16 @@ enum class TreeSearchResultType {
2425
TREE_NODE,
2526
STRING_VALUE,
2627
LITERAL,
27-
REFERENCE;
28+
REFERENCE,
29+
CLASS_BY_PARENT;
2830

2931
val icon: ImageVector
3032
get() = when (this) {
3133
TREE_NODE -> Icons.Default.FamilyHistory
3234
STRING_VALUE -> Icons.Default.Abc
3335
LITERAL -> Icons.Default.ThreadUnread
3436
REFERENCE -> Icons.Default.Route
37+
CLASS_BY_PARENT -> Icons.Default.EscalatorWarning
3538
}
3639
}
3740

@@ -68,6 +71,14 @@ fun searchTreeBFS(
6871
if (current is DexEntryClassNode) {
6972
val classDef = current.getClassDef()
7073

74+
// Check parent class
75+
classDef.superclass?.let { parentClass ->
76+
if (matcher(parentClass)) {
77+
emit(TreeSearchResult(current, ".super $parentClass", TreeSearchResultType.CLASS_BY_PARENT))
78+
resultsFound++
79+
}
80+
}
81+
7182
// Search string constants
7283
classDef.getStringPool().forEach { string ->
7384
if (matcher(string)) {

composeApp/src/commonMain/kotlin/me/lkl/dalvikus/tree/dex/DexEntryClassNode.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class DexEntryClassNode(
118118
)
119119
}
120120

121-
override fun getSizeEstimate(): Long = 64 * 1024 // 64 kB
121+
override fun getSizeEstimate(): Long = 8 * 1024 // 8 kB
122122
override fun isDisplayable(): Boolean = true
123123
override fun isEditable(): Boolean = true
124124

composeApp/src/commonMain/kotlin/me/lkl/dalvikus/ui/LeftPanel.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
1111
import androidx.compose.foundation.verticalScroll
1212
import androidx.compose.material.icons.Icons
1313
import androidx.compose.material.icons.automirrored.filled.ArrowBack
14+
import androidx.compose.material.icons.filled.Add
1415
import androidx.compose.material.icons.filled.Clear
1516
import androidx.compose.material.icons.filled.FolderOpen
1617
import androidx.compose.material.icons.filled.Search
@@ -34,6 +35,7 @@ import me.lkl.dalvikus.tree.*
3435
import me.lkl.dalvikus.tree.filesystem.FileSystemFileNode
3536
import me.lkl.dalvikus.tree.root.HiddenRoot
3637
import me.lkl.dalvikus.errorreport.crtExHandler
38+
import me.lkl.dalvikus.selectedNavItem
3739
import me.lkl.dalvikus.ui.tree.FileSelectorDialog
3840
import me.lkl.dalvikus.ui.tree.TreeDragAndDropTarget
3941
import me.lkl.dalvikus.ui.tree.TreeView
@@ -165,7 +167,7 @@ internal fun LeftPanelContent() {
165167
ExtendedFloatingActionButton(
166168
onClick = { showTreeAddFileDialog = true },
167169
icon = {
168-
Icon(Icons.Default.FolderOpen, contentDescription = stringResource(Res.string.fab_load_file))
170+
Icon(Icons.Default.Add, contentDescription = stringResource(Res.string.fab_load_file))
169171
},
170172
text = {
171173
Text(stringResource(Res.string.fab_load_file), maxLines = 1)
@@ -202,6 +204,12 @@ internal fun LeftPanelContent() {
202204
currentSelection = node
203205

204206
if (node is FileNode) {
207+
208+
if(node.name.equals("resources.arsc", true)) {
209+
selectedNavItem = "Resources"
210+
return@TreeView
211+
}
212+
205213
scope.launch(crtExHandler) {
206214
val newTab = node.createTab()
207215
tabManager.addOrSelectTab(newTab)
@@ -257,6 +265,7 @@ private fun SearchResults(
257265
TreeSearchResultType.STRING_VALUE -> Res.string.tree_search_result_type_string
258266
TreeSearchResultType.REFERENCE -> Res.string.tree_search_result_type_reference
259267
TreeSearchResultType.LITERAL -> Res.string.tree_search_result_type_literal
268+
TreeSearchResultType.CLASS_BY_PARENT -> Res.string.tree_search_result_type_class_by_parent
260269
}
261270
)
262271

@@ -303,6 +312,7 @@ private fun SearchResults(
303312
TreeSearchResultType.STRING_VALUE -> MaterialTheme.colorScheme.primary
304313
TreeSearchResultType.REFERENCE -> MaterialTheme.colorScheme.secondary
305314
TreeSearchResultType.LITERAL -> MaterialTheme.colorScheme.tertiary
315+
TreeSearchResultType.CLASS_BY_PARENT -> MaterialTheme.colorScheme.onSurfaceVariant
306316
},
307317
maxLines = 1,
308318
overflow = TextOverflow.Ellipsis

composeApp/src/commonMain/kotlin/me/lkl/dalvikus/ui/editor/EditorViewModel.kt

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ import me.lkl.dalvikus.util.SearchOptions
3030
import me.lkl.dalvikus.util.createSearchMatcher
3131
import java.math.BigInteger
3232

33-
const val maxEditorFileSize = 128 * 1024 // 128 KiB
34-
const val maxEditorLines = 10000 // 10,000 lines
35-
3633
class EditorViewModel(private val tab: TabElement) {
3734
var isLoaded by mutableStateOf(false)
3835
private set
@@ -56,9 +53,11 @@ class EditorViewModel(private val tab: TabElement) {
5653
private set
5754

5855
var openable by
59-
mutableStateOf(tab.contentProvider.getSizeEstimate() < maxEditorFileSize && tab.contentProvider.isDisplayable())
56+
mutableStateOf(tab.contentProvider.getSizeEstimate() < getMaxFileBytes() && tab.contentProvider.isDisplayable())
6057
private set
6158

59+
private fun getMaxFileBytes(): Int = ((dalvikusSettings["max_file_size"] as Int) * 1024)
60+
6261
var isSearchActive by mutableStateOf(false)
6362
var searchOptions by mutableStateOf(SearchOptions())
6463

@@ -111,7 +110,11 @@ class EditorViewModel(private val tab: TabElement) {
111110

112111
internalContent = internalContent.copy(text = tab.contentProvider.contentFlow.value.decodeToString())
113112

114-
if (internalContent.text.lines().size > maxEditorLines) {
113+
val charCount = internalContent.text.length
114+
115+
Logger.i("Loaded ${tab.contentProvider.getFileType()} with $charCount characters.")
116+
117+
if (charCount * 2 > getMaxFileBytes()) {
115118
openable = false
116119
return@withContext
117120
}
@@ -127,7 +130,11 @@ class EditorViewModel(private val tab: TabElement) {
127130
}
128131
}
129132

130-
fun changeContent(newTextFieldValue: TextFieldValue, coroutineScope: CoroutineScope, checkNewline: Boolean = true) {
133+
fun changeContent(
134+
newTextFieldValue: TextFieldValue,
135+
coroutineScope: CoroutineScope,
136+
editorAdditions: Boolean = true
137+
) {
131138
val oldText = internalContent.text
132139
val newText = newTextFieldValue.text
133140

@@ -143,28 +150,58 @@ class EditorViewModel(private val tab: TabElement) {
143150
val safeNewSuffixStart = maxOf(newSuffixStart, diffIndex)
144151
val insertedText = newText.substring(diffIndex, safeNewSuffixStart)
145152

146-
val isNewlineInserted = insertedText == "\n"
147-
148-
if (checkNewline && isNewlineInserted) {
153+
if (editorAdditions) {
149154
val caretPosition = newTextFieldValue.selection.start
150155
if (caretPosition > 0) {
151-
val beforeNewline = newText.substring(0, caretPosition - 1)
152-
val currentLineStart = beforeNewline.lastIndexOf('\n') + 1
153-
val currentLine = beforeNewline.substring(currentLineStart)
154-
val leadingIndent = currentLine.takeWhile { it == ' ' || it == '\t' }
155-
156-
val updatedText = newText.substring(0, caretPosition) + leadingIndent + newText.substring(caretPosition)
157-
val updatedSelection = TextRange(caretPosition + leadingIndent.length)
158-
159-
changeContent(
160-
newTextFieldValue.copy(text = updatedText, selection = updatedSelection),
161-
coroutineScope,
162-
checkNewline = false
163-
)
164-
return
156+
fun insertTextAtCaret(suffix: String, moveCaretBack: Boolean = true) {
157+
val updatedText =
158+
newText.substring(0, caretPosition) + suffix + newText.substring(caretPosition)
159+
val updatedSelection = TextRange(
160+
caretPosition - if (moveCaretBack) 0 else -suffix.length
161+
)
162+
163+
changeContent(
164+
newTextFieldValue.copy(text = updatedText, selection = updatedSelection),
165+
coroutineScope,
166+
editorAdditions = false
167+
)
168+
}
169+
170+
when (insertedText) {
171+
"\n" -> {
172+
val beforeNewline = newText.substring(0, caretPosition - 1)
173+
val currentLineStart = beforeNewline.lastIndexOf('\n') + 1
174+
val currentLine = beforeNewline.substring(currentLineStart)
175+
val leadingIndent = currentLine.takeWhile { it == ' ' || it == '\t' }
176+
177+
insertTextAtCaret(leadingIndent, moveCaretBack = false)
178+
return
179+
}
180+
181+
"{" -> {
182+
insertTextAtCaret("}")
183+
return
184+
}
185+
186+
"(" -> {
187+
insertTextAtCaret(")")
188+
return
189+
}
190+
191+
"L" -> {
192+
insertTextAtCaret(";")
193+
return
194+
}
195+
196+
"\"" -> {
197+
insertTextAtCaret("\"")
198+
return
199+
}
200+
}
165201
}
166202
}
167203

204+
168205
mimicOldHighlight(newText, diffIndex, newSuffixStart, oldSuffixStart, insertedText)
169206

170207
if (newText.length > oldText.length) {

0 commit comments

Comments
 (0)