Skip to content

Commit 3f91445

Browse files
committed
Merge branch '2025.1' into 2025.2
2 parents a67f9a2 + 93f3d93 commit 3f91445

30 files changed

+592
-609
lines changed

build.gradle.kts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,6 @@ tasks.withType<GroovyCompile>().configureEach {
188188
}
189189

190190
tasks.processResources {
191-
for (lang in arrayOf("", "_en")) {
192-
from("src/main/resources/messages.MinecraftDevelopment_en_US.properties") {
193-
rename { "messages.MinecraftDevelopment$lang.properties" }
194-
}
195-
}
196191
// These templates aren't allowed to be in a directory structure in the output jar
197192
// But we have a lot of templates that would get real hard to deal with if we didn't have some structure
198193
// So this just flattens out the fileTemplates/j2ee directory in the jar, while still letting us have directories

crowdin.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
files:
2+
- source: /src/main/resources/messages/MinecraftDevelopment.properties
3+
translation: /src/main/resources/messages/MinecraftDevelopment_%two_letters_code%.properties

readme.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ Minecraft Development for IntelliJ
1111
<td align="right"><b>Main Build</b></td>
1212
<td colspan="2"><a href="https://ci.mcdev.io/viewType.html?buildTypeId=MinecraftDev_Build"><img src="https://ci.mcdev.io/app/rest/builds/buildType:(id:MinecraftDev_Build)/statusIcon.svg" alt="Teamcity Build Status" /></a></td>
1313
</tr>
14-
<tr>
15-
<td align="left">2024.2</td>
16-
<td align="left"><a href="https://ci.mcdev.io/viewType.html?buildTypeId=MinecraftDev_Nightly_20242"><img src="https://ci.mcdev.io/app/rest/builds/buildType:(id:MinecraftDev_Nightly_20242)/statusIcon.svg" alt="2024.2 Nightly Status" /></a></td>
17-
</tr>
1814
<tr>
1915
<td align="left">2024.3</td>
2016
<td align="left"><a href="https://ci.mcdev.io/viewType.html?buildTypeId=MinecraftDev_Nightly_20243"><img src="https://ci.mcdev.io/app/rest/builds/buildType:(id:MinecraftDev_Nightly_20243)/statusIcon.svg" alt="2024.3 Nightly Status" /></a></td>
@@ -35,7 +31,7 @@ Minecraft Development for IntelliJ
3531
</tr>
3632
</table>
3733

38-
Info and Documentation [![Current Release](https://img.shields.io/badge/release-1.8.5-orange.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/8327)
34+
Info and Documentation [![Current Release](https://img.shields.io/badge/release-1.8.6-orange.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/8327)
3935
----------------------
4036

4137
<a href="https://discord.gg/j6UNcfr"><img src="https://i.imgur.com/JXu9C1G.png" height="48px"></img></a>

src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@
2121
package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint
2222

2323
import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
24-
import com.demonwav.mcdev.platform.mixin.reference.toMixinString
2524
import com.demonwav.mcdev.platform.mixin.util.fakeResolve
2625
import com.demonwav.mcdev.platform.mixin.util.findOrConstructSourceField
2726
import com.demonwav.mcdev.util.MemberReference
2827
import com.demonwav.mcdev.util.constantValue
29-
import com.demonwav.mcdev.util.getQualifiedMemberReference
3028
import com.intellij.codeInsight.completion.JavaLookupElementBuilder
3129
import com.intellij.codeInsight.lookup.LookupElementBuilder
3230
import com.intellij.openapi.editor.Editor
@@ -107,10 +105,11 @@ class FieldInjectionPoint : QualifiedInjectionPoint<PsiField>() {
107105
return target?.let { MyCollectVisitor(mode, at.project, it, opcode, arrayAccess, fuzz) }
108106
}
109107

110-
override fun createLookup(targetClass: ClassNode, m: PsiField, owner: String): LookupElementBuilder {
108+
override fun createLookup(targetClass: ClassNode, m: PsiField, insn: AbstractInsnNode): LookupElementBuilder {
109+
insn as FieldInsnNode
111110
return JavaLookupElementBuilder.forField(
112111
m,
113-
m.getQualifiedMemberReference(owner).toMixinString(),
112+
"L${insn.owner};.${insn.name}:${insn.desc}",
114113
null,
115114
)
116115
.setBoldIfInClass(m, targetClass)

src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint
2222

2323
import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
24-
import com.demonwav.mcdev.platform.mixin.reference.toMixinString
2524
import com.demonwav.mcdev.platform.mixin.util.InjectionPointSpecifier
2625
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.SLICE
2726
import com.demonwav.mcdev.platform.mixin.util.SourceCodeLocationInfo
@@ -32,7 +31,6 @@ import com.demonwav.mcdev.util.constantValue
3231
import com.demonwav.mcdev.util.createLiteralExpression
3332
import com.demonwav.mcdev.util.findAnnotations
3433
import com.demonwav.mcdev.util.fullQualifiedName
35-
import com.demonwav.mcdev.util.getQualifiedMemberReference
3634
import com.demonwav.mcdev.util.internalName
3735
import com.demonwav.mcdev.util.memoized
3836
import com.demonwav.mcdev.util.realName
@@ -132,30 +130,32 @@ abstract class InjectionPoint<T : PsiElement> {
132130
return doCreateCollectVisitor(at, target, targetClass, mode)?.also {
133131
val isInsideSlice = at.parentOfType<PsiAnnotation>()?.hasQualifiedName(SLICE) == true
134132
val defaultSpecifier = if (isInsideSlice) InjectionPointSpecifier.FIRST else InjectionPointSpecifier.ALL
135-
addFilters(at, targetClass, it, defaultSpecifier)
133+
addFilters(at, targetClass, it, defaultSpecifier, mode)
136134
}
137135
}
138136

139137
protected open fun addFilters(
140138
at: PsiAnnotation,
141139
targetClass: ClassNode,
142140
collectVisitor: CollectVisitor<T>,
143-
defaultSpecifier: InjectionPointSpecifier
141+
defaultSpecifier: InjectionPointSpecifier,
142+
mode: CollectVisitor.Mode,
144143
) {
145-
addStandardFilters(at, targetClass, collectVisitor, defaultSpecifier)
144+
addStandardFilters(at, targetClass, collectVisitor, defaultSpecifier, mode)
146145
}
147146

148147
fun addStandardFilters(
149148
at: PsiAnnotation,
150149
targetClass: ClassNode,
151150
collectVisitor: CollectVisitor<T>,
152-
defaultSpecifier: InjectionPointSpecifier
151+
defaultSpecifier: InjectionPointSpecifier,
152+
mode: CollectVisitor.Mode,
153153
) {
154154
addShiftSupport(at, targetClass, collectVisitor)
155155
addSliceFilter(at, targetClass, collectVisitor)
156156
// make sure the ordinal filter is last, so that the ordinal only increments once the other filters have passed
157157
addOrdinalFilter(at, targetClass, collectVisitor)
158-
addSpecifierFilter(at, targetClass, collectVisitor, defaultSpecifier)
158+
addSpecifierFilter(at, targetClass, collectVisitor, defaultSpecifier, mode)
159159
}
160160

161161
protected open fun addShiftSupport(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor<*>) {
@@ -215,8 +215,13 @@ abstract class InjectionPoint<T : PsiElement> {
215215
at: PsiAnnotation,
216216
targetClass: ClassNode,
217217
collectVisitor: CollectVisitor<T>,
218-
defaultSpecifier: InjectionPointSpecifier
218+
defaultSpecifier: InjectionPointSpecifier,
219+
mode: CollectVisitor.Mode,
219220
) {
221+
if (mode == CollectVisitor.Mode.COMPLETION) {
222+
// Ignore the specifier, we want to show all results
223+
return
224+
}
220225
val point = at.findDeclaredAttributeValue("value")?.constantStringValue ?: return
221226
val specifier = InjectionPointSpecifier.entries.firstOrNull { point.endsWith(":$it") } ?: defaultSpecifier
222227
collectVisitor.addResultFilter("specifier") { results, _ ->
@@ -262,7 +267,7 @@ abstract class QualifiedInjectionPoint<T : PsiMember> : InjectionPoint<T>() {
262267

263268
final override fun usesMemberReference() = true
264269

265-
protected abstract fun createLookup(targetClass: ClassNode, m: T, owner: String): LookupElementBuilder
270+
protected abstract fun createLookup(targetClass: ClassNode, m: T, insn: AbstractInsnNode): LookupElementBuilder
266271

267272
protected open fun getInternalName(m: T): String {
268273
return m.realName ?: m.name!!
@@ -273,7 +278,7 @@ abstract class QualifiedInjectionPoint<T : PsiMember> : InjectionPoint<T>() {
273278
result: CollectVisitor.Result<T>,
274279
): LookupElementBuilder {
275280
return qualifyLookup(
276-
createLookup(targetClass, result.target, result.qualifier ?: targetClass.name),
281+
createLookup(targetClass, result.target, result.originalInsn),
277282
targetClass,
278283
result.target,
279284
)
@@ -296,10 +301,11 @@ abstract class QualifiedInjectionPoint<T : PsiMember> : InjectionPoint<T>() {
296301

297302
abstract class AbstractMethodInjectionPoint : QualifiedInjectionPoint<PsiMethod>() {
298303

299-
override fun createLookup(targetClass: ClassNode, m: PsiMethod, owner: String): LookupElementBuilder {
304+
override fun createLookup(targetClass: ClassNode, m: PsiMethod, insn: AbstractInsnNode): LookupElementBuilder {
305+
insn as MethodInsnNode
300306
return JavaLookupElementBuilder.forMethod(
301307
m,
302-
m.getQualifiedMemberReference(owner).toMixinString(),
308+
"L${insn.owner};${insn.name}${insn.desc}",
303309
PsiSubstitutor.EMPTY,
304310
null,
305311
)

src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
2727
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.AT
2828
import com.demonwav.mcdev.platform.mixin.util.findClassNodeByPsiClass
2929
import com.demonwav.mcdev.platform.mixin.util.findMethod
30-
import com.demonwav.mcdev.platform.mixin.util.shortName
3130
import com.demonwav.mcdev.util.MemberReference
3231
import com.demonwav.mcdev.util.constantStringValue
33-
import com.demonwav.mcdev.util.descriptor
3432
import com.demonwav.mcdev.util.fullQualifiedName
3533
import com.demonwav.mcdev.util.internalName
3634
import com.demonwav.mcdev.util.shortName
@@ -110,18 +108,19 @@ class NewInsnInjectionPoint : InjectionPoint<PsiMember>() {
110108
}
111109

112110
override fun createLookup(targetClass: ClassNode, result: CollectVisitor.Result<PsiMember>): LookupElementBuilder? {
111+
val newInsn = result.originalInsn as? TypeInsnNode ?: return null
112+
val methodInsn = findInitCall(newInsn) ?: return null
113113
when (val target = result.target) {
114114
is PsiClass -> {
115115
return JavaLookupElementBuilder.forClass(target, target.internalName)
116116
.withPresentableText(target.shortName ?: return null)
117117
}
118118
is PsiMethod -> {
119-
val ownerName = result.qualifier?.substringAfterLast('.')?.replace('$', '.') ?: targetClass.shortName
120-
val descriptorArgs = target.descriptor?.dropLast(1) ?: return null
121-
val qualifierInternalName = result.qualifier?.replace('.', '/')
119+
val ownerName = methodInsn.owner.substringAfterLast('/').replace('$', '.')
120+
val descriptorArgs = methodInsn.desc.dropLast(1)
122121
return JavaLookupElementBuilder.forMethod(
123122
target,
124-
"${descriptorArgs}L$qualifierInternalName;",
123+
"${descriptorArgs}L${methodInsn.owner};",
125124
PsiSubstitutor.EMPTY,
126125
null,
127126
)

src/main/kotlin/platform/mixin/inspection/mixinextras/LocalArgsOnlyInspection.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import com.demonwav.mcdev.platform.mixin.util.MixinConstants
2929
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.MixinExtras.unwrapLocalRef
3030
import com.demonwav.mcdev.util.constantValue
3131
import com.demonwav.mcdev.util.findContainingMethod
32-
import com.demonwav.mcdev.util.mapFirstNotNull
32+
import com.demonwav.mcdev.util.ifEmpty
3333
import com.intellij.codeInspection.ProblemsHolder
3434
import com.intellij.psi.JavaElementVisitor
3535
import com.intellij.psi.PsiAnnotation
@@ -52,15 +52,15 @@ class LocalArgsOnlyInspection : MixinInspection() {
5252
val parameter = localAnnotation.parentOfType<PsiParameter>() ?: return
5353
val method = parameter.findContainingMethod() ?: return
5454

55-
val targets = method.annotations.mapFirstNotNull { annotation ->
55+
val targets = method.annotations.flatMap { annotation ->
5656
MixinAnnotationHandler.resolveTarget(annotation).asSequence()
5757
.filterIsInstance<MethodTargetMember>()
5858
.map { it.classAndMethod }
59-
} ?: return
59+
}.ifEmpty { return }
6060

6161
val localType = parameter.type.unwrapLocalRef()
6262

63-
if (ModifyVariableArgsOnlyInspection.shouldReport(localAnnotation, localType, targets)) {
63+
if (ModifyVariableArgsOnlyInspection.shouldReport(localAnnotation, localType, targets.asSequence())) {
6464
holder.registerProblem(
6565
localAnnotation.nameReferenceElement ?: localAnnotation,
6666
"@Local may be argsOnly = true",
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2025 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.translations
22+
23+
import com.google.gson.JsonParser
24+
import com.intellij.openapi.project.Project
25+
import com.intellij.openapi.roots.OrderEnumerator
26+
import com.intellij.openapi.roots.ProjectRootModificationTracker
27+
import com.intellij.openapi.vfs.VirtualFile
28+
import com.intellij.psi.util.CachedValueProvider
29+
import com.intellij.psi.util.CachedValuesManager
30+
import java.io.InputStreamReader
31+
import java.io.IOException
32+
import java.nio.charset.StandardCharsets
33+
34+
class DeprecatedTranslations private constructor(val removed: Set<String>, val renamed: Map<String, String>) {
35+
val inverseRenamed = renamed.entries.associateBy({ it.value }) { it.key }
36+
37+
val isEmpty
38+
get() = removed.isEmpty() && renamed.isEmpty()
39+
40+
companion object {
41+
private val DEFAULT = DeprecatedTranslations(emptySet(), emptyMap())
42+
private const val FILE_PATH = "assets/minecraft/lang/deprecated.json"
43+
44+
fun getInstance(project: Project): DeprecatedTranslations {
45+
return CachedValuesManager.getManager(project).getCachedValue(project) {
46+
val file = findFile(project) ?: return@getCachedValue CachedValueProvider.Result(
47+
DEFAULT,
48+
ProjectRootModificationTracker.getInstance(project)
49+
)
50+
CachedValueProvider.Result(getInstanceFromFile(file), file)
51+
}
52+
}
53+
54+
private fun findFile(project: Project): VirtualFile? {
55+
for (libraryRoot in OrderEnumerator.orderEntries(project).librariesOnly().classes().roots) {
56+
val file = libraryRoot.findFileByRelativePath(FILE_PATH) ?: continue
57+
if (!file.isDirectory) {
58+
return file
59+
}
60+
}
61+
62+
return null
63+
}
64+
65+
private fun getInstanceFromFile(file: VirtualFile): DeprecatedTranslations {
66+
try {
67+
val rootElement = InputStreamReader(file.inputStream, StandardCharsets.UTF_8).use { reader ->
68+
JsonParser.parseReader(reader)
69+
}
70+
71+
if (rootElement == null || !rootElement.isJsonObject) {
72+
return DEFAULT
73+
}
74+
75+
val obj = rootElement.asJsonObject
76+
77+
val removedElt = obj.get("removed")
78+
val removed = if (removedElt != null && removedElt.isJsonArray) {
79+
val removedArr = removedElt.asJsonArray
80+
removedArr.mapNotNullTo(HashSet.newHashSet(removedArr.size())) { e ->
81+
if (e.isJsonPrimitive && e.asJsonPrimitive.isString) {
82+
e.asString
83+
} else {
84+
null
85+
}
86+
}
87+
} else {
88+
emptySet()
89+
}
90+
91+
val renamedElt = obj.get("renamed")
92+
val renamed = if (renamedElt != null && renamedElt.isJsonObject) {
93+
val renamedObj = renamedElt.asJsonObject
94+
renamedObj.asMap().asSequence().mapNotNull { (k, v) ->
95+
if (v.isJsonPrimitive && v.asJsonPrimitive.isString) {
96+
k to v.asString
97+
} else {
98+
null
99+
}
100+
}.toMap(HashMap.newHashMap(renamedObj.size()))
101+
} else {
102+
emptyMap()
103+
}
104+
105+
return DeprecatedTranslations(removed, renamed)
106+
} catch (_: IOException) {
107+
return DEFAULT
108+
}
109+
}
110+
}
111+
}

src/main/kotlin/translations/TranslationFiles.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,6 @@ object TranslationFiles {
238238
is FileEntry.Comment -> result.append("# ${entry.text}\n")
239239
is FileEntry.Translation -> result.append("${entry.key}=${entry.text}\n")
240240
FileEntry.EmptyLine -> result.append('\n')
241-
// TODO: IntelliJ shows a false error here without the `else`. The compiler doesn't care because
242-
// FileEntry is a sealed class. When this bug in IntelliJ is fixed, remove this `else`.
243-
else -> {}
244241
}
245242
}
246243

@@ -282,9 +279,6 @@ object TranslationFiles {
282279
result.append("\"${StringUtil.escapeStringCharacters(entry.text)}\",\n")
283280
}
284281
FileEntry.EmptyLine -> result.append('\n')
285-
// TODO: IntelliJ shows a false error here without the `else`. The compiler doesn't care because
286-
// FileEntry is a sealed class. When this bug in IntelliJ is fixed, remove this `else`.
287-
else -> {}
288282
}
289283
}
290284

0 commit comments

Comments
 (0)