From 00c5159470aebfba3d61ff2037d118f38b27707e Mon Sep 17 00:00:00 2001
From: SeeBasTStick <sebastian.stock@hhu.de>
Date: Tue, 21 Jul 2020 16:38:36 +0200
Subject: [PATCH] added dynamic response, mainly translated existing vscode
 server to java

---
 .gitignore                                    |  2 +-
 build.gradle.kts                              |  6 +-
 .../b/language/server/BDocumentService.kt     | 46 ++++++++---
 .../b/language/server/BWorkspaceService.kt    | 20 ++++-
 .../kotlin/b/language/server/ProBInterface.kt | 82 +++++++++++++++++++
 .../b/language/server/ProblemHandler.kt       | 82 +++++++++++++++++++
 src/main/kotlin/b/language/server/Server.kt   | 50 +++++++++--
 src/main/kotlin/b/language/server/Util.kt     | 14 ++++
 .../b/language/server/dataStorage/Problem.kt  | 19 +++++
 .../b/language/server/dataStorage/Settings.kt |  6 ++
 10 files changed, 301 insertions(+), 26 deletions(-)
 create mode 100644 src/main/kotlin/b/language/server/ProBInterface.kt
 create mode 100644 src/main/kotlin/b/language/server/ProblemHandler.kt
 create mode 100644 src/main/kotlin/b/language/server/Util.kt
 create mode 100644 src/main/kotlin/b/language/server/dataStorage/Problem.kt
 create mode 100644 src/main/kotlin/b/language/server/dataStorage/Settings.kt

diff --git a/.gitignore b/.gitignore
index 9460fd2..eac18f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,4 +27,4 @@ hs_err_pid*
 
 # Ignore Gradle build output directory
 build
-/.idea/
+/.idea/*
diff --git a/build.gradle.kts b/build.gradle.kts
index 03f0b4e..e87b3b6 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -15,7 +15,6 @@ plugins {
 
 repositories {
     // Use jcenter for resolving dependencies.
-    // You can declare any Maven/Ivy/file repository here.
     jcenter()
 }
 
@@ -26,15 +25,18 @@ dependencies {
     // Use the Kotlin JDK 8 standard library.
     implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
 
+    // eclipse lsp implementation
     implementation("org.eclipse.lsp4j", "org.eclipse.lsp4j",  "0.9.0")
 
+    // json converter
+    implementation("com.google.code.gson" ,"gson" ,"2.8.6")
+
     // Use the Kotlin test library.
     testImplementation("org.jetbrains.kotlin:kotlin-test")
 
     // Use the Kotlin JUnit integration.
     testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
 }
-
 application {
     // Define the main class for the application.
     mainClassName = "b.language.server.AppKt"
diff --git a/src/main/kotlin/b/language/server/BDocumentService.kt b/src/main/kotlin/b/language/server/BDocumentService.kt
index a0dbf76..57534bc 100644
--- a/src/main/kotlin/b/language/server/BDocumentService.kt
+++ b/src/main/kotlin/b/language/server/BDocumentService.kt
@@ -1,14 +1,16 @@
 package b.language.server
 
+import b.language.server.dataStorage.Problem
 import org.eclipse.lsp4j.*
-import org.eclipse.lsp4j.jsonrpc.messages.Either
 import org.eclipse.lsp4j.services.TextDocumentService
-import java.util.concurrent.CompletableFuture
+import java.io.File
+import java.net.URI
 import java.util.concurrent.ConcurrentHashMap
 
 class BDocumentService(private val server: Server) : TextDocumentService {
 
     private val documents = ConcurrentHashMap<String, String>()
+    private val issueTracker : ConcurrentHashMap<String, ArrayList<String>> = ConcurrentHashMap()
 
     /**
      * The document open notification is sent from the client to the server to
@@ -29,8 +31,30 @@ class BDocumentService(private val server: Server) : TextDocumentService {
      * Registration Options: TextDocumentSaveRegistrationOptions
      */
     override fun didSave(params: DidSaveTextDocumentParams?) {
-        println("Save")
-        TODO("Not yet implemented")
+
+
+        val uri = URI(params!!.textDocument.uri)
+
+        val path = File(uri.path)
+
+        val errorPath = File(path.parent + "/tmp/_error.json")
+        val errorDict = File(path.parent + "/tmp")
+
+        val clientSettings = server.getDocumentSettings(params.textDocument.uri)
+
+        clientSettings.thenAccept{ setting ->
+            val probInterface = ProBInterface(setting.probHome, path, errorDict, errorPath, server = server)
+            probInterface.createFolder()
+            probInterface.performActionOnDocument()
+
+            val problemHandler = ProblemHandler()
+
+            val problemList: List<Problem> = problemHandler.readProblems(errorPath.absolutePath)
+            val diagnostics: List<Diagnostic> = problemHandler.transformProblems(problemList)
+
+            server.languageClient.publishDiagnostics(PublishDiagnosticsParams(params.textDocument.uri, diagnostics))
+        }
+
     }
 
     /**
@@ -42,7 +66,7 @@ class BDocumentService(private val server: Server) : TextDocumentService {
      * Registration Options: TextDocumentRegistrationOptions
      */
     override fun didClose(params: DidCloseTextDocumentParams?) {
-        TODO("Not yet implemented")
+        server.documentSettings.remove(params!!.textDocument.uri)
     }
 
     /**
@@ -52,16 +76,16 @@ class BDocumentService(private val server: Server) : TextDocumentService {
      * Registration Options: TextDocumentChangeRegistrationOptions
      */
     override fun didChange(params: DidChangeTextDocumentParams?) {
-
-        CompletableFuture.runAsync {
-             val diagnostics : ArrayList<Diagnostic> =
-                     arrayListOf(Diagnostic(Range(Position(1,1), Position(1,1)), "Test",  DiagnosticSeverity.Error, "Test"))
-            server.languageClient.publishDiagnostics(PublishDiagnosticsParams(params?.textDocument?.uri, diagnostics))
-        }
+      //Nothing
     }
 
 
 
 
 
