@@ -13,7 +13,10 @@ import com.jetbrains.toolbox.api.remoteDev.connection.ToolboxProxySettings
1313import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
1414import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
1515import com.jetbrains.toolbox.api.ui.ToolboxUi
16+ import kotlinx.coroutines.CancellationException
17+ import kotlinx.coroutines.CoroutineName
1618import kotlinx.coroutines.CoroutineScope
19+ import kotlinx.coroutines.launch
1720import java.net.URL
1821import java.util.UUID
1922
@@ -49,44 +52,53 @@ data class CoderToolboxContext(
4952 ? : settingsStore.defaultURL.toURL()
5053 }
5154
52- suspend fun logAndShowError (title : String , error : String ) {
55+ fun logAndShowError (title : String , error : String ) {
5356 logger.error(error)
54- ui.showSnackbar(
55- UUID .randomUUID().toString(),
56- i18n.pnotr(title),
57- i18n.pnotr(error),
58- i18n.ptrl(" OK" )
59- )
57+ showSnackbar(title, error)
6058 }
6159
62- suspend fun logAndShowError (title : String , error : String , exception : Exception ) {
60+ fun logAndShowError (title : String , error : String , exception : Exception ) {
6361 logger.error(exception, error)
64- ui.showSnackbar(
65- UUID .randomUUID().toString(),
66- i18n.pnotr(title),
67- i18n.pnotr(error),
68- i18n.ptrl(" OK" )
69- )
62+ showSnackbar(title, error)
7063 }
7164
72- suspend fun logAndShowWarning (title : String , warning : String ) {
65+ fun logAndShowWarning (title : String , warning : String ) {
7366 logger.warn(warning)
74- ui.showSnackbar(
75- UUID .randomUUID().toString(),
76- i18n.pnotr(title),
77- i18n.pnotr(warning),
78- i18n.ptrl(" OK" )
79- )
67+ showSnackbar(title, warning)
8068 }
8169
82- suspend fun logAndShowInfo (title : String , info : String ) {
70+ fun logAndShowInfo (title : String , info : String ) {
8371 logger.info(info)
84- ui.showSnackbar(
85- UUID .randomUUID().toString(),
86- i18n.pnotr(title),
87- i18n.pnotr(info),
88- i18n.ptrl(" OK" )
89- )
72+ showSnackbar(title, info)
73+ }
74+
75+ /* *
76+ * Displays a snackbar on a child of the plugin coroutine scope rather than on the
77+ * caller's coroutine, without waiting for it.
78+ *
79+ * Toolbox keeps the [ToolboxUi.showSnackbar] coroutine suspended for the entire lifetime
80+ * of the snackbar and cancels it (rather than resuming it) when the snackbar goes away.
81+ * Calling it directly on the caller's coroutine would therefore either block the caller
82+ * until the snackbar is gone or, on dismissal, abruptly cancel the caller (e.g. the URI
83+ * handler) - skipping any code that runs after the error is shown, such as resetting the
84+ * busy state. Launching it fire-and-forget on the plugin scope lets the caller continue
85+ * immediately while the snackbar lives independently.
86+ */
87+ fun showSnackbar (title : String , text : String ) {
88+ cs.launch(CoroutineName (" snackbar" )) {
89+ try {
90+ ui.showSnackbar(
91+ UUID .randomUUID().toString(),
92+ i18n.pnotr(title),
93+ i18n.pnotr(text),
94+ i18n.ptrl(" OK" )
95+ )
96+ } catch (_: CancellationException ) {
97+ // Expected when the snackbar is dismissed or the plugin scope shuts down.
98+ } catch (ex: Exception ) {
99+ logger.error(ex, " Failed to display snackbar with title '$title '" )
100+ }
101+ }
90102 }
91103
92104 fun popupPluginMainPage () {
0 commit comments