Skip to content

Commit

Permalink
refactor: move files around, add progress impl from LSP4IJ
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiandoetsch committed Sep 25, 2024
1 parent 5e12077 commit f7865da
Show file tree
Hide file tree
Showing 16 changed files with 298 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import io.snyk.plugin.isUrlValid
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.ui.SnykBalloonNotificationHelper
import io.snyk.plugin.ui.SnykSettingsDialog
import snyk.common.lsp.FolderConfigSettings
import snyk.common.lsp.settings.FolderConfigSettings
import snyk.common.lsp.LanguageServerWrapper
import javax.swing.JComponent

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.intellij.util.ui.GridBag
import com.intellij.util.ui.JBUI
import org.jetbrains.concurrency.runAsync
import snyk.common.lsp.FolderConfig
import snyk.common.lsp.FolderConfigSettings
import snyk.common.lsp.settings.FolderConfigSettings
import snyk.common.lsp.LanguageServerWrapper
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/io/snyk/plugin/ui/SnykSettingsDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import io.snyk.plugin.ui.settings.ScanTypesPanel
import io.snyk.plugin.ui.settings.SeveritiesEnablementPanel
import io.snyk.plugin.ui.toolwindow.SnykPluginDisposable
import snyk.SnykBundle
import snyk.common.lsp.FolderConfigSettings
import snyk.common.lsp.settings.FolderConfigSettings
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.Insets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import io.snyk.plugin.ui.wrapWithScrollPane
import org.jetbrains.annotations.TestOnly
import snyk.common.ProductType
import snyk.common.SnykError
import snyk.common.lsp.FolderConfigSettings
import snyk.common.lsp.settings.FolderConfigSettings
import snyk.common.lsp.LanguageServerWrapper
import snyk.common.lsp.ScanIssue
import snyk.container.ContainerIssuesForImage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.snyk.plugin.ui.toolwindow