+
+
+
+
+
 }
\ No newline at end of file
diff --git a/src/main/kotlin/b/language/server/BWorkspaceService.kt b/src/main/kotlin/b/language/server/BWorkspaceService.kt
index 2ae8540..70c5159 100644
--- a/src/main/kotlin/b/language/server/BWorkspaceService.kt
+++ b/src/main/kotlin/b/language/server/BWorkspaceService.kt
@@ -1,16 +1,20 @@
 package b.language.server
 
+import b.language.server.dataStorage.Settings
+import com.google.gson.Gson
+import com.google.gson.JsonObject
 import org.eclipse.lsp4j.DidChangeConfigurationParams
 import org.eclipse.lsp4j.DidChangeWatchedFilesParams
 import org.eclipse.lsp4j.services.WorkspaceService
+import java.io.File
 
-class BWorkspaceService : WorkspaceService {
+class BWorkspaceService(private val server : Server) : WorkspaceService {
     /**
      * The watched files notification is sent from the client to the server when
      * the client detects changes to file watched by the language client.
      */
     override fun didChangeWatchedFiles(params: DidChangeWatchedFilesParams?) {
-        TODO("Not yet implemented")
+        // Not needed
     }
 
     /**
@@ -18,6 +22,14 @@ class BWorkspaceService : WorkspaceService {
      * configuration settings.
      */
     override fun didChangeConfiguration(params: DidChangeConfigurationParams?) {
-        TODO("Not yet implemented")
+        if(server.configurationAbility) {
+            server.documentSettings.clear()
+        }else{
+            if (params!!.settings is JsonObject) {
+                val settings: JsonObject = params.settings as JsonObject
+                val documentSetting = castJsonToSetting(settings)
+                server.globalSettings = documentSetting
+            }
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/b/language/server/ProBInterface.kt b/src/main/kotlin/b/language/server/ProBInterface.kt
new file mode 100644
index 0000000..745f176
--- /dev/null
+++ b/src/main/kotlin/b/language/server/ProBInterface.kt
@@ -0,0 +1,82 @@
+package b.language.server
+
+import org.eclipse.lsp4j.MessageParams
+import org.eclipse.lsp4j.MessageType
+import java.io.File
+import java.io.FileWriter
+import java.io.InputStream
+
+/**
+ * Interacts with ProB via Commandline
+ * @param probHome the target ProbCli
+ * @param fileToCheck the main machine to start evaluating
+ * @param errorDict the path to store errors
+ * @param wdActive is wd checks enabled?
+ * @param strictActive is strict enabled?
+ * @param performanceHintsActive are performance hints activated?
+ * @param server the server used to connect with
+ */
+class ProBInterface(probHome : File,
+                    fileToCheck : File,
+                    val errorDict : File,
+                    val errorPath : File,
+                    wdActive : Boolean = false,
+                    strictActive : Boolean = false,
+                    performanceHintsActive : Boolean = false,
+                    private val server : Server){
+
+
+    private val configuration : String = " -p MAX_INITIALISATIONS 0 -version "
+    private val ndjson : String = " -p NDJSON_ERROR_LOG_FILE "
+    private val wd : String = if(wdActive){
+        " -wd-check -release_java_parser "
+    }else{
+        " "
+    }
+    private val strict : String = if(strictActive){
+        " -p STRICT_CLASH_CHECKING TRUE -p TYPE_CHECK_DEFINITIONS TRUE -lint "
+    }else{
+        " "
+    }
+    private val performanceHints : String = if(performanceHintsActive){
+        " -p PERFORMANCE_INFO TRUE "
+    }else{
+        " "
+    }
+
+    private val command : String
+
+    init {
+        command = probHome.path +
+                configuration +
+                performanceHints +
+                strict +
+                wd +
+                fileToCheck.absoluteFile +
+                ndjson +
+                errorPath.absoluteFile
+    }
+
+
+    fun createFolder() : Boolean{
+        errorDict.mkdirs()
+        FileWriter(errorPath, false).close()
+        return errorDict.mkdirs()
+    }
+
+    /**
+     * Executes the command and returns and sends an error message if something fails
+     */
+    fun performActionOnDocument() {
+        val process : Process = Runtime.getRuntime().exec(command)
+        val output : InputStream = process.inputStream
+
+        process.waitFor() //we must wait here to ensure correct behavior when reading an error
+
+        val outputAsString  = String(output.readAllBytes())
+        if(!outputAsString.contains("ProB Command Line Interface")){
+            server.languageClient.showMessage(
+                    MessageParams(MessageType.Error, "Something went wrong when calling probcli $command"))
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/b/language/server/ProblemHandler.kt b/src/main/kotlin/b/language/server/ProblemHandler.kt
new file mode 100644
index 0000000..da05b45
--- /dev/null
+++ b/src/main/kotlin/b/language/server/ProblemHandler.kt
@@ -0,0 +1,82 @@
+package b.language.server
+
+import b.language.server.dataStorage.Problem
+import com.google.gson.Gson
+import org.eclipse.lsp4j.Diagnostic
+import org.eclipse.lsp4j.DiagnosticSeverity
+import org.eclipse.lsp4j.Position
+import org.eclipse.lsp4j.Range
+import java.io.*
+
+
+
+/**
+ * Responsible for reading Errors from the corresponding JSON File and sending transforming them into a object
+ */
+class ProblemHandler {
+
+    /**
+     * Reads the errors and transforms them to java objects
+     * @param path the path to the error document
+     * @return the list of read errors
+     */
+    fun readProblems(path : String): List<Problem> {
+        val jsonStrings : ArrayList<String> = ArrayList()
+        BufferedReader(FileReader(path)).use { br ->
+            var line: String?
+            while (br.readLine().also { line = it } != null) {
+                jsonStrings.add(line!!)
+            }
+        }
+
+
+        return jsonStrings.toList().map { string -> Gson().fromJson(string, Problem::class.java) }
+    }
+
+    /**
+     * Transforms errors to error messages
+     * @param problems the list of errors to transform
+     * @return the transformed errors
+     */
+    fun transformProblems(problems : List<Problem>): List<Diagnostic> {
+        return problems.
+                /**
+                 * Some errors from prob_cli have negative values when there is no exact error location - we set them
+                 * to the first line
+                 */
+                map{ problem ->
+                    if (problem.start.line == -1 && problem.start.character == -1 &&
+                            problem.end.line == -1 && problem.end.character == -1)
+                    {
+                        Problem(problem.type,
+                                problem.message,
+                                problem.reason,
+                                problem.file,
+                                Position(1, 0),
+                                Position(1, Integer.MAX_VALUE),
+                                problem.version)
+                    }
+                    else{
+                        problem
+                    }
+                }
+                .map { problem -> Diagnostic(Range(problem.start, problem.end), problem.message,
+                when (problem.type) {
+                    "error" -> {
+                        DiagnosticSeverity.Error
+                    }
+                    "warning" -> {
+                        DiagnosticSeverity.Warning
+                    }
+                    "information" -> {
+                        DiagnosticSeverity.Information
+                    }
+                    else -> {
+                        DiagnosticSeverity.Hint
+                    }
+                },
+                problem.file,
+                "probcli v.${problem.version}")}
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/b/language/server/Server.kt b/src/main/kotlin/b/language/server/Server.kt
index 3e5e5b1..eab1196 100644
--- a/src/main/kotlin/b/language/server/Server.kt
+++ b/src/main/kotlin/b/language/server/Server.kt
@@ -1,11 +1,14 @@
 package b.language.server
 
+import b.language.server.dataStorage.Settings
+import com.google.gson.JsonObject
 import org.eclipse.lsp4j.*
 import org.eclipse.lsp4j.services.LanguageClient
 import org.eclipse.lsp4j.services.LanguageServer
 import org.eclipse.lsp4j.services.TextDocumentService
 import org.eclipse.lsp4j.services.WorkspaceService
 import java.util.concurrent.CompletableFuture
+import kotlin.collections.HashMap
 import kotlin.system.exitProcess
 
 
@@ -14,12 +17,13 @@ class Server : LanguageServer {
     private val textDocumentService : TextDocumentService
     private val bWorkspaceService : WorkspaceService
     lateinit var languageClient : LanguageClient
-
-
+    var globalSettings : Settings = Settings()
+    val documentSettings : HashMap<String, CompletableFuture<Settings>> = HashMap()
+    var configurationAbility : Boolean = true;
 
     init {
         textDocumentService = BDocumentService(this)
-        bWorkspaceService = BWorkspaceService()
+        bWorkspaceService = BWorkspaceService(this)
     }
 
 
@@ -39,17 +43,18 @@ class Server : LanguageServer {
      */
     override fun initialize(params: InitializeParams?): CompletableFuture<InitializeResult> {
         val res = InitializeResult(ServerCapabilities())
-        res.capabilities.setCodeActionProvider(true)
-        res.capabilities.completionProvider = CompletionOptions()
-        res.capabilities.definitionProvider = false
+        res.capabilities.setCodeActionProvider(false)
+//        res.capabilities.definitionProvider = false
         res.capabilities.hoverProvider = false
         res.capabilities.referencesProvider = false
         res.capabilities.setTextDocumentSync(TextDocumentSyncKind.Full)
         res.capabilities.documentSymbolProvider = false
-
         return CompletableFuture.supplyAsync { res }
     }
 
+    override fun initialized(params: InitializedParams?) {
+        //languageClient.registerCapability(DidChangeConfigurationCapabilities())
+    }
 
     /**
      * The shutdown request is sent from the client to the server. It asks the
@@ -72,7 +77,7 @@ class Server : LanguageServer {
      * A notification to ask the server to exit its process.
      */
     override fun exit() {
-        exitProcess(0);
+        exitProcess(0)
     }
 
 
@@ -87,4 +92,33 @@ class Server : LanguageServer {
     fun setRemoteProxy(remoteProxy: LanguageClient) {
         this.languageClient = remoteProxy
     }
+
+
+    /**
+     * Get the settings for the current document - will fallback to global settings eventually; If setting not cached
+     * method will try to get setting from the client
+     * @param uri the uri of the document requested
+     * @return settings of the document requested
+     */
+    fun getDocumentSettings(uri : String) : CompletableFuture<Settings> {
+        this.languageClient.showMessage(
+                MessageParams(MessageType.Log, "uri $uri " ))
+        if(!configurationAbility){
+            val returnValue = CompletableFuture<Settings>()
+            returnValue.complete(globalSettings)
+            return returnValue
+        }
+        // if client has configuration abilities
+        return if(documentSettings.containsKey(uri))
+        {
+            documentSettings[uri]!!
+        }else{
+            val configurationItem = ConfigurationItem()
+            configurationItem.scopeUri = uri
+            configurationItem.section = "languageServer"
+            val requestedConfig = languageClient.configuration(ConfigurationParams(listOf(configurationItem)))
+            documentSettings[uri] = CompletableFuture.allOf(requestedConfig).thenApply{ castJsonToSetting(requestedConfig.get().first() as JsonObject) }
+            documentSettings[uri]!!
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/b/language/server/Util.kt b/src/main/kotlin/b/language/server/Util.kt
new file mode 100644
index 0000000..9687ebd
--- /dev/null
+++ b/src/main/kotlin/b/language/server/Util.kt
@@ -0,0 +1,14 @@
+package b.language.server
+
+import b.language.server.dataStorage.Settings
+import com.google.gson.Gson
+import com.google.gson.JsonObject
+import java.io.File
+
+fun castJsonToSetting(json : JsonObject) : Settings {
+    return Settings(Gson().fromJson(json.get("maxNumberOfProblems"), Int::class.java),
+            Gson().fromJson(json.get("wdChecks"), Boolean::class.java),
+            Gson().fromJson(json.get("strictChecks"), Boolean::class.java),
+            Gson().fromJson(json.get("performanceHints"), Boolean::class.java),
+            File(Gson().fromJson(json.get("probHome"), String::class.java)))
+}
\ No newline at end of file
diff --git a/src/main/kotlin/b/language/server/dataStorage/Problem.kt b/src/main/kotlin/b/language/server/dataStorage/Problem.kt
new file mode 100644
index 0000000..631fd69
--- /dev/null
+++ b/src/main/kotlin/b/language/server/dataStorage/Problem.kt
@@ -0,0 +1,19 @@
+package b.language.server.dataStorage
+
+import org.eclipse.lsp4j.Position
+
+/**
+ * kotlin representation of prob ndjson problems
+ */
+/**
+ * @param type type of the problem (error/warning/information(
+ * @param message problem message
+ * @param reason reason for the problem to occur
+ * @param file the file where the problem occurred
+ * @param start start position of the problem
+ * @param end end position of the problem
+ * @version version of porbcli
+ */
+data class Problem(val type : String , val message: String, val reason : String, val file : String, val start : Position,
+                   val end : Position, val version : String)
+
diff --git a/src/main/kotlin/b/language/server/dataStorage/Settings.kt b/src/main/kotlin/b/language/server/dataStorage/Settings.kt
new file mode 100644
index 0000000..9a2ca6f
--- /dev/null
+++ b/src/main/kotlin/b/language/server/dataStorage/Settings.kt
@@ -0,0 +1,6 @@
+package b.language.server.dataStorage
+
+import java.io.File
+
+data class Settings(val maxNumberOfProblem : Int = 1000, val strictChecks : Boolean = true,  val wdChecks : Boolean = true,
+                    val performanceHints : Boolean = true , val probHome : File = File("~/prob_prolog/probcli.sh"))
-- 
GitLab