@@ -16,9 +16,11 @@ import com.demonwav.mcdev.util.fromJson
16
16
import com.google.gson.Gson
17
17
import com.intellij.ide.plugins.PluginManagerCore
18
18
import com.intellij.openapi.diagnostic.Attachment
19
+ import com.intellij.openapi.util.text.StringUtil
19
20
import java.net.HttpURLConnection
20
21
import java.nio.ByteBuffer
21
22
import java.nio.charset.CodingErrorAction
23
+ import org.apache.commons.httpclient.HttpStatus
22
24
import org.apache.commons.io.IOUtils
23
25
import org.apache.http.HttpHeaders
24
26
import org.apache.http.entity.ContentType
@@ -65,12 +67,8 @@ object AnonymousFeedback {
65
67
errorMessage = " no error"
66
68
}
67
69
68
- var stackTrace = body.remove(" error.stacktrace" )
69
- stackTrace = if (stackTrace.isNullOrEmpty()) {
70
- " no stacktrace"
71
- } else {
72
- linkStacktrace(stackTrace)
73
- }
70
+ val rawStackTrace = body.remove(" error.raw_stacktrace" )?.takeIf { it.isNotBlank() } ? : " no stacktrace"
71
+ val stackTrace = body.remove(" error.stacktrace" )?.takeIf { it.isNotBlank() } ? : " no stacktrace"
74
72
75
73
val sb = StringBuilder ()
76
74
@@ -84,13 +82,20 @@ object AnonymousFeedback {
84
82
sb.append(" </table></td><td><table>\n " )
85
83
}
86
84
val (key, value) = entry
87
- sb.append(" <tr><td><b>" ).append(key).append(" </b></td><td><code>" ).append(value).append(
88
- " </code></td></tr>\n "
89
- )
85
+ sb.append(" <tr><td><b>" )
86
+ .append(key)
87
+ .append(" </b></td><td><code>" )
88
+ .append(value)
89
+ .append(" </code></td></tr>\n " )
90
90
}
91
91
sb.append(" </table></td></tr></table>\n " )
92
92
93
- sb.append(" \n <pre>\n " ).append(stackTrace).append(" \n </pre>\n " )
93
+ sb.append(" \n <pre><code>" ).append(stackTrace).append(" </code></pre>\n " )
94
+
95
+ sb.append(" \n <details><summary>Original stack trace</summary>\n\n ```\n " )
96
+ .append(rawStackTrace)
97
+ .append(" \n ```\n </details>\n " )
98
+
94
99
sb.append(" \n ```\n " ).append(errorMessage).append(" \n ```\n " )
95
100
96
101
if (attachments.isNotEmpty()) {
@@ -149,11 +154,16 @@ object AnonymousFeedback {
149
154
val numberRegex = Regex (" \\ d+" )
150
155
val newLineRegex = Regex (" [\r\n ]+" )
151
156
152
- val stack = envDetails[" error.stacktrace " ]?.replace(numberRegex, " " ) ? : return null
157
+ val stack = envDetails[" error.raw_stacktrace " ]?.replace(numberRegex, " " ) ? : return null
153
158
154
- val stackMcdevParts = stack.lineSequence()
159
+ val ourMcdevParts = stack.lineSequence()
155
160
.filter { line -> line.startsWith(packagePrefix) }
156
- .joinToString(" \n " )
161
+ .map { it.trim() }
162
+ .toList()
163
+
164
+ if (ourMcdevParts.isEmpty()) {
165
+ return null
166
+ }
157
167
158
168
val predicate = fun (map : Map <* , * >): Boolean {
159
169
val body = (map[" body" ] as ? String ? : return false )
@@ -167,23 +177,36 @@ object AnonymousFeedback {
167
177
168
178
val first = body.indexOf(" \n ```\n " , startIndex = 0 ) + 5
169
179
val second = body.indexOf(" \n ```\n " , startIndex = first)
180
+ if (first == 4 || second == - 1 ) {
181
+ return false
182
+ }
183
+
170
184
val stackText = body.substring(first, second)
171
185
172
- val mcdevParts = stackText.lineSequence()
186
+ val theirMcdevParts = stackText.lineSequence()
173
187
.filter { line -> line.startsWith(packagePrefix) }
174
- .joinToString(" \n " )
188
+ .map { it.trim() }
189
+ .toList()
175
190
176
- return stackMcdevParts == mcdevParts
191
+ return ourMcdevParts == theirMcdevParts
177
192
}
178
193
179
194
// Look first for an open issue, then for a closed issue if one isn't found
180
195
val block = getAllIssues(openIssueUrl, factory)?.firstOrNull(predicate)
181
- ? : getAllIssues(closedIssueUrl, factory)?.firstOrNull(predicate)
196
+ ? : getAllIssues(closedIssueUrl, factory, limit = 300 )?.firstOrNull(predicate)
182
197
? : return null
183
198
return (block[" number" ] as Double ).toInt()
184
199
}
185
200
186
- private fun getAllIssues (url : String , factory : HttpConnectionFactory ): List <Map <* , * >>? {
201
+ private fun getMcdevStackElementLines (stack : String , numberRegex : Regex , linkRegex : Regex ): List <String > {
202
+ return stack.lineSequence()
203
+ .mapNotNull { line -> linkRegex.matchEntire(line)?.groups?.get(" content" )?.value }
204
+ .map { line -> StringUtil .unescapeXmlEntities(line) }
205
+ .map { line -> line.replace(numberRegex, " " ) }
206
+ .toList()
207
+ }
208
+
209
+ private fun getAllIssues (url : String , factory : HttpConnectionFactory , limit : Int = -1): List <Map <* , * >>? {
187
210
var useAuthed = false
188
211
189
212
var next: String? = url
@@ -197,13 +220,13 @@ object AnonymousFeedback {
197
220
198
221
connection.connect()
199
222
200
- if (connection.responseCode == 403 && ! useAuthed) {
223
+ if (connection.responseCode == HttpStatus . SC_FORBIDDEN && ! useAuthed) {
201
224
useAuthed = true
202
225
next = replaceWithAuth(next)
203
226
continue
204
227
}
205
228
206
- if (connection.responseCode != 200 ) {
229
+ if (connection.responseCode != HttpStatus . SC_OK ) {
207
230
return null
208
231
}
209
232
@@ -216,6 +239,10 @@ object AnonymousFeedback {
216
239
val response = Gson ().fromJson<List <Map <* , * >>>(data)
217
240
list.addAll(response)
218
241
242
+ if (limit > 0 && list.size >= limit) {
243
+ return list
244
+ }
245
+
219
246
val link = connection.getHeaderField(" Link" )
220
247
221
248
next = getNextLink(link, useAuthed)
@@ -277,7 +304,7 @@ object AnonymousFeedback {
277
304
}
278
305
279
306
val responseCode = connection.responseCode
280
- if (responseCode != 201 ) {
307
+ if (responseCode != HttpStatus . SC_CREATED ) {
281
308
throw RuntimeException (" Expected HTTP_CREATED (201), obtained $responseCode instead." )
282
309
}
283
310
@@ -287,7 +314,7 @@ object AnonymousFeedback {
287
314
}
288
315
connection.disconnect()
289
316
290
- return Gson ().fromJson< Map < * , * >> (body)
317
+ return Gson ().fromJson(body)
291
318
}
292
319
293
320
private fun getConnection (factory : HttpConnectionFactory , url : String ): HttpURLConnection {
@@ -300,74 +327,6 @@ object AnonymousFeedback {
300
327
return connection
301
328
}
302
329
303
- private fun linkStacktrace (stacktrace : String ): String {
304
- val versionRegex = Regex (""" (?<intellijVersion>\d{4}\.\d)-(?<pluginVersion>\d+\.\d+\.\d+)""" )
305
-
306
- val version = PluginUtil .pluginVersion
307
- val match = versionRegex.matchEntire(version) ? : return stacktrace
308
-
309
- val intellijVersion = match.groups[" intellijVersion" ]?.value ? : return stacktrace
310
- val pluginVersion = match.groups[" pluginVersion" ]?.value ? : return stacktrace
311
-
312
- val tag = " $pluginVersion -$intellijVersion "
313
-
314
- // v stack element text v
315
- // at com.demonwav.mcdev.facet.MinecraftFacet.shouldShowPluginIcon(MinecraftFacet.kt:185)
316
- // prefix ^ class path ^ ^ file name ^ ^ ^ line number
317
- val stackElementRegex = Regex (
318
- """ (?<prefix>\s+at\s+)""" +
319
- """ (?<stackElementText>""" +
320
- """ (?<className>com\.demonwav\.mcdev(?:\.\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*)+)""" +
321
- """ (?:\.\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*|<(?:cl)?init>)""" +
322
- """ \((?<fileName>.*\.\w+):(?<lineNumber>\d+)\)""" +
323
- """ )\s*"""
324
- )
325
-
326
- val baseTagUrl = " https://github.com/minecraft-dev/MinecraftDev/blob/$tag /src/main/kotlin/"
327
-
328
- val sb = StringBuilder (stacktrace.length * 2 )
329
-
330
- for (line in stacktrace.lineSequence()) {
331
- val lineMatch = stackElementRegex.matchEntire(line)
332
- if (lineMatch == null ) {
333
- sb.append(line).append(' \n ' )
334
- continue
335
- }
336
-
337
- val prefix = lineMatch.groups[" prefix" ]?.value
338
- val className = lineMatch.groups[" className" ]?.value
339
- val fileName = lineMatch.groups[" fileName" ]?.value
340
- val lineNumber = lineMatch.groups[" lineNumber" ]?.value
341
- val stackElementText = lineMatch.groups[" stackElementText" ]?.value
342
-
343
- if (prefix == null || className == null || fileName == null ||
344
- lineNumber == null || stackElementText == null
345
- ) {
346
- sb.append(line).append(' \n ' )
347
- continue
348
- }
349
-
350
- val path = className.substringAfter(" com.demonwav.mcdev." )
351
- .substringBeforeLast(' .' )
352
- .replace(' .' , ' /' )
353
- sb.apply {
354
- append(prefix)
355
- append(" <a href=\" " )
356
- append(baseTagUrl)
357
- append(path)
358
- append(' /' )
359
- append(fileName)
360
- append(" #L" )
361
- append(lineNumber)
362
- append(" \" >" )
363
- append(stackElementText)
364
- append(" </a>\n " )
365
- }
366
- }
367
-
368
- return sb.toString()
369
- }
370
-
371
330
private val userAgent by lazy {
372
331
var agent = " Minecraft Development IntelliJ IDEA plugin"
373
332
0 commit comments