diff --git a/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt b/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt index 9ff7392aa..82f96de65 100644 --- a/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt +++ b/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt @@ -85,6 +85,7 @@ class SnykPostStartupActivity : ProjectActivity { getAnalyticsScanListener(project)?.initScanListener() } catch (ignored: Exception) { // do nothing to not break UX for analytics + ignored.printStackTrace() } } diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt index c15bdb2fd..a169eb67d 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt @@ -79,17 +79,10 @@ class SnykTaskQueueService(val project: Project) { }) } - @OptIn(DelicateCoroutinesApi::class) fun connectProjectToLanguageServer(project: Project) { synchronized(ls) { - if (!ls.isInitialized) { + if (!ls.isInitialized && !ls.isInitializing) { ls.initialize() - GlobalScope.launch { - ls.process.errorStream.bufferedReader().forEachLine { println(it) } - } - GlobalScope.launch { - ls.startListening() - } } } val addedWorkspaceFolders = ProjectRootManager.getInstance(project).contentRoots @@ -134,7 +127,7 @@ class SnykTaskQueueService(val project: Project) { do { indicator.checkCanceled() Thread.sleep(WAIT_FOR_DOWNLOAD_MILLIS) - } while (isCliDownloading()) + } while (!isCliInstalled() || isCliDownloading()) } private fun scheduleContainerScan() { @@ -328,7 +321,7 @@ class SnykTaskQueueService(val project: Project) { } companion object { - private const val WAIT_FOR_DOWNLOAD_MILLIS = 500L + private const val WAIT_FOR_DOWNLOAD_MILLIS = 1000L val ls = LanguageServerWrapper() } } diff --git a/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt b/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt index e571708c1..af5b31ea3 100644 --- a/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt +++ b/src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt @@ -1,11 +1,15 @@ package snyk.common.lsp +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.roots.ProjectRootManager import io.snyk.plugin.getCliFile import io.snyk.plugin.pluginSettings import io.snyk.plugin.snykcode.core.RunUtils +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.eclipse.lsp4j.ClientInfo import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams import org.eclipse.lsp4j.ExecuteCommandParams @@ -24,6 +28,7 @@ import java.util.concurrent.TimeUnit class LanguageServerWrapper(private val lsPath: String = getCliFile().absolutePath) { private val gson = com.google.gson.Gson() + val logger = Logger.getInstance("Snyk Language Server") /** * The language client is used to receive messages from LS @@ -46,24 +51,41 @@ class LanguageServerWrapper(private val lsPath: String = getCliFile().absolutePa */ lateinit var process: Process + var isInitializing: Boolean = false val isInitialized: Boolean get() = ::languageClient.isInitialized && ::languageServer.isInitialized && ::process.isInitialized && process.info().startInstant().isPresent + + @OptIn(DelicateCoroutinesApi::class) internal fun initialize() { - val snykLanguageClient = SnykLanguageClient() - languageClient = snykLanguageClient - val logLevel = if (snykLanguageClient.logger.isDebugEnabled) "debug" else "info" - val cmd = listOf(lsPath, "language-server", "-l", logLevel) + try { + isInitializing = true + val snykLanguageClient = SnykLanguageClient() + languageClient = snykLanguageClient + val logLevel = if (snykLanguageClient.logger.isDebugEnabled) "debug" else "info" + val cmd = listOf(lsPath, "language-server", "-l", logLevel) - val processBuilder = ProcessBuilder(cmd) - pluginSettings().token?.let { EnvironmentHelper.updateEnvironment(processBuilder.environment(), it) } + val processBuilder = ProcessBuilder(cmd) + pluginSettings().token?.let { EnvironmentHelper.updateEnvironment(processBuilder.environment(), it) } - process = processBuilder.start() - launcher = LSPLauncher.createClientLauncher(languageClient, process.inputStream, process.outputStream) - languageServer = launcher.remoteProxy + process = processBuilder.start() + launcher = LSPLauncher.createClientLauncher(languageClient, process.inputStream, process.outputStream) + languageServer = launcher.remoteProxy - sendInitializeMessage() + GlobalScope.launch { + process.errorStream.bufferedReader().forEachLine { println(it) } + } + GlobalScope.launch { + launcher.startListening() + } + + sendInitializeMessage() + } catch (e: Exception) { + logger.error(e) + } finally { + isInitializing = false + } } private fun determineWorkspaceFolders(): List { @@ -76,11 +98,6 @@ class LanguageServerWrapper(private val lsPath: String = getCliFile().absolutePa return workspaceFolders.toList() } - fun startListening() { - // Start the server - launcher.startListening() - } - fun sendInitializeMessage() { val workspaceFolders = determineWorkspaceFolders() @@ -98,27 +115,36 @@ class LanguageServerWrapper(private val lsPath: String = getCliFile().absolutePa project, "Updating workspace folders for project: ${project.name}" ) { - while (!isInitialized) { - Thread.sleep(100) + try { + ensureLanguageServerInitialized() + val params = DidChangeWorkspaceFoldersParams() + params.event = WorkspaceFoldersChangeEvent(added, removed) + languageServer.workspaceService.didChangeWorkspaceFolders(params) + } catch (e: Exception) { + logger.error(e) } - val params = DidChangeWorkspaceFoldersParams() - params.event = WorkspaceFoldersChangeEvent(added, removed) - languageServer.workspaceService.didChangeWorkspaceFolders(params) } } - fun sendReportAnalyticsCommand(scanDoneEvent: ScanDoneEvent) { - while (!isInitialized) { + private fun ensureLanguageServerInitialized() { + while (isInitializing) { Thread.sleep(100) } + if (!isInitialized) { + initialize() + } + } + + fun sendReportAnalyticsCommand(scanDoneEvent: ScanDoneEvent) { + ensureLanguageServerInitialized() try { val eventString = gson.toJson(scanDoneEvent) val param = ExecuteCommandParams() param.command = "snyk.reportAnalytics" param.arguments = listOf(eventString) languageServer.workspaceService.executeCommand(param) - } catch (ignored: Exception) { - // do nothing to not break UX for analytics + } catch (e: Exception) { + logger.error(e) } }