diff --git a/.gitignore b/.gitignore
index b63da45..cc5d98b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ build/
out/
!**/src/main/**/out/
!**/src/test/**/out/
+.intellijPlatform
### Eclipse ###
.apt_generated
@@ -39,4 +40,4 @@ bin/
.vscode/
### Mac OS ###
-.DS_Store
\ No newline at end of file
+.DS_Store
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index ae3f30a..d310da5 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
-
\ No newline at end of file
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index d450a79..bce7180 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,10 @@
+
+
+
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 7169a61..b1d4a0e 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -12,38 +12,72 @@
* Zowe Community
*/
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.jetbrains.intellij.platform.gradle.TestFrameworkType
fun properties(key: String) = providers.gradleProperty(key)
plugins {
- id("java")
- id("org.jetbrains.kotlin.jvm") version "1.9.21"
- id("org.jetbrains.intellij") version "1.16.1"
- id("org.jetbrains.kotlinx.kover") version "0.8.1"
+ java
+ kotlin("jvm") version "1.9.22"
+ id("org.jetbrains.intellij.platform") version "2.1.0"
+ id("org.jetbrains.kotlinx.kover") version "0.8.3"
}
group = properties("pluginGroup").get()
version = properties("pluginVersion").get()
+val gsonVersion = "2.11.0"
val kotestVersion = "5.9.1"
-val mockkVersion = "1.13.12"
-val junitVersion = "1.10.3"
+val mockkVersion = "1.13.13"
+val junitVersion = "1.11.3"
+val lsp4ijVersion = "0.7.0"
repositories {
mavenCentral()
+ intellijPlatform {
+ defaultRepositories()
+ jetbrainsRuntime()
+ }
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+kotlin {
+ jvmToolchain {
+ languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.toString()))
+ }
}
// Configure Gradle IntelliJ Plugin
-// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
-intellij {
- version.set(properties("platformVersion").get())
-// pluginsRepositories {
-// custom("https://plugins.jetbrains.com/plugins/nightly/23257")
-// }
- plugins.set(listOf("org.jetbrains.plugins.textmate", "com.redhat.devtools.lsp4ij:0.4.0"))
+// Read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin.html
+intellijPlatform {
+ pluginConfiguration {
+ version = properties("platformVersion").get()
+ ideaVersion {
+ sinceBuild = properties("pluginSinceBuild").get()
+ untilBuild = provider { null }
+ }
+ }
}
dependencies {
+ // ===== Runtime env setup ===
+ // IntelliJ
+ intellijPlatform {
+ intellijIdeaCommunity(properties("platformVersion").get(), useInstaller = false)
+ jetbrainsRuntime()
+ instrumentationTools()
+ bundledPlugin("org.jetbrains.plugins.textmate")
+//// pluginsRepositories {
+//// custom("https://plugins.jetbrains.com/plugins/nightly/23257")
+//// }
+ plugin("com.redhat.devtools.lsp4ij:$lsp4ijVersion")
+ testFramework(TestFrameworkType.Plugin.Java)
+ }
+ // Gson
+ implementation("com.google.code.gson:gson:$gsonVersion")
// ===== Test env setup =====
// Kotest
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
@@ -57,21 +91,6 @@ dependencies {
}
tasks {
- // Set the JVM compatibility versions
- withType {
- sourceCompatibility = JavaVersion.VERSION_17.toString()
- targetCompatibility = JavaVersion.VERSION_17.toString()
- }
- withType {
- kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
- }
-
- patchPluginXml {
- version.set(properties("pluginVersion").get())
- sinceBuild.set(properties("pluginSinceBuild").get())
- untilBuild.set(properties("pluginUntilBuild").get())
- }
-
test {
useJUnitPlatform()
testLogging {
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 249e583..d64cd49 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a595206..79eb9d0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 1b6c787..1aa94a4 100755
--- a/gradlew
+++ b/gradlew
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,11 @@ do
esac
done
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-APP_NAME="Gradle"
+# This is normally unused
+# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +131,29 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
diff --git a/gradlew.bat b/gradlew.bat
index 107acd3..93e3f59 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/src/main/kotlin/org/zowe/cobol/Sections.kt b/src/main/kotlin/org/zowe/cobol/Sections.kt
new file mode 100644
index 0000000..6de39a0
--- /dev/null
+++ b/src/main/kotlin/org/zowe/cobol/Sections.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2024 IBA Group.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBA Group
+ * Zowe Community
+ */
+
+package org.zowe.cobol
+
+/**
+ * Various config section options that could be found across configuration files
+ * @property section the section string representation
+ */
+enum class Sections(val section: String) {
+ DIALECTS_SECTION("cobol-lsp.dialects"),
+ DIALECT_REGISTRY("cobol-lsp.dialect.registry"),
+ DIALECT_LIBS("cobol-lsp.dialect.libs"),
+ CPY_SECTION("cobol-lsp.cpy-manager"),
+ CPY_LOCAL_PATH("cobol-lsp.cpy-manager.paths-local"),
+ CPY_EXTENSIONS("cobol-lsp.cpy-manager.copybook-extensions"),
+ CPY_FILE_ENCODING("cobol-lsp.cpy-manager.copybook-file-encoding"),
+ SQL_BACKEND("cobol-lsp.target-sql-backend"),
+ COMPILER_OPTIONS("cobol-lsp.compiler.options"),
+ LOGGIN_LEVEL_ROOT("cobol-lsp.logging.level.root"),
+ LOCALE("cobol-lsp.locale"),
+ COBOL_PROGRAM_LAYOUT("cobol-lsp.cobol.program.layout"),
+ SUBROUTINE_LOCAL_PATH("cobol-lsp.subroutine-manager.paths-local"),
+ CICS_TRANSLATOR("cobol-lsp.cics.translator"),
+ UNRECOGNIZED("unrecognized-section");
+
+ companion object {
+ operator fun invoke(section: String): Sections {
+ return entries.find { it.section == section } ?: UNRECOGNIZED
+ }
+ }
+
+ override fun toString(): String {
+ return section
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/zowe/cobol/VSCodeSettingsAdapterService.kt b/src/main/kotlin/org/zowe/cobol/VSCodeSettingsAdapterService.kt
new file mode 100644
index 0000000..00ca16a
--- /dev/null
+++ b/src/main/kotlin/org/zowe/cobol/VSCodeSettingsAdapterService.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024 IBA Group.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBA Group
+ * Zowe Community
+ */
+
+package org.zowe.cobol
+
+import com.google.gson.JsonParser
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.FileReader
+import java.nio.file.Path
+
+/** VS Code settings adapter service. Needed to recognize configs in .vscode folder */
+@Service
+class VSCodeSettingsAdapterService {
+
+ companion object {
+ fun getService(): VSCodeSettingsAdapterService = service()
+ }
+
+ /**
+ * Read the .vscode/settings.json file section
+ * @param workspaceFolder the workspace folder path to search for the file in
+ * @param readBlock the function to handle the read section
+ * @return the result of the read section handling or error if file is not found
+ */
+ private fun readConfigFileSection(workspaceFolder: Path, readBlock: (FileReader) -> T?): T? {
+ return try {
+ val settingsPath = workspaceFolder.resolve(".vscode").resolve("settings.json")
+ val settingsFile = File(settingsPath.toUri())
+ FileReader(settingsFile).use(readBlock)
+ } catch (e: FileNotFoundException) {
+ // TODO: logger
+// println("settings.json file not found")
+ null
+ }
+ }
+
+ /**
+ * Read the .vscode/settings.json file section and expect to return a list of strings as a result
+ * @param workspaceFolder the workspace folder path to search for the file in
+ * @param section the section to read
+ * @return the list of strings, read from the section, or empty list on failure or if there are no items
+ */
+ fun getListOfStringsConfiguration(workspaceFolder: Path, section: Sections): List {
+ return readConfigFileSection(workspaceFolder) { settingsJsonReader ->
+ val settingsJsonElement = JsonParser.parseReader(settingsJsonReader)
+ if (settingsJsonElement.isJsonObject) {
+ val settingsJsonObject = settingsJsonElement.asJsonObject
+ val settingsDialectJsonArray = settingsJsonObject.get(section.toString())?.asJsonArray
+ settingsDialectJsonArray?.map { it.asString } ?: listOf()
+ } else {
+ listOf()
+ }
+ } ?: listOf()
+ }
+
+}
diff --git a/src/main/kotlin/org/zowe/cobol/lsp/CobolConfigsRecognitionService.kt b/src/main/kotlin/org/zowe/cobol/lsp/CobolConfigsRecognitionService.kt
new file mode 100644
index 0000000..870b06c
--- /dev/null
+++ b/src/main/kotlin/org/zowe/cobol/lsp/CobolConfigsRecognitionService.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2024 IBA Group.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBA Group
+ * Zowe Community
+ */
+
+package org.zowe.cobol.lsp
+
+import com.google.gson.*
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import org.eclipse.lsp4j.ConfigurationItem
+import org.zowe.cobol.findFilesInWorkspace
+import java.io.File
+import java.lang.reflect.Type
+import java.net.URI
+import java.nio.file.*
+
+private const val PROCESSOR_GROUP_FOLDER = ".cobolplugin"
+private const val PROCESSOR_GROUP_PGM = "pgm_conf.json"
+private const val PROCESSOR_GROUP_PROC = "proc_grps.json"
+private val COBOL_EXTENSTIONS = listOf(".CBL", ".COB", ".COBOL")
+
+/** Service to handle COBOL-related configs */
+@Service
+class CobolConfigsRecognitionService {
+
+ companion object {
+ fun getService(): CobolConfigsRecognitionService = service()
+ }
+
+ /**
+ * Preprocessor description item class
+ * @property name the name of the preprocessor
+ * @property libs the optional preprocessor libs
+ */
+ private data class PreprocessorItem(val name: String, val libs: List = emptyList())
+
+ /**
+ * Processor group class to represent processor groups
+ * @property name the name of the processor group
+ * @property libs the optional libs to be used
+ * @property preprocessor the optional preprocessors list to be used with the processor group
+ */
+ private data class ProcessorGroup(
+ val name: String,
+ val libs: List = emptyList(),
+ val preprocessor: List = emptyList()
+ )
+
+ /**
+ * Processor groups container class
+ * @property pgroups the processor groups list
+ */
+ private data class PGroupContainer(val pgroups: List)
+
+ /** Class to provide a deserialization mechanism for processor groups */
+ private class ProcessorGroupDeserializer : JsonDeserializer {
+ override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ProcessorGroup {
+ val jsonObject = json.asJsonObject
+
+ val name = jsonObject.get("name").asString
+ val libs = jsonObject.getAsJsonArray("libs")?.map { it.asString } ?: emptyList()
+
+ val preprocessor = when {
+ jsonObject.has("preprocessor") -> {
+ val preprocessorElement = jsonObject.get("preprocessor")
+ if (preprocessorElement.isJsonArray) {
+ preprocessorElement.asJsonArray.map { parsePreprocessorItem(it) }
+ } else {
+ listOf(parsePreprocessorItem(preprocessorElement))
+ }
+ }
+ else -> emptyList()
+ }
+
+ return ProcessorGroup(name, libs, preprocessor)
+ }
+
+ /**
+ * Parse preprocessor item from JSON. It is either a string or an object
+ * @param element the [JsonElement] to parse preprocessor item from
+ * @return parsed and formed [PreprocessorItem] instance
+ */
+ private fun parsePreprocessorItem(element: JsonElement): PreprocessorItem {
+ return if (element.isJsonPrimitive && element.asJsonPrimitive.isString) {
+ PreprocessorItem(name = element.asString)
+ } else {
+ val jsonObject = element.asJsonObject
+ val name = jsonObject.get("name").asString
+ val libs = jsonObject.getAsJsonArray("libs")?.map { it.asString } ?: emptyList()
+ PreprocessorItem(name = name, libs = libs)
+ }
+ }
+ }
+
+ /**
+ * Program config item class to represent a single COBOL program configuration
+ * @param program a name or a wildcard of a file to be considered as the main program (open code)
+ * @param pgroup name of a processor group as defined in proc_grps.json
+ */
+ private data class ProgramItem(val program: String, val pgroup: String)
+
+ /**
+ * Programs config class to represent program configuration options for the COBOL programs
+ * @param pgms a list of main programs
+ */
+ private data class ProgramContainer(val pgms: List)
+
+ // TODO: doc
+ // TODO: recheck functionality correctness
+ private fun matchProcessorGroup(pgmCfg: ProgramContainer, documentPathStr: String, workspaceFolder: Path): String? {
+ val documentPath = Paths.get(documentPathStr)
+ val relativeDocPath = workspaceFolder.relativize(documentPath)
+
+ val candidates = mutableListOf()
+
+ for ((program, pgroup) in pgmCfg.pgms) {
+ val programPath = try {
+ Paths.get(program)
+ } catch (e: InvalidPathException) {
+ null
+ }
+ // exact match
+ if (programPath != null && programPath.isAbsolute) {
+ if (
+ programPath == documentPath
+ || programPath.toString().uppercase() == documentPath.toString().uppercase()
+ ) {
+ return pgroup
+ }
+ } else {
+ if (relativeDocPath == programPath) {
+ candidates.add(pgroup)
+ }
+ }
+
+ val relativeDocPathIgnoreCase = Paths.get(relativeDocPath.toString().uppercase())
+ val matcher = FileSystems.getDefault().getPathMatcher("glob:${program.uppercase()}")
+ if (matcher.matches(relativeDocPathIgnoreCase)) {
+ candidates.add(pgroup)
+ }
+ }
+
+ return candidates.getOrNull(0)
+ }
+
+ // TODO: doc
+ private fun loadProcessorsConfig(workspaceFolder: Path, documentUri: URI): ProcessorGroup? {
+ val documentPath = Paths.get(documentUri).toString()
+ val cfgPath = workspaceFolder.resolve(PROCESSOR_GROUP_FOLDER)
+ val procCfgPath = cfgPath.resolve(PROCESSOR_GROUP_PROC)
+ val pgmCfgPath = cfgPath.resolve(PROCESSOR_GROUP_PGM)
+ if (!Files.exists(pgmCfgPath) || !Files.exists(procCfgPath)) {
+ return null
+ }
+ val procCfgFile = File(procCfgPath.toUri()).readText()
+ val procCfgGson = GsonBuilder()
+ .registerTypeAdapter(ProcessorGroup::class.java, ProcessorGroupDeserializer())
+ .create()
+ val procCfg = procCfgGson.fromJson(procCfgFile, PGroupContainer::class.java)
+
+ val pgmCfgFile = File(pgmCfgPath.toUri()).readText()
+ val pgmCfgGson = Gson()
+ val pgmCfg = pgmCfgGson.fromJson(pgmCfgFile, ProgramContainer::class.java)
+
+ val pgroup = matchProcessorGroup(pgmCfg, documentPath, workspaceFolder)
+
+ val result = procCfg.pgroups.find { p -> pgroup == p.name }
+ return result
+ }
+
+ // TODO: doc
+ fun loadProcessorGroupDialectConfig(
+ workspaceFolder: Path,
+ item: ConfigurationItem,
+ configObject: List
+ ): List {
+ return try {
+ // "SQL" is not a real dialect, we will use it only to set up sql backend for now
+ loadProcessorsConfig(workspaceFolder, URI(item.scopeUri))
+ ?.preprocessor
+ ?.map { (name, _) -> name }
+ ?.filter { name -> name != "SQL" }
+ ?.ifEmpty { configObject }
+ ?: configObject
+ } catch (e: Exception) {
+ println(e)
+ configObject
+ }
+ }
+
+ // TODO: doc
+ fun loadProcessorGroupCopybookPathsConfig(
+ workspaceFolder: Path,
+ item: ConfigurationItem,
+ configObject: List
+ ): List {
+ val copybookPaths = loadProcessorsConfig(workspaceFolder, URI(item.scopeUri))
+ ?.libs
+ ?.ifEmpty { configObject }
+ ?: configObject
+ return findFilesInWorkspace(copybookPaths, workspaceFolder)
+ }
+
+ // TODO: doc
+ // TODO: implement
+// fun resolveSubroutineURI(project: Project, name: String): String {
+// val folders = VSCodeSettingsAdapterService
+// .getService(project)
+// .getListOfStringsConfiguration(Sections.SUBROUTINE_LOCAL_PATH)
+// return searchCopybookInWorkspace(project, name, folders, COBOL_EXTENSTIONS)
+// }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/zowe/cobol/lsp/CobolCopybooksService.kt b/src/main/kotlin/org/zowe/cobol/lsp/CobolCopybooksService.kt
new file mode 100644
index 0000000..de3040e
--- /dev/null
+++ b/src/main/kotlin/org/zowe/cobol/lsp/CobolCopybooksService.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2024 IBA Group.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBA Group
+ * Zowe Community
+ */
+
+package org.zowe.cobol.lsp
+
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import org.zowe.cobol.searchForFileInFolder
+import java.nio.file.Path
+import java.nio.file.Paths
+
+/** Service to handle the functionalities around copybooks */
+@Service
+class CobolCopybooksService {
+
+ companion object {
+ fun getService(): CobolCopybooksService = service()
+ }
+
+ // TODO: doc
+ // TODO: implement
+ // TODO: resolve the actual functionality
+// private fun getTargetFolderForCopybook(
+// folderKind: string | CopybookFolderKind,
+// documentUri: string,
+// dialectType: string,
+// ) {
+// let result: string[] = [];
+// const profile = SettingsService.getProfileName()!;
+// switch (folderKind) {
+// case CopybookFolderKind[CopybookFolderKind.local]:
+// result = SettingsService.getCopybookLocalPath(documentUri, dialectType);
+// break;
+// case CopybookFolderKind[CopybookFolderKind["downloaded-dsn"]]:
+// result = SettingsService.getDsnPath(documentUri, dialectType).map(
+// (dnsPath) => CopybookURI.createDatasetPath(profile, dnsPath),
+// );
+// break;
+// case CopybookFolderKind[CopybookFolderKind["downloaded-uss"]]:
+// result = SettingsService.getUssPath(documentUri, dialectType).map(
+// (dnsPath) => CopybookURI.createDatasetPath(profile, dnsPath),
+// );
+// break;
+// }
+// return result;
+// }
+
+ // TODO: doc
+ // TODO: implement
+ // TODO: resolve the actual functionality
+// private fun searchCopybook(
+// documentUri: String,
+// copybookName: String,
+// dialectType: String
+// ) {
+// let result: string | undefined;
+// for (let i = 0; i < Object.values(CopybookFolderKind).length; i++) {
+// const folderKind = Object.values(CopybookFolderKind)[i];
+// const targetFolder = getTargetFolderForCopybook(
+// folderKind,
+// documentUri,
+// dialectType,
+// );
+// const allowedExtensions = resolveAllowedExtensions(folderKind, documentUri);
+// result = searchCopybookInWorkspace(
+// copybookName,
+// targetFolder,
+// allowedExtensions,
+// );
+// if (result) {
+// return result;
+// }
+// }
+// return result;
+// }
+
+ /**
+ * Search for the specified copybook in the workspace folder
+ * @param workspaceFolder the workspace folder path to search for the copybook in
+ * @param copybookName the name of the copybook to search for
+ * @param copybookFolders the copybook folders in the workspace to search for the copybook in
+ * @param extensions the potential copybook extensions to search for the exact copybook by
+ * @return the found copybook path string or null
+ */
+ private fun searchCopybookInWorkspace(
+ workspaceFolder: Path,
+ copybookName: String,
+ copybookFolders: List,
+ extensions: List
+ ): String? {
+ if (copybookName.isEmpty() || extensions.isEmpty()) return null
+ for (copybookFolder in copybookFolders) {
+ for (ext in extensions) {
+ val foundCopybookPath = searchForFileInFolder(workspaceFolder, copybookFolder, copybookName, ext)
+ if (foundCopybookPath != null) return Paths.get(foundCopybookPath).toUri().toString()
+ }
+ }
+ return null
+ }
+
+ // TODO: finalize the functionality when interaction with Zowe Explorer is set up
+ /**
+ * Resolve a path of the provided copybook
+ * @param workspaceFolder the workspace folder path
+ * @param copybookFolders the folders containing the copybooks
+ * @param copybookName the name of the copybook to search for
+ * @param copybookExtensions the potential copybook extension to search for the exact copybook
+ * @return the copybook path in a URI string style
+ */
+ fun resolveCopybookPath(
+ workspaceFolder: Path,
+ copybookFolders: List,
+ copybookName: String,
+ copybookExtensions: List
+ ): String? {
+// val copybook = searchCopybook(documentUri, copybookName, dialectType);
+ val result = searchCopybookInWorkspace(workspaceFolder, copybookName, copybookFolders, copybookExtensions)
+ return result
+ }
+
+}
diff --git a/src/main/kotlin/org/zowe/cobol/lsp/CobolLanguageClient.kt b/src/main/kotlin/org/zowe/cobol/lsp/CobolLanguageClient.kt
index f509b14..44ff317 100644
--- a/src/main/kotlin/org/zowe/cobol/lsp/CobolLanguageClient.kt
+++ b/src/main/kotlin/org/zowe/cobol/lsp/CobolLanguageClient.kt
@@ -16,24 +16,13 @@ package org.zowe.cobol.lsp
import com.intellij.openapi.project.Project
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl
-import org.eclipse.lsp4j.ConfigurationParams
-import org.eclipse.lsp4j.MessageParams
-import org.eclipse.lsp4j.MessageType
+import org.eclipse.lsp4j.*
+import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
+import org.zowe.cobol.Sections
+import org.zowe.cobol.VSCodeSettingsAdapterService
+import java.net.URI
import java.util.concurrent.CompletableFuture
-
-const val DIALECT_REGISTRY_SECTION = "cobol-lsp.dialect.registry"
-const val SETTINGS_DIALECT = "cobol-lsp.dialects"
-const val SETTINGS_CPY_LOCAL_PATH = "cobol-lsp.cpy-manager.paths-local"
-const val DIALECT_LIBS = "cobol-lsp.dialect.libs"
-const val SETTINGS_CPY_EXTENSIONS = "cobol-lsp.cpy-manager.copybook-extensions"
-const val SETTINGS_SQL_BACKEND = "cobol-lsp.target-sql-backend"
-const val SETTINGS_CPY_FILE_ENCODING = "cobol-lsp.cpy-manager.copybook-file-encoding"
-const val SETTINGS_COMPILE_OPTIONS = "cobol-lsp.compiler.options"
-const val SETTINGS_CLIENT_LOGGING_LEVEL = "cobol-lsp.logging.level.root"
-const val SETTINGS_LOCALE = "cobol-lsp.locale"
-const val SETTINGS_COBOL_PROGRAM_LAYOUT = "cobol-lsp.cobol.program.layout"
-const val SETTINGS_SUBROUTINE_LOCAL_PATH = "cobol-lsp.subroutine-manager.paths-local"
-const val SETTINGS_CICS_TRANSLATOR = "cobol-lsp.cics.translator"
+import kotlin.io.path.toPath
/** COBOL LSP client wrapper. Provides a comprehensive support for the COBOL LSP communications */
class CobolLanguageClient(project: Project) : LanguageClientImpl(project) {
@@ -48,84 +37,109 @@ class CobolLanguageClient(project: Project) : LanguageClientImpl(project) {
override fun configuration(configurationParams: ConfigurationParams?): CompletableFuture> {
val result = mutableListOf()
for (item in configurationParams?.items ?: emptyList()) {
+ val section = Sections(item.section)
try {
- if (item.section == DIALECT_REGISTRY_SECTION) {
+ if (section == Sections.DIALECT_REGISTRY) {
logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 1"))
result.add(emptyList())
-// val computed = DialectRegistry.getDialects()
-// result.add(computed)
- } else if (item.scopeUri != "") {
+// val dialectInfos = DialectsService.getService().getDialects()
+// logMessage(MessageParams(MessageType.Info, "Registered dialects: $dialectInfos"))
+// result.add(dialectInfos)
+ } else if (item.scopeUri != null && item.scopeUri != "") {
+ val workspaceFolders = this.workspaceFolders().get().map { pathObj -> URI.create(pathObj.uri).toPath() }
// val cfg = vscode.workspace.getConfiguration().get(item.section)
- when (item.section) {
- SETTINGS_DIALECT -> {
+ when (section) {
+ Sections.DIALECTS_SECTION -> {
logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 2"))
result.add(emptyList())
- // val computed = loadProcessorGroupDialectConfig(item, cfg)
- // result.add(computed)
+// val settingsCfg = VSCodeSettingsAdapterService.getService()
+// .getListOfStringsConfiguration(workspaceFolders[0], Sections.DIALECTS_SECTION)
+// val dialects = CobolConfigsRecognitionService.getService()
+// .loadProcessorGroupDialectConfig(workspaceFolders[0], item, settingsCfg)
+// logMessage(MessageParams(MessageType.Info, "For ${item.scopeUri} using dialects: $dialects"))
+// result.add()
+// result.add(dialects)
}
- SETTINGS_CPY_LOCAL_PATH -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not recognized yet 3"))
- // val computed = loadProcessorGroupCopybookPathsConfig(item, cfg as List)
- // result.add(computed)
- // } else if (item.section === DIALECT_LIBS && !!item.dialect) {
+
+ Sections.CPY_LOCAL_PATH -> {
+ val settingsCfg = VSCodeSettingsAdapterService.getService()
+ .getListOfStringsConfiguration(workspaceFolders[0], Sections.CPY_LOCAL_PATH)
+ val cpyLocalPaths = CobolConfigsRecognitionService.getService()
+ .loadProcessorGroupCopybookPathsConfig(workspaceFolders[0], item, settingsCfg)
+ logMessage(
+ MessageParams(MessageType.Info, "For ${item.scopeUri} using cpy local paths: $cpyLocalPaths")
+ )
+ result.add(cpyLocalPaths)
}
- DIALECT_LIBS -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not recognized yet 4"))
- // val dialectLibs = SettingsService.getCopybookLocalPath(item.scopeUri, item.dialect)
- // result.add(dialectLibs)
+
+ Sections.DIALECT_LIBS -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not recognized yet 3"))
+ // val dialectLibs = SettingsService.getCopybookLocalPath(item.scopeUri, item.dialect)
+ // result.add(dialectLibs)
}
- SETTINGS_CPY_EXTENSIONS -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized 5"))
- result.add(listOf(".CPY", ".COPY", ".cpy", ".copy",""))
- // val computed = loadProcessorGroupCopybookExtensionsConfig(item, cfg as List)
- // result.add(computed)
+
+ Sections.CPY_EXTENSIONS -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not correctly recognized yet 4"))
+ result.add(listOf(".CPY", ".COPY", ".cpy", ".copy", ""))
+ // val computed = loadProcessorGroupCopybookExtensionsConfig(item, cfg as List)
+ // result.add(computed)
}
- SETTINGS_SQL_BACKEND -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 6"))
+
+ Sections.SQL_BACKEND -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not correctly recognized yet 5"))
result.add("DB2_SERVER")
- // val computed = loadProcessorGroupSqlBackendConfig(item, cfg as String)
- // result.add(computed)
+ // val computed = loadProcessorGroupSqlBackendConfig(item, cfg as String)
+ // result.add(computed)
}
- SETTINGS_CPY_FILE_ENCODING -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not recognized yet 7"))
- // val computed = loadProcessorGroupCopybookEncodingConfig(item, cfg as String)
- // result.add(computed)
+
+ Sections.CPY_FILE_ENCODING -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not recognized yet 6"))
+ // val computed = loadProcessorGroupCopybookEncodingConfig(item, cfg as String)
+ // result.add(computed)
}
- SETTINGS_COMPILE_OPTIONS -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 8"))
+
+ Sections.COMPILER_OPTIONS -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not correctly recognized yet 7"))
result.add(null)
- // val computed = loadProcessorGroupCompileOptionsConfig(item, cfg as String)
- // result.add(computed)
+ // val computed = loadProcessorGroupCompileOptionsConfig(item, cfg as String)
+ // result.add(computed)
}
- SETTINGS_CLIENT_LOGGING_LEVEL -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized 11"))
+
+ Sections.LOGGIN_LEVEL_ROOT -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not correctly recognized yet 8"))
result.add("ERROR")
}
- SETTINGS_LOCALE -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized 12"))
+
+ Sections.LOCALE -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not correctly recognized yet 9"))
result.add("en")
}
- SETTINGS_COBOL_PROGRAM_LAYOUT -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 12"))
+
+ Sections.COBOL_PROGRAM_LAYOUT -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not correctly recognized yet 10"))
result.add(null)
}
- SETTINGS_SUBROUTINE_LOCAL_PATH -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 14"))
+
+ Sections.SUBROUTINE_LOCAL_PATH -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not correctly recognized yet 11"))
result.add(emptyList())
+ //
}
- SETTINGS_CICS_TRANSLATOR -> {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 15"))
+
+ Sections.CICS_TRANSLATOR -> {
+ logMessage(MessageParams(MessageType.Info, "$section is not correctly recognized yet 12"))
result.add("true")
}
+
else -> {
- // result.add(cfg)
- logMessage(MessageParams(MessageType.Info, "${item.section} is not recognized yet 9"))
+ // result.add(cfg)
+ logMessage(MessageParams(MessageType.Info, "${item.section} is not recognized yet 13"))
}
}
} else {
- logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 10"))
+ logMessage(MessageParams(MessageType.Info, "${item.section} is not correctly recognized yet 14"))
result.add(emptyList())
-// result.add(vscode.workspace.getConfiguration().get(item.section));
+// result.add(vscode.workspace.getConfiguration().get(item.section))
}
} catch (error: Throwable) {
logMessage(MessageParams(MessageType.Error, "${error.message}\n${error.stackTrace}"))
@@ -134,4 +148,40 @@ class CobolLanguageClient(project: Project) : LanguageClientImpl(project) {
return CompletableFuture.completedFuture(result)
}
+ /**
+ * Handle the "copybook/resolve" request
+ * @param documentUri the document uri string, that triggered the request
+ * @param copybookName the copybook name to resolve
+ * @param dialectType the dialect type to resolve the copybook with
+ * @return the URI path string to the resolved copybook
+ */
+ @JsonRequest("copybook/resolve")
+ fun resolveCopybook(documentUri: String, copybookName: String, dialectType: String): CompletableFuture {
+ val cpyExtenstionsConfigItem = ConfigurationItem()
+ cpyExtenstionsConfigItem.section = Sections.CPY_EXTENSIONS.toString()
+ cpyExtenstionsConfigItem.scopeUri = documentUri
+
+ val cpyLocalPathsConfigItem = ConfigurationItem()
+ cpyLocalPathsConfigItem.section = Sections.CPY_LOCAL_PATH.toString()
+ cpyLocalPathsConfigItem.scopeUri = documentUri
+
+ val configParams = ConfigurationParams(listOf(cpyExtenstionsConfigItem, cpyLocalPathsConfigItem))
+
+ return this.workspaceFolders()
+ .thenCombine?, String?>(this.configuration(configParams)) { workspaceFolders, cpyConfigsAny ->
+ val workspaceFolder = URI.create(workspaceFolders[0].uri).toPath()
+
+ val cpyConfigs = cpyConfigsAny as List>
+ val cpyExtensions = cpyConfigs[0]
+ val cpyLocalPaths = cpyConfigs[1]
+
+ CobolCopybooksService.getService()
+ .resolveCopybookPath(workspaceFolder, cpyLocalPaths, copybookName, cpyExtensions)
+ }
+ }
+
+// TODO: implement custom requests
+// @JsonRequest("cobol/resolveSubroutine")
+// @JsonRequest("copybook/download")
+
}
diff --git a/src/main/kotlin/org/zowe/cobol/lsp/CobolLanguageServerFactory.kt b/src/main/kotlin/org/zowe/cobol/lsp/CobolLanguageServerFactory.kt
index 07f3a41..276c9be 100644
--- a/src/main/kotlin/org/zowe/cobol/lsp/CobolLanguageServerFactory.kt
+++ b/src/main/kotlin/org/zowe/cobol/lsp/CobolLanguageServerFactory.kt
@@ -14,7 +14,6 @@
package org.zowe.cobol.lsp
-import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.redhat.devtools.lsp4ij.LanguageServerFactory
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl
diff --git a/src/main/kotlin/org/zowe/cobol/lsp/DialectInfo.kt b/src/main/kotlin/org/zowe/cobol/lsp/DialectInfo.kt
new file mode 100644
index 0000000..b35fbec
--- /dev/null
+++ b/src/main/kotlin/org/zowe/cobol/lsp/DialectInfo.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2024 IBA Group.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBA Group
+ * Zowe Community
+ */
+
+package org.zowe.cobol.lsp
+
+import java.net.URI
+
+// TODO: doc
+// TODO: clarify the usage
+data class DialectInfo(
+ val name: String,
+ val uri: URI,
+ val description: String,
+ val extensionId: String,
+ val snippetPath: String
+)
diff --git a/src/main/kotlin/org/zowe/cobol/lsp/DialectsService.kt b/src/main/kotlin/org/zowe/cobol/lsp/DialectsService.kt
new file mode 100644
index 0000000..037721c
--- /dev/null
+++ b/src/main/kotlin/org/zowe/cobol/lsp/DialectsService.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 IBA Group.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBA Group
+ * Zowe Community
+ */
+
+package org.zowe.cobol.lsp
+
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import java.net.URI
+
+/** Service class that provides read/write dialect settings functionality */
+@Service
+class DialectsService {
+
+ private val dialects: MutableMap = mutableMapOf()
+
+ companion object {
+ fun getService(): DialectsService = service()
+ }
+
+ /**
+ * Gets registered [DialectInfo]s
+ * @return the list of [DialectInfo]s
+ */
+ fun getDialects(): List {
+ return dialects.values.toList()
+ }
+
+ /** Clears the registry */
+ fun clear() {
+ dialects.clear()
+ }
+
+ /**
+ * Registers dialect in the system
+ * @param extensionId the extension id
+ * @param name the name of the dialect
+ * @param uri the path to jar file
+ * @param description the description of the dialect
+ * @param snippetPath the snippet map path for the dialect
+ */
+ fun register(extensionId: String, name: String, uri: URI, description: String, snippetPath: String) {
+ val dialectInfo = DialectInfo(name, uri, description, extensionId, snippetPath)
+ dialects[dialectInfo.name] = dialectInfo
+ }
+
+ /**
+ * Unregisters dialect from the system
+ * @param name the name of the dialect
+ */
+ fun unregister(name: String) {
+ dialects.remove(name)
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/zowe/cobol/utils.kt b/src/main/kotlin/org/zowe/cobol/utils.kt
new file mode 100644
index 0000000..bc1b476
--- /dev/null
+++ b/src/main/kotlin/org/zowe/cobol/utils.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024 IBA Group.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBA Group
+ * Zowe Community
+ */
+
+package org.zowe.cobol
+
+import java.nio.file.FileSystems
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+
+/**
+ * Find all file paths that are provided in the config in the specified folder
+ * @param patterns the file patterns to search for (should be as a Unix-style paths or path parts)
+ * @param workspaceFolder the workspace folder path
+ * @return list of file paths as strings
+ */
+fun findFilesInWorkspace(patterns: List, workspaceFolder: Path): List {
+ val matchedFiles = mutableListOf()
+
+ return if (Files.isDirectory(workspaceFolder)) {
+ Files.walk(workspaceFolder).use { paths ->
+ paths.forEach { path ->
+ patterns.forEach { pattern ->
+ val matcher = FileSystems.getDefault().getPathMatcher("glob:**$pattern")
+ if (matcher.matches(path)) {
+ matchedFiles.add(path.toString())
+ }
+ }
+ }
+ }
+ matchedFiles
+ } else emptyList()
+}
+
+/**
+ * Cleans the [sourceStr] from the "magic" characters (such as *?[])
+ * @param sourceStr the string to clean
+ * @return a cleaned string
+ */
+fun getStringWithoutMagic(sourceStr: String): String {
+ val magicRegex = Regex("[*?\\[\\]]")
+ val magicFound = magicRegex.find(sourceStr)
+ val firstMagicCharIdx = magicFound?.range?.first ?: sourceStr.length
+ return sourceStr.substring(0, firstMagicCharIdx)
+}
+
+/**
+ * Search for a specific file by the provided parameters in the [parentFolder] path
+ * @param parentFolder the parent folder path to search for the file in
+ * @param resourceFolder the folder in the parent folder path to search for the file in
+ * @param fileName the file name to search file by
+ * @param ext the file extension to search the exact file by
+ */
+fun searchForFileInFolder(
+ parentFolder: Path,
+ resourceFolder: String,
+ fileName: String,
+ ext: String
+): String? {
+ val resourcePathStr = getStringWithoutMagic(resourceFolder)
+ val resourcePath = Paths.get(resourcePathStr)
+ val absResourcePath = if (resourcePath.isAbsolute) resourcePath else parentFolder.resolve(resourceFolder)
+ val formedPatternPath = absResourcePath.resolve("$fileName$ext")
+ val foundFiles = findFilesInWorkspace(
+ listOf(formedPatternPath.toString().replace("\\", "/")),
+ absResourcePath
+ )
+ return if (foundFiles.isNotEmpty()) foundFiles[0] else null
+}
diff --git a/src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientTestSpec.kt b/src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientConfigurationTestSpec.kt
similarity index 72%
rename from src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientTestSpec.kt
rename to src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientConfigurationTestSpec.kt
index 2cfd147..db79bc7 100644
--- a/src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientTestSpec.kt
+++ b/src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientConfigurationTestSpec.kt
@@ -23,8 +23,14 @@ import io.mockk.*
import org.eclipse.lsp4j.ConfigurationItem
import org.eclipse.lsp4j.ConfigurationParams
import org.eclipse.lsp4j.MessageParams
+import org.eclipse.lsp4j.WorkspaceFolder
+import org.zowe.cobol.Sections
+import org.zowe.cobol.VSCodeSettingsAdapterService
+import java.net.URI
+import java.util.concurrent.CompletableFuture
+import kotlin.io.path.toPath
-class CobolLanguageClientTestSpec : FunSpec({
+class CobolLanguageClientConfigurationTestSpec : FunSpec({
context("CobolLanguageClientTestSpec.configuration") {
afterTest {
@@ -37,14 +43,14 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
- every { section } returns DIALECT_REGISTRY_SECTION
+ every { section } returns Sections.DIALECT_REGISTRY.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(DIALECT_REGISTRY_SECTION)) {
+ if (firstArg().message.contains(Sections.DIALECT_REGISTRY.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -68,6 +74,9 @@ class CobolLanguageClientTestSpec : FunSpec({
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
if (firstArg().message.contains(someTestSection)) {
isLogMessageTriggeredCorrectly = true
@@ -86,14 +95,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_DIALECT
+ every { section } returns Sections.DIALECTS_SECTION.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_DIALECT)) {
+ if (firstArg().message.contains(Sections.DIALECTS_SECTION.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -107,17 +119,40 @@ class CobolLanguageClientTestSpec : FunSpec({
test("process 'workspace/configuration' for cpy-manager paths-local request with scope URI") {
var isLogMessageTriggeredCorrectly = false
+ val fakeUriPath = "file://c:/test"
+ val fakeFinalPath = "final_test_path"
+
+ val vscodeSettingsAdapterService = mockk {
+ every {
+ getListOfStringsConfiguration(URI.create(fakeUriPath).toPath(), Sections.CPY_LOCAL_PATH)
+ } returns listOf()
+ }
+ mockkObject(VSCodeSettingsAdapterService)
+ every { VSCodeSettingsAdapterService.getService() } returns vscodeSettingsAdapterService
+
+ val cobolConfigsRecognitionService = mockk {
+ every {
+ loadProcessorGroupCopybookPathsConfig(URI.create(fakeUriPath).toPath(), any(), listOf())
+ } returns listOf(fakeFinalPath)
+ }
+
+ mockkObject(CobolConfigsRecognitionService)
+ every { CobolConfigsRecognitionService.getService() } returns cobolConfigsRecognitionService
+
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_CPY_LOCAL_PATH
+ every { section } returns Sections.CPY_LOCAL_PATH.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder(fakeUriPath)))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_CPY_LOCAL_PATH)) {
+ if (firstArg().message.contains(fakeFinalPath)) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -125,7 +160,7 @@ class CobolLanguageClientTestSpec : FunSpec({
val result = cobolLanguageClient.configuration(configurationParamsMock).join()
assertSoftly { isLogMessageTriggeredCorrectly shouldBe true }
- assertSoftly { result shouldBeEqual listOf() }
+ assertSoftly { result shouldBeEqual listOf(listOf(fakeFinalPath)) }
}
test("process 'workspace/configuration' for dialect libs request with scope URI") {
@@ -134,14 +169,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns DIALECT_LIBS
+ every { section } returns Sections.DIALECT_LIBS.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(DIALECT_LIBS)) {
+ if (firstArg().message.contains(Sections.DIALECT_LIBS.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -158,14 +196,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_CPY_EXTENSIONS
+ every { section } returns Sections.CPY_EXTENSIONS.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_CPY_EXTENSIONS)) {
+ if (firstArg().message.contains(Sections.CPY_EXTENSIONS.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -182,14 +223,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_SQL_BACKEND
+ every { section } returns Sections.SQL_BACKEND.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_SQL_BACKEND)) {
+ if (firstArg().message.contains(Sections.SQL_BACKEND.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -206,14 +250,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_CPY_FILE_ENCODING
+ every { section } returns Sections.CPY_FILE_ENCODING.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_CPY_FILE_ENCODING)) {
+ if (firstArg().message.contains(Sections.CPY_FILE_ENCODING.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -230,14 +277,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_COMPILE_OPTIONS
+ every { section } returns Sections.COMPILER_OPTIONS.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_COMPILE_OPTIONS)) {
+ if (firstArg().message.contains(Sections.COMPILER_OPTIONS.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -254,14 +304,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_CLIENT_LOGGING_LEVEL
+ every { section } returns Sections.LOGGIN_LEVEL_ROOT.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_CLIENT_LOGGING_LEVEL)) {
+ if (firstArg().message.contains(Sections.LOGGIN_LEVEL_ROOT.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -278,14 +331,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_LOCALE
+ every { section } returns Sections.LOCALE.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_LOCALE)) {
+ if (firstArg().message.contains(Sections.LOCALE.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -302,14 +358,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_COBOL_PROGRAM_LAYOUT
+ every { section } returns Sections.COBOL_PROGRAM_LAYOUT.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_COBOL_PROGRAM_LAYOUT)) {
+ if (firstArg().message.contains(Sections.COBOL_PROGRAM_LAYOUT.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -326,14 +385,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_SUBROUTINE_LOCAL_PATH
+ every { section } returns Sections.SUBROUTINE_LOCAL_PATH.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_SUBROUTINE_LOCAL_PATH)) {
+ if (firstArg().message.contains(Sections.SUBROUTINE_LOCAL_PATH.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -350,14 +412,17 @@ class CobolLanguageClientTestSpec : FunSpec({
val projectMock = mockk()
val configurationItemMock = mockk {
every { scopeUri } returns "test"
- every { section } returns SETTINGS_CICS_TRANSLATOR
+ every { section } returns Sections.CICS_TRANSLATOR.toString()
}
val configurationParamsMock = mockk {
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
- if (firstArg().message.contains(SETTINGS_CICS_TRANSLATOR)) {
+ if (firstArg().message.contains(Sections.CICS_TRANSLATOR.toString())) {
isLogMessageTriggeredCorrectly = true
}
}
@@ -380,6 +445,9 @@ class CobolLanguageClientTestSpec : FunSpec({
every { items } returns listOf(configurationItemMock)
}
val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder("file://c:/test")))
every { cobolLanguageClient.logMessage(any()) } answers {
if (firstArg().message.contains(someTestSection)) {
isLogMessageTriggeredCorrectly = true
diff --git a/src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientResolveCopybookTestSpec.kt b/src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientResolveCopybookTestSpec.kt
new file mode 100644
index 0000000..20f01dc
--- /dev/null
+++ b/src/test/kotlin/org/zowe/cobol/lsp/CobolLanguageClientResolveCopybookTestSpec.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024 IBA Group.
+ *
+ * This program and the accompanying materials are made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBA Group
+ * Zowe Community
+ */
+
+package org.zowe.cobol.lsp
+
+import com.intellij.openapi.project.Project
+import io.kotest.assertions.assertSoftly
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.equals.shouldBeEqual
+import io.kotest.matchers.shouldBe
+import io.mockk.*
+import org.eclipse.lsp4j.ConfigurationItem
+import org.eclipse.lsp4j.ConfigurationParams
+import org.eclipse.lsp4j.MessageParams
+import org.eclipse.lsp4j.WorkspaceFolder
+import org.zowe.cobol.Sections
+import org.zowe.cobol.VSCodeSettingsAdapterService
+import java.net.URI
+import java.util.concurrent.CompletableFuture
+import kotlin.io.path.toPath
+
+class CobolLanguageClientResolveCopybookTestSpec : FunSpec({
+
+ context("CobolLanguageClientTestSpec.resolveCopybook") {
+ afterTest {
+ unmockkAll()
+ clearAllMocks()
+ }
+
+ test("process 'copybook/resolve' request") {
+ val fakeUriPath = "file://c:/test"
+ val testCopybookName = "TSTCPY"
+ val testCpyExtensions = listOf(".testext")
+ val testCpyLocalPaths = listOf("test_local_path")
+ val fakeFinalPath = "final_test_path"
+
+ val cobolCopybooksService = mockk {
+ every {
+ resolveCopybookPath(URI.create(fakeUriPath).toPath(), testCpyLocalPaths, testCopybookName, testCpyExtensions)
+ } returns fakeFinalPath
+ }
+ mockkObject(CobolCopybooksService)
+ every { CobolCopybooksService.getService() } returns cobolCopybooksService
+
+ val projectMock = mockk()
+ val cobolLanguageClient = spyk(CobolLanguageClient(projectMock))
+ every {
+ cobolLanguageClient.workspaceFolders()
+ } returns CompletableFuture.completedFuture(listOf(WorkspaceFolder(fakeUriPath)))
+ every {
+ cobolLanguageClient.configuration(any())
+ } returns CompletableFuture.completedFuture(mutableListOf(testCpyExtensions, testCpyLocalPaths))
+
+ val result = cobolLanguageClient.resolveCopybook("test", testCopybookName, "test").join()
+ assertSoftly { result shouldBe fakeFinalPath }
+ }
+ }
+
+})