import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.TextRange
Expand All @@ -29,13 +28,10 @@ import io.snyk.plugin.ui.toolwindow.nodes.root.RootIacIssuesTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.root.RootOssTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.root.RootQualityIssuesTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.root.RootSecurityIssuesTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.ChooseBranchNode
import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.InfoTreeNode
import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.SnykFileTreeNode
import snyk.common.ProductType
import snyk.common.SnykFileIssueComparator
import snyk.common.lsp.FolderConfig
import snyk.common.lsp.FolderConfigSettings
import snyk.common.lsp.ScanIssue
import snyk.common.lsp.SnykScanParams
import javax.swing.JTree
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/snyk/common/lsp/LanguageServerWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import snyk.common.lsp.commands.COMMAND_LOGOUT
import snyk.common.lsp.commands.COMMAND_REPORT_ANALYTICS
import snyk.common.lsp.commands.COMMAND_WORKSPACE_FOLDER_SCAN
import snyk.common.lsp.commands.ScanDoneEvent
import snyk.common.lsp.settings.LanguageServerSettings
import snyk.common.lsp.settings.SeverityFilter
import snyk.pluginInfo
import snyk.trust.WorkspaceTrustService
import snyk.trust.confirmScanningAndSetWorkspaceTrustedStateIfNeeded
Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/snyk/common/lsp/SnykLanguageClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import org.eclipse.lsp4j.services.LanguageClient
import org.jetbrains.concurrency.runAsync
import snyk.common.ProductType
import snyk.common.editor.DocumentChanger
import snyk.common.lsp.progress.ProgressManager
import snyk.common.lsp.settings.FolderConfigSettings
import snyk.trust.WorkspaceTrustService
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.CompletableFuture
Expand All @@ -53,7 +55,7 @@ class SnykLanguageClient :
Disposable {
val logger = Logger.getInstance("Snyk Language Server")
val gson = Gson()
private val progressManager = LSPProgressManager()
private val progressManager = ProgressManager()

private var disposed = false
get() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
@file:Suppress("UnstableApiUsage")

package snyk.common.lsp
package snyk.common.lsp.hovers

import com.intellij.markdown.utils.convertMarkdownToHtml
import com.intellij.model.Pointer
import com.intellij.openapi.Disposable
import com.intellij.platform.backend.documentation.DocumentationResult
Expand All @@ -18,6 +17,7 @@ import org.eclipse.lsp4j.Hover
import org.eclipse.lsp4j.HoverParams
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.TextDocumentIdentifier
import snyk.common.lsp.LanguageServerWrapper
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException

Expand Down
45 changes: 45 additions & 0 deletions src/main/kotlin/snyk/common/lsp/progress/Progress.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Copyright (c) 2024 Snyk Ltd
*
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
* Snyk Ltd - adjustments for use in Snyk IntelliJ Plugin
*******************************************************************************/
package snyk.common.lsp.progress

import org.eclipse.lsp4j.WorkDoneProgressNotification
import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.TimeUnit

internal class Progress(val token: String) {
var cancellable: Boolean = false
var done: Boolean = false

private val progressNotifications = LinkedBlockingDeque<WorkDoneProgressNotification>()

var cancelled: Boolean = false
private set

var title: String? = null
get() = if (field != null) field else token

fun add(progressNotification: WorkDoneProgressNotification) {
progressNotifications.add(progressNotification)
}

@get:Throws(InterruptedException::class)
val nextProgressNotification: WorkDoneProgressNotification?
get() = progressNotifications.pollFirst(200, TimeUnit.MILLISECONDS)

fun cancel() {
this.cancelled = true
}
}
220 changes: 220 additions & 0 deletions src/main/kotlin/snyk/common/lsp/progress/ProgressManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Copyright (c) 2024 Snyk Ltd
*
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
* Snyk Ltd - adjustments for use in Snyk IntelliJ Plugin
*******************************************************************************/
package snyk.common.lsp.progress


import com.intellij.ide.impl.ProjectUtil
import com.intellij.openapi.Disposable
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import io.snyk.plugin.ui.toolwindow.SnykPluginDisposable
import org.eclipse.lsp4j.ProgressParams
import org.eclipse.lsp4j.WorkDoneProgressBegin
import org.eclipse.lsp4j.WorkDoneProgressCancelParams
import org.eclipse.lsp4j.WorkDoneProgressCreateParams
import org.eclipse.lsp4j.WorkDoneProgressKind
import org.eclipse.lsp4j.WorkDoneProgressNotification
import org.eclipse.lsp4j.WorkDoneProgressReport
import org.eclipse.lsp4j.jsonrpc.messages.Either
import snyk.common.lsp.LanguageServerWrapper
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.function.Function


class ProgressManager() : Disposable {
private val progresses: MutableMap<String, Progress> = ConcurrentHashMap<String, Progress>()
private var disposed = false
get() {
return SnykPluginDisposable.getInstance().isDisposed() || field
}

fun isDisposed() = disposed

fun createProgress(params: WorkDoneProgressCreateParams): CompletableFuture<Void> {
if (!disposed) {
val token = getToken(params.token)
getProgress(token)
}
return CompletableFuture.completedFuture(null)
}

private fun createProgressIndicator(progress: Progress) {
val token: String = progress.token
if (isDone(progress)) {
progresses.remove(token)
return
}
val title = "Snyk: " + progress.title
val cancellable: Boolean = progress.cancellable
ProgressManager.getInstance()
.run(newProgressBackgroundTask(title, cancellable, progress, token))
}

private fun newProgressBackgroundTask(
title: String,
cancellable: Boolean,
progress: Progress,
token: String
) = object : Task.Backgroundable(ProjectUtil.getActiveProject(), title, cancellable) {
override fun run(indicator: ProgressIndicator) {
try {
while (!isDone(progress)) {
if (indicator.isCanceled) {
progresses.remove(token)
val workDoneProgressCancelParams = WorkDoneProgressCancelParams()
workDoneProgressCancelParams.setToken(token)
val languageServerWrapper = LanguageServerWrapper.getInstance()
if (languageServerWrapper.isInitialized) {
val languageServer = languageServerWrapper.languageServer
languageServer.cancelProgress(workDoneProgressCancelParams)
}
throw ProcessCanceledException()
}

var progressNotification: WorkDoneProgressNotification?
try {
progressNotification = progress.nextProgressNotification
} catch (e: InterruptedException) {
progresses.remove(token)
Thread.currentThread().interrupt()
throw ProcessCanceledException(e)
}
if (progressNotification != null) {
val kind = progressNotification.kind ?: return
when (kind) {
WorkDoneProgressKind.begin -> // 'begin' has been notified
begin(progressNotification as WorkDoneProgressBegin, indicator)

WorkDoneProgressKind.report -> // 'report' has been notified
report(progressNotification as WorkDoneProgressReport, indicator)

WorkDoneProgressKind.end -> Unit
}
}
}
} finally {
progresses.remove(token)
}
}
}

private fun isDone(progress: Progress): Boolean {
return progress.done || progress.cancelled || disposed
}

private fun begin(
begin: WorkDoneProgressBegin,
progressIndicator: ProgressIndicator
) {
val percentage = begin.percentage
progressIndicator.isIndeterminate = percentage == null
updateProgressIndicator(begin.message, percentage, progressIndicator)
}

private fun report(
report: WorkDoneProgressReport,
progressIndicator: ProgressIndicator
) {
updateProgressIndicator(report.message, report.percentage, progressIndicator)
}

@Synchronized
private fun getProgress(token: String): Progress {
var progress: Progress? = progresses[token]
if (progress != null) {
return progress
}
progress = Progress(token)
progresses[token] = progress
return progress
}

private fun updateProgressIndicator(
message: String?,
percentage: Int?,
progressIndicator: ProgressIndicator
) {
if (!message.isNullOrBlank()) {
progressIndicator.text = message
}
if (percentage != null) {
progressIndicator.fraction = percentage.toDouble() / 100
}
}

fun notifyProgress(params: ProgressParams) {
if (params.value == null || params.token == null || disposed) {
return
}
val value = params.value
if (value.isRight) {
// we don't need partial results progress support
return
}

if (!value.isLeft) {
return
}

val progressNotification = value.left
val kind = progressNotification.kind ?: return
val token = getToken(params.token)
var progress: Progress? = progresses[token]
if (progress == null) {
// The server is not spec-compliant and reports progress using server-initiated progress but didn't
// call window/workDoneProgress/create beforehand. In that case, we check the 'kind' field of the
// progress data. If the 'kind' field is 'begin', we set up a progress reporter anyway.
if (kind != WorkDoneProgressKind.begin) {
return
}
progress = getProgress(token)
}

// Add the progress notification
progress.add(progressNotification)
when (progressNotification.kind!!) {
WorkDoneProgressKind.begin -> {
// 'begin' progress
val begin = progressNotification as WorkDoneProgressBegin
progress.title = begin.title
progress.cancellable = begin.cancellable != null && begin.cancellable
// The IJ task is created on 'begin' and not on 'create' to initialize
// the Task with the 'begin' title.
createProgressIndicator(progress)
}

WorkDoneProgressKind.end -> progress.done = true
WorkDoneProgressKind.report -> Unit
}
}

override fun dispose() {
this.disposed = true
progresses.values.forEach(Progress::cancel)
progresses.clear()
}

companion object {
private fun getToken(token: Either<String, Int>): String {
return token.map(
Function.identity()
) { obj: Int -> obj.toString() }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package snyk.common.lsp
package snyk.common.lsp.settings

import com.google.gson.Gson
import com.intellij.openapi.components.BaseState
Expand All @@ -10,6 +10,7 @@ import com.intellij.openapi.components.Storage
import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.annotations.MapAnnotation
import io.snyk.plugin.getContentRootPaths
import snyk.common.lsp.FolderConfig
import java.util.stream.Collectors

@Service
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
@file:Suppress("unused")

package snyk.common.lsp
package snyk.common.lsp.settings

import com.google.gson.annotations.SerializedName
import com.intellij.openapi.components.service
import io.snyk.plugin.pluginSettings
import org.apache.commons.lang3.SystemUtils
import snyk.common.lsp.FolderConfig
import snyk.pluginInfo

data class LanguageServerSettings(
Expand Down
Loading

0 comments on commit f7865da

Please sign in to comment.