Skip to content

Commit aafcb94

Browse files
committed
Improve previewer crash recovery
1 parent 7ebc973 commit aafcb94

File tree

6 files changed

+221
-34
lines changed

6 files changed

+221
-34
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package me.fornever.avaloniarider.exceptions
2+
3+
import org.jetbrains.annotations.Nls
4+
5+
class AvaloniaPreviewerExecutionException(
6+
val exitCode: Int?,
7+
val processOutput: String?
8+
) : Exception(buildMessage(exitCode, processOutput))
9+
10+
private fun buildMessage(exitCode: Int?, output: String?): @Nls String {
11+
val codePart = exitCode?.let { "Previewer process exited with code $it." }
12+
?: "Previewer process exited unexpectedly."
13+
val outputPart = output
14+
?.trim()
15+
?.takeIf { it.isNotEmpty() }
16+
?.let { "\n\n$it" }
17+
?: ""
18+
return codePart + outputPart
19+
}

src/rider/main/kotlin/me/fornever/avaloniarider/idea/editor/actions/RunnableAssemblySelectorAction.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ class RunnableAssemblySelectorAction(
236236
): List<RunnableProject> {
237237
val filteredProjectList = filteredProjects.toList()
238238
val targetFileProjectEntity = xamlFile.getProjectContainingFile(lifetime, project)
239+
if (targetFileProjectEntity == null) {
240+
logger.warn("Unable to determine project containing file $xamlFile; falling back to all runnable projects")
241+
return filteredProjectList
242+
.filter { !isWebAssemblyProject(it) }
243+
.sortedWith(compareBy(
244+
{ !it.name.endsWith(".Desktop", ignoreCase = true) },
245+
{ it.name }
246+
))
247+
}
239248
val targetFileProjectPath = targetFileProjectEntity.url!!.toPath()
240249
val runnableProjectPaths = filteredProjectList
241250
.filter { !FileUtil.pathsEqual(it.projectFilePath, targetFileProjectPath.toString()) }

src/rider/main/kotlin/me/fornever/avaloniarider/previewer/AvaloniaPreviewerProcess.kt

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ data class AvaloniaPreviewerParameters(
3939
val workingDirectory: Path
4040
)
4141

42+
data class AvaloniaPreviewerProcessResult(
43+
val exitCode: Int?,
44+
val outputSnippet: String?
45+
)
46+
4247
class AvaloniaPreviewerProcess(
4348
private val project: Project,
4449
private val lifetime: Lifetime,
@@ -79,16 +84,18 @@ class AvaloniaPreviewerProcess(
7984
executionMode: ProcessExecutionMode,
8085
commandLine: GeneralCommandLine,
8186
consoleView: ConsoleView?,
82-
title: String
87+
title: String,
88+
outputListener: (String) -> Unit = {}
8389
): ProcessHandler = when (executionMode) {
84-
ProcessExecutionMode.Run -> runProcess(commandLine, consoleView, title)
90+
ProcessExecutionMode.Run -> runProcess(commandLine, consoleView, title, outputListener)
8591
ProcessExecutionMode.Debug -> debugProcess(commandLine, consoleView)
8692
}
8793

8894
private fun runProcess(
8995
commandLine: GeneralCommandLine,
9096
consoleView: ConsoleView?,
91-
title: String
97+
title: String,
98+
outputListener: (String) -> Unit
9299
): OSProcessHandler {
93100
val processHandler = lifetime.bracketOrThrowEx({
94101
object : OSProcessHandler(commandLine) {
@@ -97,6 +104,7 @@ class AvaloniaPreviewerProcess(
97104

98105
override fun notifyTextAvailable(text: String, outputType: Key<*>) {
99106
logger.info("$title [$outputType]: $text")
107+
outputListener(text)
100108
super.notifyTextAvailable(text, outputType)
101109
}
102110

@@ -158,13 +166,41 @@ class AvaloniaPreviewerProcess(
158166
transport: PreviewerTransport,
159167
method: PreviewerMethod,
160168
title: String
161-
) {
169+
): AvaloniaPreviewerProcessResult {
162170
logger.info("1/4: generating process command line")
163171
val commandLine = getCommandLine(transport, method)
164172
logger.info("2/3: starting a process")
165-
val process = startProcess(executionMode, commandLine, consoleView, title)
173+
val outputCollector = OutputCollector()
174+
val process = startProcess(
175+
executionMode,
176+
commandLine,
177+
consoleView,
178+
title,
179+
outputCollector::append
180+
)
166181
logger.info("3/3: awaiting termination")
167182
waitForTermination(process, title)
183+
val exitCode = process.exitCode
184+
val outputSnippet = outputCollector.content()
185+
return AvaloniaPreviewerProcessResult(exitCode, outputSnippet)
186+
}
187+
188+
private class OutputCollector(private val maxChars: Int = 32 * 1024) {
189+
private val buffer = StringBuilder()
190+
191+
fun append(text: String) {
192+
synchronized(buffer) {
193+
buffer.append(text)
194+
if (buffer.length > maxChars) {
195+
val excess = buffer.length - maxChars
196+
buffer.delete(0, excess)
197+
}
198+
}
199+
}
200+
201+
fun content(): String? = synchronized(buffer) {
202+
if (buffer.isEmpty()) null else buffer.toString()
203+
}
168204
}
169205
}
170206

0 commit comments

Comments
 (0)