diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt
index fd3e1f0..a7c89ce 100644
--- a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt
+++ b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt
@@ -61,7 +61,10 @@ class KotlinTextDocumentService(
         get() = uri.endsWith(".kts")

     private val TextDocumentIdentifier.content: String
-        get() = uriContentProvider.contentOf(parseURI(uri))
+        get() {
+            val uri = parseURI(uri)
+            return sf.contentOfTracked(uri) ?: uriContentProvider.contentOf(uri)
+        }

     fun connect(client: LanguageClient) {
         this.client = client
diff --git a/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt b/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt
index b1d67e9..9153d51 100644
--- a/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt
+++ b/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt
@@ -113,6 +113,10 @@ class SourceFiles(
         }
     }

+    fun contentOfTracked(uri: URI):String? {
+        return files[uri]?.content
+    }
+
     fun createdOnDisk(uri: URI) {
         changedOnDisk(uri)
     }

VSCode Format on Save deletes any file changes
Having fwcd-kotlin and editor.formatOnSave enabled in VSCode makes saving files impossible.
https://i.gyazo.com/9bdfba2d927450a0a8aac00c545f1384.mp4

fun example() = "Hello world!"

fun otherFun() = example()

fun yetAnotherFun() = example()

package org.javacs.kt

import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiIdentifier
import org.javacs.kt.position.changedRegion
import org.javacs.kt.position.position
import org.javacs.kt.util.findParent
import org.javacs.kt.util.nullResult
import org.javacs.kt.util.toPath
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.types.KotlinType
import java.nio.file.Paths

class CompiledFile(
    val content: String,
    val parse: KtFile,
    val compile: BindingContext,
    val container: ComponentProvider,
    val sourcePath: Collection<KtFile>,
    val classPath: CompilerClassPath,
    val isScript: Boolean = false,
    val kind: CompilationKind = CompilationKind.DEFAULT
) {
    /**
     * Find the type of the expression at `cursor`
     */
    fun typeAtPoint(cursor: Int): KotlinType? {
        var cursorExpr = parseAtPoint(cursor, asReference = true)?.findParent<KtExpression>() ?: return nullResult("Couldn't find expression at ${describePosition(cursor)}")
        val surroundingExpr = expandForType(cursor, cursorExpr)
        val scope = scopeAtPoint(cursor) ?: return nullResult("Couldn't find scope at ${describePosition(cursor)}")
        return typeOfExpression(surroundingExpr, scope)
    }

    fun typeOfExpression(expression: KtExpression, scopeWithImports: LexicalScope): KotlinType? =
            bindingContextOf(expression, scopeWithImports).getType(expression)

    fun bindingContextOf(expression: KtExpression, scopeWithImports: LexicalScope): BindingContext =
            classPath.compiler.compileKtExpression(expression, scopeWithImports, sourcePath, kind).first

    private fun expandForType(cursor: Int, surroundingExpr: KtExpression): KtExpression {
        val dotParent = surroundingExpr.parent as? KtDotQualifiedExpression
        if (dotParent != null && dotParent.selectorExpression?.textRange?.contains(cursor) ?: false) {
            return expandForType(cursor, dotParent)
        }
        else return surroundingExpr
    }

    fun referenceAtPoint(cursor: Int): Pair<KtExpression, DeclarationDescriptor>? {
        val element = parseAtPoint(cursor, asReference = true)
        var cursorExpr = element?.findParent<KtExpression>() ?: return nullResult("Couldn't find expression at ${describePosition(cursor)} (only found $element)")
        val surroundingExpr = expandForReference(cursor, cursorExpr)
        val scope = scopeAtPoint(cursor) ?: return nullResult("Couldn't find scope at ${describePosition(cursor)}")
        val context = bindingContextOf(surroundingExpr, scope)
        LOG.info("Hovering {}", surroundingExpr)
        return referenceFromContext(cursor, context)
    }

    private fun referenceFromContext(cursor: Int, context: BindingContext): Pair<KtExpression, DeclarationDescriptor>? {
        val targets = context.getSliceContents(BindingContext.REFERENCE_TARGET)
        return targets.asSequence()
                .filter { cursor in it.key.textRange }
                .sortedBy { it.key.textRange.length }
                .map { it.toPair() }
                .firstOrNull()
    }

    private fun expandForReference(cursor: Int, surroundingExpr: KtExpression): KtExpression {
        val parent: KtExpression? =
            (surroundingExpr.parent as? KtDotQualifiedExpression)?.let { it as KtExpression } // foo.bar
            ?: (surroundingExpr.parent as? KtSafeQualifiedExpression)?.let { it as KtExpression } // foo?.bar
            ?: (surroundingExpr.parent as? KtCallExpression)?.let { it as KtExpression } // foo()
        return parent?.let { expandForReference(cursor, it) } ?: surroundingExpr
    }

    /**
     * Parse the expression at `cursor`.
     *
     * If the `asReference` flag is set, the method will attempt to
     * convert a declaration (e.g. of a class or a function) to a referencing
     * expression before parsing it.
     */
    fun parseAtPoint(cursor: Int, asReference: Boolean = false): KtElement? {
        val oldCursor = oldOffset(cursor)
        val oldChanged = changedRegion(parse.text, content)?.first ?: TextRange(cursor, cursor)
        val psi = parse.findElementAt(oldCursor) ?: return nullResult("Couldn't find anything at ${describePosition(cursor)}")
        val oldParent = psi.parentsWithSelf
                .filterIsInstance<KtDeclaration>()
                .firstOrNull { it.textRange.contains(oldChanged) } ?: parse

        LOG.debug { "PSI path: ${psi.parentsWithSelf.toList()}" }

        val (surroundingContent, offset) = contentAndOffsetFromElement(psi, oldParent, asReference)
        val padOffset = " ".repeat(offset)
        val recompile = classPath.compiler.createKtFile(padOffset + surroundingContent, Paths.get("dummy.virtual" + if (isScript) ".kts" else ".kt"), kind)
        return recompile.findElementAt(cursor)?.findParent<KtElement>()
    }

    /**
     * Extracts the surrounding content and the text offset from a
     * PSI element.
     *
     * See `parseAtPoint` for documentation of the `asReference` flag.
     */
    private fun contentAndOffsetFromElement(psi: PsiElement, parent: KtElement, asReference: Boolean): Pair<String, Int> {
        var surroundingContent: String
        var offset: Int

        if (asReference) {
            // Convert the declaration into a fake reference expression
            when {
                parent is KtClass && psi.node.elementType == KtTokens.IDENTIFIER -> {
                    // Converting class name identifier: Use a fake property with the class name as type
                    //                                   Otherwise the compiler/analyzer would throw an exception due to a missing TopLevelDescriptorProvider
                    val prefix = "val x: "
                    surroundingContent = prefix + psi.text
                    offset = psi.textRange.startOffset - prefix.length

                    return Pair(surroundingContent, offset)
                }
            }
        }

        // Otherwise just use the expression
        val recoveryRange = parent.textRange
        LOG.info("Re-parsing {}", describeRange(recoveryRange, true))

        surroundingContent = content.substring(recoveryRange.startOffset, content.length - (parse.text.length - recoveryRange.endOffset))
        offset = recoveryRange.startOffset

        if (asReference && !((parent as? KtParameter)?.hasValOrVar() ?: true)) {
            // Prepend 'val' to (e.g. function) parameters
            val prefix = "val "
            surroundingContent = prefix + surroundingContent
            offset -= prefix.length
        }

        return Pair(surroundingContent, offset)
    }

    /**
     * Get the typed, compiled element at `cursor`.
     * This may be out-of-date if the user is typing quickly.
     */
    fun elementAtPoint(cursor: Int): KtElement? {
        val oldCursor = oldOffset(cursor)
        val psi = parse.findElementAt(oldCursor) ?: return nullResult("Couldn't find anything at ${describePosition(cursor)}")
        return psi.findParent<KtElement>()
    }

    /**
     * Find the lexical-scope surrounding `cursor`.
     * This may be out-of-date if the user is typing quickly.
     */
    fun scopeAtPoint(cursor: Int): LexicalScope? {
        val oldCursor = oldOffset(cursor)
        return compile.getSliceContents(BindingContext.LEXICAL_SCOPE).asSequence()
                .filter { it.key.textRange.startOffset <= oldCursor && oldCursor <= it.key.textRange.endOffset }
                .sortedBy { it.key.textRange.length  }
                .map { it.value }
                .firstOrNull()
    }

    fun lineBefore(cursor: Int): String = content.substring(0, cursor).substringAfterLast('\n')

    fun lineAfter(cursor: Int): String = content.substring(cursor).substringBefore('\n')

    private fun oldOffset(cursor: Int): Int {
        val (oldChanged, newChanged) = changedRegion(parse.text, content) ?: return cursor

        return when {
            cursor <= newChanged.startOffset -> cursor
            cursor < newChanged.endOffset -> {
                val newRelative = cursor - newChanged.startOffset
                val oldRelative = newRelative * oldChanged.length / newChanged.length
                oldChanged.startOffset + oldRelative
            }
            else -> parse.text.length - (content.length - cursor)
        }
    }

    fun describePosition(offset: Int, oldContent: Boolean = false): String {
        val c = if (oldContent) parse.text else content
        val pos = position(c, offset)
        val file = parse.toPath().fileName

        return "$file ${pos.line + 1}:${pos.character + 1}"
    }

    private fun describeRange(range: TextRange, oldContent: Boolean = false): String {
        val c = if (oldContent) parse.text else content
        val start = position(c, range.startOffset)
        val end = position(c, range.endOffset)
        val file = parse.toPath().fileName

        return "$file ${start.line}:${start.character + 1}-${end.line + 1}:${end.character + 1}"
    }
}

private fun fileName(file: KtFile): String {
    val parts = file.name.split('/')

    return parts.last()
}

package org.javacs.kt

import org.jetbrains.kotlin.com.intellij.codeInsight.NullableNotNullManager
import org.jetbrains.kotlin.com.intellij.lang.Language
import org.jetbrains.kotlin.com.intellij.openapi.Disposable
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.com.intellij.openapi.vfs.StandardFileSystems
import org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFileManager
import org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFileSystem
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
import org.jetbrains.kotlin.com.intellij.mock.MockProject
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.jvm.compiler.CliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoots
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration as KotlinCompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
import org.jetbrains.kotlin.resolve.calls.components.InferenceSession
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.extensions.ExtraImportsProviderExtension
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar
import org.jetbrains.kotlin.scripting.compiler.plugin.definitions.CliScriptDefinitionProvider
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.definitions.ScriptCompilationConfigurationFromDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import org.jetbrains.kotlin.scripting.definitions.StandardScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition // Legacy
import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.getEnvironment
import org.jetbrains.kotlin.scripting.extensions.ScriptExtraImportsProviderExtension
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
import org.jetbrains.kotlin.util.KotlinFrontEndException
import org.jetbrains.kotlin.utils.PathUtil
import java.io.Closeable
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import java.net.URLClassLoader
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.script.dependencies.Environment
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.DependenciesResolver.ResolveResult
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.configurationDependencies
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.JvmDependency
import org.javacs.kt.util.KotlinLSException
import org.javacs.kt.util.KotlinNullableNotNullManager
import org.javacs.kt.util.LoggingMessageCollector

private val GRADLE_DSL_DEPENDENCY_PATTERN = Regex("^gradle-(?:kotlin-dsl|core).*\\.jar$")

/**
 * Kotlin compiler APIs used to parse, analyze and compile
 * files and expressions.
 */
private class CompilationEnvironment(
    javaSourcePath: Set<Path>,
    classPath: Set<Path>
) : Closeable {
    private val disposable = Disposer.newDisposable()

    val environment: KotlinCoreEnvironment
    val parser: KtPsiFactory
    val scripts: ScriptDefinitionProvider

    init {
        environment = KotlinCoreEnvironment.createForProduction(
            parentDisposable = disposable,
            // Not to be confused with the CompilerConfiguration in the language server Configuration
            configuration = KotlinCompilerConfiguration().apply {
                val langFeatures = mutableMapOf<LanguageFeature, LanguageFeature.State>()
                langFeatures[LanguageFeature.MultiPlatformProjects] = LanguageFeature.State.ENABLED
                val languageVersionSettings = LanguageVersionSettingsImpl(
                    LanguageVersion.LATEST_STABLE,
                    ApiVersion.createByLanguageVersion(LanguageVersion.LATEST_STABLE),
                    emptyMap(),
                    langFeatures
                )

                put(CommonConfigurationKeys.MODULE_NAME, JvmProtoBufUtil.DEFAULT_MODULE_NAME)
                put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, languageVersionSettings)
                put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, LoggingMessageCollector)
                add(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS, ScriptingCompilerConfigurationComponentRegistrar())

                addJvmClasspathRoots(classPath.map { it.toFile() })
                addJavaSourceRoots(javaSourcePath.map { it.toFile() })

                // Setup script templates (e.g. used by Gradle's Kotlin DSL)
                val scriptDefinitions: MutableList<ScriptDefinition> = mutableListOf(ScriptDefinition.getDefault(defaultJvmScriptingHostConfiguration))

                if (classPath.any { GRADLE_DSL_DEPENDENCY_PATTERN.matches(it.fileName.toString()) }) {
                    LOG.info("Configuring Kotlin DSL script templates...")

                    val scriptTemplates = listOf(
                        "org.gradle.kotlin.dsl.KotlinInitScript",
                        "org.gradle.kotlin.dsl.KotlinSettingsScript",
                        "org.gradle.kotlin.dsl.KotlinBuildScript"
                    )

                    try {
                        // Load template classes
                        val scriptClassLoader = URLClassLoader(classPath.map { it.toUri().toURL() }.toTypedArray())
                        val fileClassPath = classPath.map { it.toFile() }
                        val scriptHostConfig = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
                            configurationDependencies(JvmDependency(fileClassPath))
                        }
                        // TODO: KotlinScriptDefinition will soon be deprecated, use
                        //       ScriptDefinition.compilationConfiguration and its defaultImports instead
                        //       of KotlinScriptDefinition.dependencyResolver
                        // TODO: Use ScriptDefinition.FromLegacyTemplate directly if possible
                        // scriptDefinitions = scriptTemplates.map { ScriptDefinition.FromLegacyTemplate(scriptHostConfig, scriptClassLoader.loadClass(it).kotlin) }
                        scriptDefinitions.addAll(scriptTemplates.map { ScriptDefinition.FromLegacy(scriptHostConfig, object : KotlinScriptDefinitionFromAnnotatedTemplate(
                            scriptClassLoader.loadClass(it).kotlin,
                            scriptHostConfig[ScriptingHostConfiguration.getEnvironment]?.invoke()
                        ) {
                            override val dependencyResolver: DependenciesResolver = object : DependenciesResolver {
                                override fun resolve(scriptContents: ScriptContents, environment: Environment) = ResolveResult.Success(ScriptDependencies(
                                    imports = listOf("org.gradle.kotlin.dsl.*")
                                ))
                            }
                        }) })
                    } catch (e: Exception) {
                        LOG.error("Error while loading script template classes")
                        LOG.printStackTrace(e)
                    }
                }

                LOG.info("Adding script definitions ${scriptDefinitions.map { it.asLegacyOrNull<KotlinScriptDefinition>()?.template?.simpleName }}")
                addAll(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinitions)
            },
            configFiles = EnvironmentConfigFiles.JVM_CONFIG_FILES
        )

        val project = environment.project
        if (project is MockProject) {
            project.registerService(NullableNotNullManager::class.java, KotlinNullableNotNullManager(project))
        }

        parser = KtPsiFactory(project)
        scripts = ScriptDefinitionProvider.getInstance(project)!! as CliScriptDefinitionProvider
    }

    fun updateConfiguration(config: CompilerConfiguration) {
        jvmTargetFrom(config.jvm.target)
            ?.let { environment.configuration.put(JVMConfigurationKeys.JVM_TARGET, it) }
    }

    private fun jvmTargetFrom(target: String): JvmTarget? = when (target) {
        // See https://github.com/JetBrains/kotlin/blob/master/compiler/frontend.java/src/org/jetbrains/kotlin/config/JvmTarget.kt
        "default" -> JvmTarget.DEFAULT
        "1.6" -> JvmTarget.JVM_1_6
        "1.8" -> JvmTarget.JVM_1_8
        // "9" -> JvmTarget.JVM_9
        // "10" -> JvmTarget.JVM_10
        // "11" -> JvmTarget.JVM_11
        // "12" -> JvmTarget.JVM_12
        else -> null
    }

    fun createContainer(sourcePath: Collection<KtFile>): Pair<ComponentProvider, BindingTraceContext> {
        val trace = CliBindingTrace()
        val container = TopDownAnalyzerFacadeForJVM.createContainer(
            project = environment.project,
            files = sourcePath,
            trace = trace,
            configuration = environment.configuration,
            packagePartProvider = environment::createPackagePartProvider,
            // TODO FileBasedDeclarationProviderFactory keeps indices, re-use it across calls
            declarationProviderFactory = ::FileBasedDeclarationProviderFactory
        )
        return Pair(container, trace)
    }

    override fun close() {
        Disposer.dispose(disposable)
    }
}

/**
 * Determines the compilation environment used
 * by the compiler (and thus the class path).
 */
enum class CompilationKind {
    /** Uses the default class path. */
    DEFAULT,
    /** Uses the Kotlin DSL class path if available. */
    BUILD_SCRIPT
}

/**
 * Incrementally compiles files and expressions.
 * The basic strategy for compiling one file at-a-time is outlined in OneFilePerformance.
 */
class Compiler(javaSourcePath: Set<Path>, classPath: Set<Path>, buildScriptClassPath: Set<Path> = emptySet()) : Closeable {
    private var closed = false
    private val localFileSystem: VirtualFileSystem

    private val defaultCompileEnvironment = CompilationEnvironment(javaSourcePath, classPath)
    private val buildScriptCompileEnvironment = buildScriptClassPath.takeIf { it.isNotEmpty() }?.let { CompilationEnvironment(emptySet(), it) }
    private val compileLock = ReentrantLock() // TODO: Lock at file-level

    companion object {
        init {
            setIdeaIoUseFallback()
        }
    }

    init {
        localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)
    }

    /**
     * Updates the compiler environment using the given
     * configuration (which is a class from this project).
     */
    fun updateConfiguration(config: CompilerConfiguration) {
        defaultCompileEnvironment.updateConfiguration(config)
        buildScriptCompileEnvironment?.updateConfiguration(config)
    }

    fun createPsiFile(content: String, file: Path = Paths.get("dummy.virtual.kt"), language: Language = KotlinLanguage.INSTANCE, kind: CompilationKind = CompilationKind.DEFAULT): PsiFile {
        assert(!content.contains('\r'))

        val new = psiFileFactoryFor(kind).createFileFromText(file.toString(), language, content, true, false)
        assert(new.virtualFile != null)

        return new
    }

    fun createKtFile(content: String, file: Path = Paths.get("dummy.virtual.kt"), kind: CompilationKind = CompilationKind.DEFAULT): KtFile =
            createPsiFile(content, file, language = KotlinLanguage.INSTANCE, kind = kind) as KtFile

    fun createKtExpression(content: String, file: Path = Paths.get("dummy.virtual.kt"), kind: CompilationKind = CompilationKind.DEFAULT): KtExpression {
        val property = createKtDeclaration("val x = $content", file, kind) as KtProperty
        return property.initializer!!
    }

    fun createKtDeclaration(content: String, file: Path = Paths.get("dummy.virtual.kt"), kind: CompilationKind = CompilationKind.DEFAULT): KtDeclaration {
        val parse = createKtFile(content, file, kind)
        val declarations = parse.declarations

        assert(declarations.size == 1) { "${declarations.size} declarations in $content" }

        val onlyDeclaration = declarations.first()

        if (onlyDeclaration is KtScript) {
            val scriptDeclarations = onlyDeclaration.declarations

            assert(declarations.size == 1) { "${declarations.size} declarations in script in $content" }

            return scriptDeclarations.first()
        }
        else return onlyDeclaration
    }

    private fun compileEnvironmentFor(kind: CompilationKind): CompilationEnvironment = when (kind) {
        CompilationKind.DEFAULT -> defaultCompileEnvironment
        CompilationKind.BUILD_SCRIPT -> buildScriptCompileEnvironment ?: defaultCompileEnvironment
    }

    fun psiFileFactoryFor(kind: CompilationKind): PsiFileFactory =
        PsiFileFactory.getInstance(compileEnvironmentFor(kind).environment.project)

    fun compileKtFile(file: KtFile, sourcePath: Collection<KtFile>, kind: CompilationKind = CompilationKind.DEFAULT): Pair<BindingContext, ComponentProvider> =
        compileKtFiles(listOf(file), sourcePath, kind)

    fun compileKtFiles(files: Collection<KtFile>, sourcePath: Collection<KtFile>, kind: CompilationKind = CompilationKind.DEFAULT): Pair<BindingContext, ComponentProvider> {
        if (kind == CompilationKind.BUILD_SCRIPT) {
            // Print the (legacy) script template used by the compiled Kotlin DSL build file
            files.forEach { LOG.debug { "$it -> ScriptDefinition: ${it.findScriptDefinition()?.asLegacyOrNull<KotlinScriptDefinition>()?.template?.simpleName}" } }
        }

        compileLock.withLock {
            val compileEnv = compileEnvironmentFor(kind)
            val (container, trace) = compileEnv.createContainer(sourcePath)
            container.get<LazyTopDownAnalyzer>().analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, files)
            return Pair(trace.bindingContext, container)
        }
    }

    fun compileKtExpression(expression: KtExpression, scopeWithImports: LexicalScope, sourcePath: Collection<KtFile>, kind: CompilationKind = CompilationKind.DEFAULT): Pair<BindingContext, ComponentProvider> {
        try {
            // Use same lock as 'compileFile' to avoid concurrency issues such as #42
            compileLock.withLock {
                val (container, trace) = compileEnvironmentFor(kind).createContainer(sourcePath)
                val incrementalCompiler = container.get<ExpressionTypingServices>()
                incrementalCompiler.getTypeInfo(
                        scopeWithImports,
                        expression,
                        TypeUtils.NO_EXPECTED_TYPE,
                        DataFlowInfo.EMPTY,
                        InferenceSession.default,
                        trace,
                        true)
                return Pair(trace.bindingContext, container)
            }
        } catch (e: KotlinFrontEndException) {
            throw KotlinLSException("Error while analyzing: ${describeExpression(expression.text)}", e)
        }
    }

    override fun close() {
        if (!closed) {
            defaultCompileEnvironment.close()
            buildScriptCompileEnvironment?.close()
            closed = true
        } else {
            LOG.warn("Compiler is already closed!")
        }
    }
}

private fun describeExpression(expression: String): String = expression.lines().let { lines ->
    if (lines.size < 5) {
        expression
    } else {
        (lines.take(3) + listOf("...", lines.last())).joinToString(separator = "\n")
    }
}

package org.javacs.kt

import org.javacs.kt.classpath.defaultClassPathResolver
import java.io.Closeable
import java.nio.file.Files
import java.nio.file.FileSystems
import java.nio.file.Path

/**
 * Manages the class path (compiled JARs, etc), the Java source path
 * and the compiler. Note that Kotlin sources are stored in SourcePath.
 */
class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
    private val workspaceRoots = mutableSetOf<Path>()
    private val javaSourcePath = mutableSetOf<Path>()
    private val classPath = mutableSetOf<Path>()
    private val buildScriptClassPath = mutableSetOf<Path>()
    var compiler = Compiler(javaSourcePath, classPath, buildScriptClassPath)
        private set

    init {
        compiler.updateConfiguration(config)
    }

    /** Updates and possibly reinstantiates the compiler using new paths. */
    private fun refresh(
        updateClassPath: Boolean = true,
        updateBuildScriptClassPath: Boolean = true,
        updateJavaSourcePath: Boolean = true
    ): Boolean {
        // TODO: Fetch class path and build script class path concurrently (and asynchronously)
        val resolver = defaultClassPathResolver(workspaceRoots)
        var refreshCompiler = updateJavaSourcePath

        if (updateClassPath) {
            val newClassPath = resolver.classpathOrEmpty
            if (newClassPath != classPath) {
                syncPaths(classPath, newClassPath, "class path")
                refreshCompiler = true
            }
        }

        if (updateBuildScriptClassPath) {
            LOG.info("Update build script path")
            val newBuildScriptClassPath = resolver.buildScriptClasspathOrEmpty
            if (newBuildScriptClassPath != buildScriptClassPath) {
                syncPaths(buildScriptClassPath, newBuildScriptClassPath, "class path")
                refreshCompiler = true
            }
        }

        if (refreshCompiler) {
            LOG.info("Reinstantiating compiler")
            compiler.close()
            compiler = Compiler(javaSourcePath, classPath, buildScriptClassPath)
            updateCompilerConfiguration()
        }

        return refreshCompiler
    }

    /** Synchronizes the given two path sets and logs the differences. */
    private fun syncPaths(dest: MutableSet<Path>, new: Set<Path>, name: String) {
        val added = new - dest
        val removed = dest - new

        logAdded(added, name)
        logRemoved(removed, name)

        dest.removeAll(removed)
        dest.addAll(added)
    }

    fun updateCompilerConfiguration() {
        compiler.updateConfiguration(config)
    }

    fun addWorkspaceRoot(root: Path): Boolean {
        LOG.info("Searching for dependencies and Java sources in workspace root {}", root)

        workspaceRoots.add(root)
        javaSourcePath.addAll(findJavaSourceFiles(root))

        return refresh()
    }

    fun removeWorkspaceRoot(root: Path): Boolean {
        LOG.info("Removing dependencies and Java source path from workspace root {}", root)

        workspaceRoots.remove(root)
        javaSourcePath.removeAll(findJavaSourceFiles(root))

        return refresh()
    }

    fun createdOnDisk(file: Path): Boolean {
        if (isJavaSource(file)) {
            javaSourcePath.add(file)
        }
        return changedOnDisk(file)
    }

    fun deletedOnDisk(file: Path): Boolean {
        if (isJavaSource(file)) {
            javaSourcePath.remove(file)
        }
        return changedOnDisk(file)
    }

    fun changedOnDisk(file: Path): Boolean {
        val buildScript = isBuildScript(file)
        val javaSource = isJavaSource(file)
        if (buildScript || javaSource) {
            return refresh(updateClassPath = buildScript, updateBuildScriptClassPath = false, updateJavaSourcePath = javaSource)
        } else {
            return false
        }
    }

    private fun isJavaSource(file: Path): Boolean = file.fileName.toString().endsWith(".java")

    private fun isBuildScript(file: Path): Boolean = file.fileName.toString().let { it == "pom.xml" || it == "build.gradle" || it == "build.gradle.kts" }

    override fun close() {
        compiler.close()
    }
}

private fun findJavaSourceFiles(root: Path): Set<Path> {
    val sourceMatcher = FileSystems.getDefault().getPathMatcher("glob:*.java")
    return SourceExclusions(root)
        .walkIncluded()
        .filter { sourceMatcher.matches(it.fileName) }
        .toSet()
}

private fun logAdded(sources: Collection<Path>, name: String) {
    when {
        sources.isEmpty() -> return
        sources.size > 5 -> LOG.info("Adding {} files to {}", sources.size, name)
        else -> LOG.info("Adding {} to {}", sources, name)
    }
}

private fun logRemoved(sources: Collection<Path>, name: String) {
    when {
        sources.isEmpty() -> return
        sources.size > 5 -> LOG.info("Removing {} files from {}", sources.size, name)
        else -> LOG.info("Removing {} from {}", sources, name)
    }
}

package org.javacs.kt

public data class SnippetsConfiguration(
    /** Whether code completion should return VSCode-style snippets. */
    var enabled: Boolean = true
)

public data class CompletionConfiguration(
    val snippets: SnippetsConfiguration = SnippetsConfiguration()
)

public data class LintingConfiguration(
    /** The time interval between subsequent lints in ms. */
    var debounceTime: Long = 250L
)

public data class JVMConfiguration(
    /** Which JVM target the Kotlin compiler uses. See Compiler.jvmTargetFrom for possible values. */
    var target: String = "default"
)

public data class CompilerConfiguration(
    val jvm: JVMConfiguration = JVMConfiguration()
)

public data class ExternalSourcesConfiguration(
    /** Whether kls-URIs should be sent to the client to describe classes in JARs. */
    var useKlsScheme: Boolean = false,
    /** Whether external classes classes should be automatically converted to Kotlin. */
    var autoConvertToKotlin: Boolean = true
)

public data class Configuration(
    val compiler: CompilerConfiguration = CompilerConfiguration(),
    val completion: CompletionConfiguration = CompletionConfiguration(),
    val linting: LintingConfiguration = LintingConfiguration(),
    val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration()
)

package org.javacs.kt

import org.eclipse.lsp4j.*
import org.eclipse.lsp4j.jsonrpc.messages.Either
import org.eclipse.lsp4j.jsonrpc.services.JsonDelegate
import org.eclipse.lsp4j.services.LanguageClient
import org.eclipse.lsp4j.services.LanguageClientAware
import org.eclipse.lsp4j.services.LanguageServer
import org.javacs.kt.commands.ALL_COMMANDS
import org.javacs.kt.externalsources.JarClassContentProvider
import org.javacs.kt.util.TemporaryDirectory
import org.javacs.kt.util.parseURI
import java.net.URI
import java.io.Closeable
import java.nio.file.Paths
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletableFuture.completedFuture

class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
    private val config = Configuration()
    val classPath = CompilerClassPath(config.compiler)

    private val tempDirectory = TemporaryDirectory()
    private val uriContentProvider = URIContentProvider(JarClassContentProvider(config.externalSources, classPath, tempDirectory))
    val sourcePath = SourcePath(classPath, uriContentProvider)
    val sourceFiles = SourceFiles(sourcePath, uriContentProvider)

    private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath, config, tempDirectory, uriContentProvider)
    private val workspaces = KotlinWorkspaceService(sourceFiles, sourcePath, classPath, textDocuments, config)
    private val protocolExtensions = KotlinProtocolExtensionService(uriContentProvider)

    override fun connect(client: LanguageClient) {
        connectLoggingBackend(client)

        workspaces.connect(client)
        textDocuments.connect(client)

        LOG.info("Connected to client")
    }

    override fun getTextDocumentService(): KotlinTextDocumentService = textDocuments

    override fun getWorkspaceService(): KotlinWorkspaceService = workspaces

    @JsonDelegate
    fun getProtocolExtensionService(): KotlinProtocolExtensions = protocolExtensions

    override fun initialize(params: InitializeParams): CompletableFuture<InitializeResult> {
        val serverCapabilities = ServerCapabilities()
        serverCapabilities.setTextDocumentSync(TextDocumentSyncKind.Incremental)
        serverCapabilities.workspace = WorkspaceServerCapabilities()
        serverCapabilities.workspace.workspaceFolders = WorkspaceFoldersOptions()
        serverCapabilities.workspace.workspaceFolders.supported = true
        serverCapabilities.workspace.workspaceFolders.changeNotifications = Either.forRight(true)
        serverCapabilities.hoverProvider = true
        serverCapabilities.completionProvider = CompletionOptions(false, listOf("."))
        serverCapabilities.signatureHelpProvider = SignatureHelpOptions(listOf("(", ","))
        serverCapabilities.definitionProvider = true
        serverCapabilities.documentSymbolProvider = true
        serverCapabilities.workspaceSymbolProvider = true
        serverCapabilities.referencesProvider = true
        serverCapabilities.codeActionProvider = Either.forLeft(true)
        serverCapabilities.documentFormattingProvider = true
        serverCapabilities.documentRangeFormattingProvider = true
        serverCapabilities.executeCommandProvider = ExecuteCommandOptions(ALL_COMMANDS)

        val clientCapabilities = params.capabilities
        config.completion.snippets.enabled = clientCapabilities?.textDocument?.completion?.completionItem?.snippetSupport ?: false

        if (params.rootUri != null) {
            LOG.info("Adding workspace {} to source path", params.rootUri)

            val root = Paths.get(parseURI(params.rootUri))

            sourceFiles.addWorkspaceRoot(root)
            val refreshed = classPath.addWorkspaceRoot(root)
            if (refreshed) {
                sourcePath.refresh()
            }
        }

        return completedFuture(InitializeResult(serverCapabilities))
    }

    private fun connectLoggingBackend(client: LanguageClient) {
        val backend: (LogMessage) -> Unit = {
            client.logMessage(MessageParams().apply {
                type = it.level.toLSPMessageType()
                message = it.message
            })
        }
        LOG.connectOutputBackend(backend)
        LOG.connectErrorBackend(backend)
    }

    private fun LogLevel.toLSPMessageType(): MessageType = when (this) {
        LogLevel.ERROR -> MessageType.Error
        LogLevel.WARN -> MessageType.Warning
        LogLevel.INFO -> MessageType.Info
        else -> MessageType.Log
    }

    override fun close() {
        textDocumentService.close()
        classPath.close()
        tempDirectory.close()
    }

    override fun shutdown(): CompletableFuture<Any> {
        close()
        return completedFuture(null)
    }

    override fun exit() {}
}

package org.javacs.kt

import org.eclipse.lsp4j.*
import org.javacs.kt.externalsources.JarClassContentProvider
import org.javacs.kt.externalsources.toKlsURI
import org.javacs.kt.util.AsyncExecutor
import org.javacs.kt.util.noResult
import org.javacs.kt.util.parseURI
import java.net.URI
import java.net.URISyntaxException
import java.util.concurrent.CompletableFuture

class KotlinProtocolExtensionService(
    private val uriContentProvider: URIContentProvider
) : KotlinProtocolExtensions {
    private val async = AsyncExecutor()

    override fun jarClassContents(textDocument: TextDocumentIdentifier): CompletableFuture<String?> = async.compute {
        uriContentProvider.contentOf(parseURI(textDocument.uri))
    }
}

package org.javacs.kt

import org.eclipse.lsp4j.*
import org.eclipse.lsp4j.jsonrpc.services.JsonSegment
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
import java.util.concurrent.CompletableFuture

@JsonSegment("kotlin")
interface KotlinProtocolExtensions {
    @JsonRequest
    fun jarClassContents(textDocument: TextDocumentIdentifier): CompletableFuture<String?>
}

package org.javacs.kt

import org.eclipse.lsp4j.*
import org.eclipse.lsp4j.jsonrpc.messages.Either
import org.eclipse.lsp4j.services.LanguageClient
import org.eclipse.lsp4j.services.TextDocumentService
import org.javacs.kt.completion.*
import org.javacs.kt.definition.goToDefinition
import org.javacs.kt.diagnostic.convertDiagnostic
import org.javacs.kt.externalsources.JarClassContentProvider
import org.javacs.kt.formatting.formatKotlinCode
import org.javacs.kt.hover.hoverAt
import org.javacs.kt.position.offset
import org.javacs.kt.position.extractRange
import org.javacs.kt.position.position
import org.javacs.kt.references.findReferences
import org.javacs.kt.signaturehelp.fetchSignatureHelpAt
import org.javacs.kt.symbols.documentSymbols
import org.javacs.kt.util.noResult
import org.javacs.kt.util.AsyncExecutor
import org.javacs.kt.util.Debouncer
import org.javacs.kt.util.filePath
import org.javacs.kt.util.TemporaryDirectory
import org.javacs.kt.util.parseURI
import org.javacs.kt.util.describeURI
import org.javacs.kt.util.describeURIs
import org.javacs.kt.commands.JAVA_TO_KOTLIN_COMMAND
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import java.net.URI
import java.io.Closeable
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Duration
import java.util.concurrent.CompletableFuture

class KotlinTextDocumentService(
    private val sf: SourceFiles,
    private val sp: SourcePath,
    private val config: Configuration,
    private val tempDirectory: TemporaryDirectory,
    private val uriContentProvider: URIContentProvider
) : TextDocumentService, Closeable {
    private lateinit var client: LanguageClient
    private val async = AsyncExecutor()

    var debounceLint = Debouncer(Duration.ofMillis(config.linting.debounceTime))
    val lintTodo = mutableSetOf<URI>()
    var lintCount = 0

    var lintRecompilationCallback: () -> Unit
        get() = sp.beforeCompileCallback
        set(callback) { sp.beforeCompileCallback = callback }

    private val TextDocumentItem.filePath: Path?
        get() = parseURI(uri).filePath

    private val TextDocumentIdentifier.filePath: Path?
        get() = parseURI(uri).filePath

    private val TextDocumentIdentifier.isKotlinScript: Boolean
        get() = uri.endsWith(".kts")

    private val TextDocumentIdentifier.content: String
        get() = uriContentProvider.contentOf(parseURI(uri))

    fun connect(client: LanguageClient) {
        this.client = client
    }

    private enum class Recompile {
        ALWAYS, AFTER_DOT, NEVER
    }

    private fun recover(position: TextDocumentPositionParams, recompile: Recompile): Pair<CompiledFile, Int> {
        val uri = parseURI(position.textDocument.uri)
        val content = sp.content(uri)
        val offset = offset(content, position.position.line, position.position.character)
        val shouldRecompile = when (recompile) {
            Recompile.ALWAYS -> true
            Recompile.AFTER_DOT -> offset > 0 && content[offset - 1] == '.'
            Recompile.NEVER -> false
        }
        val compiled = if (shouldRecompile) sp.currentVersion(uri) else sp.latestCompiledVersion(uri)
        return Pair(compiled, offset)
    }

    override fun codeAction(params: CodeActionParams): CompletableFuture<List<Either<Command, CodeAction>>> = async.compute {
        val start = params.range.start
        val end = params.range.end
        val hasSelection = (end.character - start.character) != 0 || (end.line - start.line) != 0
        if (hasSelection) {
            listOf(
                Either.forLeft<Command, CodeAction>(
                    Command("Convert Java to Kotlin", JAVA_TO_KOTLIN_COMMAND, listOf(
                        params.textDocument.uri,
                        params.range
                    ))
                )
            )
        } else {
            emptyList()
        }
    }

    override fun hover(position: TextDocumentPositionParams): CompletableFuture<Hover?> = async.compute {
        reportTime {
            LOG.info("Hovering at {}", describePosition(position))

            val (file, cursor) = recover(position, Recompile.NEVER)
            hoverAt(file, cursor) ?: noResult("No hover found at ${describePosition(position)}", null)
        }
    }

    override fun documentHighlight(position: TextDocumentPositionParams): CompletableFuture<List<DocumentHighlight>> {
        TODO("not implemented")
    }

    override fun onTypeFormatting(params: DocumentOnTypeFormattingParams): CompletableFuture<List<TextEdit>> {
        TODO("not implemented")
    }

    override fun definition(position: TextDocumentPositionParams): CompletableFuture<Either<List<Location>, List<LocationLink>>> = async.compute {
        reportTime {
            LOG.info("Go-to-definition at {}", describePosition(position))

            val (file, cursor) = recover(position, Recompile.NEVER)
            goToDefinition(file, cursor, uriContentProvider.jarClassContentProvider, tempDirectory, config.externalSources)
                ?.let(::listOf)
                ?.let { Either.forLeft<List<Location>, List<LocationLink>>(it) }
                ?: noResult("Couldn't find definition at ${describePosition(position)}", Either.forLeft(emptyList()))
        }
    }

    override fun rangeFormatting(params: DocumentRangeFormattingParams): CompletableFuture<List<TextEdit>> = async.compute {
        val code = extractRange(params.textDocument.content, params.range)
        listOf(TextEdit(
            params.range,
            formatKotlinCode(
                code,
                isScript = params.textDocument.isKotlinScript,
                options = params.options
            )
        ))
    }

    override fun codeLens(params: CodeLensParams): CompletableFuture<List<CodeLens>> {
        TODO("not implemented")
    }

    override fun rename(params: RenameParams): CompletableFuture<WorkspaceEdit> {
        TODO("not implemented")
    }

    override fun completion(position: CompletionParams) = async.compute {
        reportTime {
            LOG.info("Completing at {}", describePosition(position))

            val (file, cursor) = recover(position, Recompile.NEVER) // TODO: Investigate when to recompile
            val completions = completions(file, cursor, config.completion)
            LOG.info("Found {} items", completions.items.size)

            Either.forRight<List<CompletionItem>, CompletionList>(completions)
        }
    }

    override fun resolveCompletionItem(unresolved: CompletionItem): CompletableFuture<CompletionItem> {
        TODO("not implemented")
    }

    override fun documentSymbol(params: DocumentSymbolParams): CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> = async.compute {
        LOG.info("Find symbols in {}", describeURI(params.textDocument.uri))

        reportTime {
            val uri = parseURI(params.textDocument.uri)
            val parsed = sp.parsedFile(uri)

            documentSymbols(parsed)
        }
    }

    override fun didOpen(params: DidOpenTextDocumentParams) {
        val uri = parseURI(params.textDocument.uri)
        sf.open(uri, params.textDocument.text, params.textDocument.version)
        lintNow(uri)
    }

    override fun didSave(params: DidSaveTextDocumentParams) {
        // Lint after saving to prevent inconsistent diagnostics
        val uri = parseURI(params.textDocument.uri)
        lintNow(uri)
    }

    override fun signatureHelp(position: TextDocumentPositionParams): CompletableFuture<SignatureHelp?> = async.compute {
        reportTime {
            LOG.info("Signature help at {}", describePosition(position))

            val (file, cursor) = recover(position, Recompile.NEVER)
            fetchSignatureHelpAt(file, cursor) ?: noResult("No function call around ${describePosition(position)}", null)
        }
    }

    override fun didClose(params: DidCloseTextDocumentParams) {
        val uri = parseURI(params.textDocument.uri)
        sf.close(uri)
        clearDiagnostics(uri)
    }

    override fun formatting(params: DocumentFormattingParams): CompletableFuture<List<TextEdit>> = async.compute {
        val code = params.textDocument.content
        LOG.info("Formatting {}", describeURI(params.textDocument.uri))
        listOf(TextEdit(
            Range(Position(0, 0), position(code, code.length)),
            formatKotlinCode(
                code,
                isScript = params.textDocument.isKotlinScript,
                options = params.options
            )
        ))
    }

    override fun didChange(params: DidChangeTextDocumentParams) {
        val uri = parseURI(params.textDocument.uri)
        sf.edit(uri, params.textDocument.version, params.contentChanges)
        lintLater(uri)
    }

    override fun references(position: ReferenceParams) = async.compute {
        position.textDocument.filePath
            ?.let { file ->
                val content = sp.content(parseURI(position.textDocument.uri))
                val offset = offset(content, position.position.line, position.position.character)
                findReferences(file, offset, sp)
            }
        }

    override fun resolveCodeLens(unresolved: CodeLens): CompletableFuture<CodeLens> {
        TODO("not implemented")
    }

    private fun describePosition(position: TextDocumentPositionParams): String {
        return "${describeURI(position.textDocument.uri)} ${position.position.line + 1}:${position.position.character + 1}"
    }

    public fun updateDebouncer() {
        debounceLint = Debouncer(Duration.ofMillis(config.linting.debounceTime))
    }

    private fun clearLint(): List<URI> {
        val result = lintTodo.toList()
        lintTodo.clear()
        return result
    }

    private fun lintLater(uri: URI) {
        lintTodo.add(uri)
        debounceLint.schedule(::doLint)
    }

    private fun lintNow(file: URI) {
        lintTodo.add(file)
        debounceLint.submitImmediately(::doLint)
    }

    private fun doLint(cancelCallback: () -> Boolean) {
        LOG.info("Linting {}", describeURIs(lintTodo))
        val files = clearLint()
        val context = sp.compileFiles(files)
        if (!cancelCallback.invoke()) {
            reportDiagnostics(files, context.diagnostics)
        }
        lintCount++
    }

    private fun reportDiagnostics(compiled: Collection<URI>, kotlinDiagnostics: Diagnostics) {
        val langServerDiagnostics = kotlinDiagnostics.flatMap(::convertDiagnostic)
        val byFile = langServerDiagnostics.groupBy({ it.first }, { it.second })

        for ((uri, diagnostics) in byFile) {
            if (sf.isOpen(uri)) {
                client.publishDiagnostics(PublishDiagnosticsParams(uri.toString(), diagnostics))

                LOG.info("Reported {} diagnostics in {}", diagnostics.size, describeURI(uri))
            }
            else LOG.info("Ignore {} diagnostics in {} because it's not open", diagnostics.size, describeURI(uri))
        }

        val noErrors = compiled - byFile.keys
        for (file in noErrors) {
            clearDiagnostics(file)

            LOG.info("No diagnostics in {}", file)
        }

        lintCount++
    }

    private fun clearDiagnostics(uri: URI) {
        client.publishDiagnostics(PublishDiagnosticsParams(uri.toString(), listOf()))
    }

    private fun shutdownExecutors(awaitTermination: Boolean) {
        async.shutdown(awaitTermination)
        debounceLint.shutdown(awaitTermination)
    }

    override fun close() {
        shutdownExecutors(awaitTermination = true)
    }
}

private inline fun<T> reportTime(block: () -> T): T {
    val started = System.currentTimeMillis()
    try {
        return block()
    } finally {
        val finished = System.currentTimeMillis()
        LOG.info("Finished in {} ms", finished - started)
    }
}

package org.javacs.kt

import org.jetbrains.kotlin.com.intellij.openapi.project.Project
import org.eclipse.lsp4j.*
import org.eclipse.lsp4j.services.WorkspaceService
import org.eclipse.lsp4j.services.LanguageClient
import org.eclipse.lsp4j.services.LanguageClientAware
import org.eclipse.lsp4j.jsonrpc.messages.Either
import org.javacs.kt.symbols.workspaceSymbols
import org.javacs.kt.commands.JAVA_TO_KOTLIN_COMMAND
import org.javacs.kt.j2k.convertJavaToKotlin
import org.javacs.kt.KotlinTextDocumentService
import org.javacs.kt.position.extractRange
import org.javacs.kt.util.filePath
import org.javacs.kt.util.parseURI
import java.net.URI
import java.nio.file.Paths
import java.util.concurrent.CompletableFuture
import com.google.gson.JsonElement
import com.google.gson.Gson
import com.google.gson.JsonObject

class KotlinWorkspaceService(
    private val sf: SourceFiles,
    private val sp: SourcePath,
    private val cp: CompilerClassPath,
    private val docService: KotlinTextDocumentService,
    private val config: Configuration
) : WorkspaceService, LanguageClientAware {
    private val gson = Gson()
    private var languageClient: LanguageClient? = null

    override fun connect(client: LanguageClient): Unit {
        languageClient = client
    }

    override fun executeCommand(params: ExecuteCommandParams): CompletableFuture<Any> {
        val args = params.arguments
        LOG.info("Executing command: {} with {}", params.command, params.arguments)

        when (params.command) {
            JAVA_TO_KOTLIN_COMMAND -> {
                val fileUri = gson.fromJson(args[0] as JsonElement, String::class.java)
                val range = gson.fromJson(args[1] as JsonElement, Range::class.java)

                val selectedJavaCode = extractRange(sp.content(parseURI(fileUri)), range)
                val kotlinCode = convertJavaToKotlin(selectedJavaCode, cp.compiler)

                languageClient?.applyEdit(ApplyWorkspaceEditParams(WorkspaceEdit(listOf(Either.forLeft<TextDocumentEdit, ResourceOperation>(
                    TextDocumentEdit(
                        VersionedTextDocumentIdentifier().apply { uri = fileUri },
                        listOf(TextEdit(range, kotlinCode))
                    )
                )))))
            }
        }

        return CompletableFuture.completedFuture(null)
    }

    override fun didChangeWatchedFiles(params: DidChangeWatchedFilesParams) {
        for (change in params.changes) {
            val uri = parseURI(change.uri)
            val path = uri.filePath

            when (change.type) {
                FileChangeType.Created -> {
                    sf.createdOnDisk(uri)
                    path?.let(cp::createdOnDisk)?.let { if (it) sp.refresh() }
                }
                FileChangeType.Deleted -> {
                    sf.deletedOnDisk(uri)
                    path?.let(cp::deletedOnDisk)?.let { if (it) sp.refresh() }
                }
                FileChangeType.Changed -> {
                    sf.changedOnDisk(uri)
                    path?.let(cp::changedOnDisk)?.let { if (it) sp.refresh() }
                }
                null -> {
                    // Nothing to do
                }
            }
        }
    }

    override fun didChangeConfiguration(params: DidChangeConfigurationParams) {
        val settings = params.settings as? JsonObject
        settings?.get("kotlin")?.asJsonObject?.apply {
            // Update deprecated configuration keys
            get("debounceTime")?.asLong?.let {
                config.linting.debounceTime = it
                docService.updateDebouncer()
            }
            get("snippetsEnabled")?.asBoolean?.let { config.completion.snippets.enabled = it }

            // Update compiler options
            get("compiler")?.asJsonObject?.apply {
                val compiler = config.compiler
                get("jvm")?.asJsonObject?.apply {
                    val jvm = compiler.jvm
                    get("target")?.asString?.let {
                        jvm.target = it
                        cp.updateCompilerConfiguration()
                    }
                }
            }

            // Update linter options
            get("linting")?.asJsonObject?.apply {
                val linting = config.linting
                get("debounceTime")?.asLong?.let {
                    linting.debounceTime = it
                    docService.updateDebouncer()
                }
            }

            // Update code-completion options
            get("completion")?.asJsonObject?.apply {
                val completion = config.completion
                get("snippets")?.asJsonObject?.apply {
                    val snippets = completion.snippets
                    get("enabled")?.asBoolean?.let { snippets.enabled = it }
                }
            }

            // Update options about external sources e.g. JAR files, decompilers, etc
            get("externalSources")?.asJsonObject?.apply {
                val externalSources = config.externalSources
                get("useKlsScheme")?.asBoolean?.let { externalSources.useKlsScheme = it }
                get("autoConvertToKotlin")?.asBoolean?.let { externalSources.autoConvertToKotlin = it }
            }
        }

        LOG.info("Updated configuration: {}", settings)
    }

    override fun symbol(params: WorkspaceSymbolParams): CompletableFuture<List<SymbolInformation>> {
        val result = workspaceSymbols(params.query, sp)

        return CompletableFuture.completedFuture(result)
    }

    override fun didChangeWorkspaceFolders(params: DidChangeWorkspaceFoldersParams) {
        for (change in params.event.added) {
            LOG.info("Adding workspace {} to source path", change.uri)

            val root = Paths.get(parseURI(change.uri))

            sf.addWorkspaceRoot(root)
            val refreshed = cp.addWorkspaceRoot(root)
            if (refreshed) {
                sp.refresh()
            }
        }
        for (change in params.event.removed) {
            LOG.info("Dropping workspace {} from source path", change.uri)

            val root = Paths.get(parseURI(change.uri))

            sf.removeWorkspaceRoot(root)
            val refreshed = cp.removeWorkspaceRoot(root)
            if (refreshed) {
                sp.refresh()
            }
        }
    }
}

package org.javacs.kt

import com.beust.jcommander.JCommander
import com.beust.jcommander.Parameter
import java.util.concurrent.Executors
import org.eclipse.lsp4j.launch.LSPLauncher
import org.eclipse.lsp4j.ConfigurationParams
import org.eclipse.lsp4j.ConfigurationItem
import org.javacs.kt.util.ExitingInputStream

class Args {
    /*
     * The language server can currently be launched in three modes:
     *  - Stdio, in which case no argument should be specified (used by default)
     *  - TCP Server, in which case the client has to connect to the specified tcpServerPort (used by the Docker image)
     *  - TCP Client, in whcih case the server will connect to the specified tcpClientPort/tcpClientHost (optionally used by VSCode)
     */

    @Parameter(names = ["--tcpServerPort", "-sp"])
    var tcpServerPort: Int? = null
    @Parameter(names = ["--tcpClientPort", "-p"])
    var tcpClientPort: Int? = null
    @Parameter(names = ["--tcpCllientHost", "-h"])
    var tcpClientHost: String = "localhost"
}

fun main(argv: Array<String>) {
    // Redirect java.util.logging calls (e.g. from LSP4J)
    LOG.connectJULFrontend()

    val args = Args().also { JCommander.newBuilder().addObject(it).build().parse(*argv) }
    val (inStream, outStream) = args.tcpClientPort?.let {
        // Launch as TCP Client
        LOG.connectStdioBackend()
        tcpConnectToClient(args.tcpClientHost, it)
    } ?: args.tcpServerPort?.let {
        // Launch as TCP Server
        LOG.connectStdioBackend()
        tcpStartServer(it)
    } ?: Pair(System.`in`, System.out)

    val server = KotlinLanguageServer()
    val threads = Executors.newSingleThreadExecutor { Thread(it, "client") }
    val launcher = LSPLauncher.createServerLauncher(server, ExitingInputStream(inStream), outStream, threads, { it })

    server.connect(launcher.remoteProxy)
    launcher.startListening()
}

package org.javacs.kt

import org.jetbrains.kotlin.com.intellij.openapi.util.text.StringUtil.convertLineSeparators
import org.jetbrains.kotlin.com.intellij.lang.java.JavaLanguage
import org.jetbrains.kotlin.com.intellij.lang.Language
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.eclipse.lsp4j.TextDocumentContentChangeEvent
import org.javacs.kt.util.KotlinLSException
import org.javacs.kt.util.filePath
import org.javacs.kt.util.partitionAroundLast
import org.javacs.kt.util.describeURIs
import org.javacs.kt.util.describeURI
import java.io.BufferedReader
import java.io.StringReader
import java.io.StringWriter
import java.io.IOException
import java.io.FileNotFoundException
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

private class SourceVersion(val content: String, val version: Int, val language: Language?, val isTemporary: Boolean)

/**
 * Notify SourcePath whenever a file changes
 */
private class NotifySourcePath(private val sp: SourcePath) {
    private val files = mutableMapOf<URI, SourceVersion>()

    operator fun get(uri: URI): SourceVersion? = files[uri]

    operator fun set(uri: URI, source: SourceVersion) {
        val content = convertLineSeparators(source.content)

        files[uri] = source
        sp.put(uri, content, source.language, source.isTemporary)
    }

    fun remove(uri: URI) {
        files.remove(uri)
        sp.delete(uri)
    }

    fun removeIfTemporary(uri: URI): Boolean =
        if (sp.deleteIfTemporary(uri)) {
            files.remove(uri)
            true
        } else {
            false
        }

    fun removeAll(rm: Collection<URI>) {
        files -= rm

        rm.forEach(sp::delete)
    }

    val keys get() = files.keys
}

/**
 * Keep track of the text of all files in the workspace
 */
class SourceFiles(
    private val sp: SourcePath,
    private val contentProvider: URIContentProvider
) {
    private val workspaceRoots = mutableSetOf<Path>()
    private var exclusions = SourceExclusions(workspaceRoots)
    private val files = NotifySourcePath(sp)
    private val open = mutableSetOf<URI>()

    fun open(uri: URI, content: String, version: Int) {
        files[uri] = SourceVersion(content, version, languageOf(uri), isTemporary = !exclusions.isURIIncluded(uri))
        open.add(uri)
    }

    fun close(uri: URI) {
        if (uri in open) {
            open.remove(uri)
            val removed = files.removeIfTemporary(uri)

            if (!removed) {
                val disk = readFromDisk(uri, temporary = false)

                if (disk != null) {
                    files[uri] = disk
                } else {
                    files.remove(uri)
                }
            }
        }
    }

    fun edit(uri: URI, newVersion: Int, contentChanges: List<TextDocumentContentChangeEvent>) {
        if (exclusions.isURIIncluded(uri)) {
            val existing = files[uri]!!
            var newText = existing.content

            if (newVersion <= existing.version) {
                LOG.warn("Ignored {} version {}", describeURI(uri), newVersion)
                return
            }

            for (change in contentChanges) {
                if (change.range == null) newText = change.text
                else newText = patch(newText, change)
            }

            files[uri] = SourceVersion(newText, newVersion, existing.language, existing.isTemporary)
        }
    }

    fun createdOnDisk(uri: URI) {
        changedOnDisk(uri)
    }

    fun deletedOnDisk(uri: URI) {
        if (isSource(uri)) {
            files.remove(uri)
        }
    }

    fun changedOnDisk(uri: URI) {
        if (isSource(uri)) {
            files[uri] = readFromDisk(uri, files[uri]?.isTemporary ?: true)
                ?: throw KotlinLSException("Could not read source file '$uri' after being changed on disk")
        }
    }

    private fun readFromDisk(uri: URI, temporary: Boolean): SourceVersion? = try {
        val content = contentProvider.contentOf(uri)
        SourceVersion(content, -1, languageOf(uri), isTemporary = temporary)
    } catch (e: FileNotFoundException) {
        null
    } catch (e: IOException) {
        LOG.warn("Exception while reading source file {}", describeURI(uri))
        null
    }

    private fun isSource(uri: URI): Boolean = exclusions.isURIIncluded(uri) && languageOf(uri) != null

    private fun languageOf(uri: URI): Language? {
        val fileName = uri.filePath?.fileName?.toString() ?: return null
        return when {
            fileName.endsWith(".kt") || fileName.endsWith(".kts") -> KotlinLanguage.INSTANCE
            else -> null
        }
    }

    fun addWorkspaceRoot(root: Path) {
        val addSources = findSourceFiles(root)

        logAdded(addSources, root)

        for (uri in addSources) {
            readFromDisk(uri, temporary = false)?.let {
                files[uri] = it
            } ?: LOG.warn("Could not read source file '{}'", uri.path)
        }

        workspaceRoots.add(root)
        updateExclusions()
    }

    fun removeWorkspaceRoot(root: Path) {
        val rmSources = files.keys.filter { it.filePath?.startsWith(root) ?: false }

        logRemoved(rmSources, root)

        files.removeAll(rmSources)
        workspaceRoots.remove(root)
        updateExclusions()
    }

    private fun updateExclusions() {
        exclusions = SourceExclusions(workspaceRoots)
    }

    fun isOpen(uri: URI): Boolean = (uri in open)
}

private fun patch(sourceText: String, change: TextDocumentContentChangeEvent): String {
    val range = change.range
    val reader = BufferedReader(StringReader(sourceText))
    val writer = StringWriter()

    // Skip unchanged lines
    var line = 0

    while (line < range.start.line) {
        writer.write(reader.readLine() + '\n')
        line++
    }

    // Skip unchanged chars
    for (character in 0 until range.start.character)
        writer.write(reader.read())

    // Write replacement text
    writer.write(change.text)

    // Skip replaced text
    reader.skip(change.rangeLength!!.toLong())

    // Write remaining text
    while (true) {
        val next = reader.read()

        if (next == -1) return writer.toString()
        else writer.write(next)
    }
}

private fun findSourceFiles(root: Path): Set<URI> {
    val sourceMatcher = FileSystems.getDefault().getPathMatcher("glob:*.{kt,kts}")
    return SourceExclusions(root)
        .walkIncluded()
        .filter { sourceMatcher.matches(it.fileName) }
        .map(Path::toUri)
        .toSet()
}

private fun logAdded(sources: Collection<URI>, rootPath: Path?) {
    LOG.info("Adding {} under {} to source path", describeURIs(sources), rootPath)
}

private fun logRemoved(sources: Collection<URI>, rootPath: Path?) {
    LOG.info("Removing {} under {} to source path", describeURIs(sources), rootPath)
}

package org.javacs.kt

import org.javacs.kt.util.fileExtension
import org.javacs.kt.util.filePath
import org.javacs.kt.util.describeURI
import org.jetbrains.kotlin.com.intellij.lang.Language
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.CompositeBindingContext
import kotlin.concurrent.withLock
import java.nio.file.Path
import java.nio.file.Paths
import java.net.URI
import java.util.concurrent.locks.ReentrantLock

class SourcePath(
    private val cp: CompilerClassPath,
    private val contentProvider: URIContentProvider
) {
    private val files = mutableMapOf<URI, SourceFile>()
    private val parseDataWriteLock = ReentrantLock()

    var beforeCompileCallback: () -> Unit = {}

    private inner class SourceFile(
        val uri: URI,
        var content: String,
        val path: Path? = uri.filePath,
        var parsed: KtFile? = null,
        var compiledFile: KtFile? = null,
        var compiledContext: BindingContext? = null,
        var compiledContainer: ComponentProvider? = null,
        val language: Language? = null,
        val isTemporary: Boolean = false // A temporary source file will not be returned by .all()
    ) {
        val extension: String? = uri.fileExtension ?: language?.associatedFileType?.defaultExtension
        val isScript: Boolean = extension == "kts"
        val kind: CompilationKind =
            if (path?.fileName?.toString()?.endsWith(".gradle.kts") ?: false) CompilationKind.BUILD_SCRIPT
            else CompilationKind.DEFAULT

        fun put(newContent: String) {
            content = newContent
        }

        fun clean() {
            parsed = null
            compiledFile = null
            compiledContext = null
            compiledContainer = null
        }

        fun parse() {
            // TODO: Create PsiFile using the stored language instead
            parsed = cp.compiler.createKtFile(content, path ?: Paths.get("sourceFile.virtual.$extension"), kind)
        }

        fun parseIfChanged() {
            if (content != parsed?.text) {
                parse()
            }
        }

        fun compileIfNull() = parseIfChanged().apply { doCompileIfNull() }

        private fun doCompileIfNull() {
            if (compiledFile == null) {
                doCompileIfChanged()
            }
        }

        fun compileIfChanged() = parseIfChanged().apply { doCompileIfChanged() }

        fun compile() = parse().apply { doCompile() }

        private fun doCompile() {
            LOG.debug("Compiling {}", path?.fileName)

            val (context, container) = cp.compiler.compileKtFile(parsed!!, allIncludingThis(), kind)
            parseDataWriteLock.withLock {
                compiledContext = context
                compiledContainer = container
                compiledFile = parsed
            }
        }

        private fun doCompileIfChanged() {
            if (parsed?.text != compiledFile?.text) {
                doCompile()
            }
        }

        fun prepareCompiledFile(): CompiledFile =
                parseIfChanged().apply { compileIfNull() }.let { doPrepareCompiledFile() }

        private fun doPrepareCompiledFile(): CompiledFile =
                CompiledFile(content, compiledFile!!, compiledContext!!, compiledContainer!!, allIncludingThis(), cp, isScript, kind)

        private fun allIncludingThis(): Collection<KtFile> = parseIfChanged().let {
            if (isTemporary) (all().asSequence() + sequenceOf(parsed!!)).toList()
            else all()
        }
    }

    private fun sourceFile(uri: URI): SourceFile {
        if (uri !in files) {
            // Fallback solution, usually *all* source files
            // should be added/opened through SourceFiles
            LOG.warn("Requested source file {} is not on source path, this is most likely a bug. Adding it now temporarily...", describeURI(uri))
            put(uri, contentProvider.contentOf(uri), null, temporary = true)
        }
        return files[uri]!!
    }

    fun put(uri: URI, content: String, language: Language?, temporary: Boolean = false) {
        assert(!content.contains('\r'))

        if (temporary) {
            LOG.info("Adding temporary source file {} to source path", describeURI(uri))
        }

        if (uri in files) {
            sourceFile(uri).put(content)
        } else {
            files[uri] = SourceFile(uri, content, language = language, isTemporary = temporary)
        }
    }

    fun deleteIfTemporary(uri: URI): Boolean =
        if (sourceFile(uri).isTemporary) {
            LOG.info("Removing temporary source file {} from source path", describeURI(uri))
            delete(uri)
            true
        } else {
            false
        }

    fun delete(uri: URI) {
        files.remove(uri)
    }

    /**
     * Get the latest content of a file
     */
    fun content(uri: URI): String = sourceFile(uri).content

    fun parsedFile(uri: URI): KtFile = sourceFile(uri).apply { parseIfChanged() }.parsed!!

    /**
     * Compile the latest version of a file
     */
    fun currentVersion(uri: URI): CompiledFile =
            sourceFile(uri).apply { compileIfChanged() }.prepareCompiledFile()

    /**
     * Return whatever is the most-recent already-compiled version of `file`
     */
    fun latestCompiledVersion(uri: URI): CompiledFile =
            sourceFile(uri).prepareCompiledFile()

    /**
     * Compile changed files
     */
    fun compileFiles(all: Collection<URI>): BindingContext {
        // Figure out what has changed
        val sources = all.map { files[it]!! }
        val allChanged = sources.filter { it.content != it.compiledFile?.text }
        val (changedBuildScripts, changedSources) = allChanged.partition { it.kind == CompilationKind.BUILD_SCRIPT }

        // Compile changed files
        fun compileAndUpdate(changed: List<SourceFile>, kind: CompilationKind): BindingContext? {
            if (changed.isEmpty()) return null
            val parse = changed.associateWith { it.apply { parseIfChanged() }.parsed!! }
            val allFiles = all()
            beforeCompileCallback.invoke()
            val (context, container) = cp.compiler.compileKtFiles(parse.values, allFiles, kind)

            // Update cache
            for ((f, parsed) in parse) {
                parseDataWriteLock.withLock {
                    if (f.parsed == parsed) {
                        //only updated if the parsed file didn't change:
                        f.compiledFile = parsed
                        f.compiledContext = context
                        f.compiledContainer = container
                    }
                }
            }

            return context
        }

        val buildScriptsContext = compileAndUpdate(changedBuildScripts, CompilationKind.BUILD_SCRIPT)
        val sourcesContext = compileAndUpdate(changedSources, CompilationKind.DEFAULT)

        // Combine with past compilations
        val same = sources - allChanged
        val combined = listOf(buildScriptsContext, sourcesContext).filterNotNull() + same.map { it.compiledContext!! }

        return CompositeBindingContext.create(combined)
    }

    /**
     * Recompiles all source files that are initialized.
     */
    fun refresh() {
        val initialized = files.values.any { it.parsed != null }
        if (initialized) {
            LOG.info("Refreshing source path")
            files.values.forEach { it.clean() }
            files.values.forEach { it.compile() }
        }
    }

    /**
     * Get parsed trees for all .kt files on source path
     */
    fun all(includeHidden: Boolean = false): Collection<KtFile> =
            files.values
                .filter { includeHidden || !it.isTemporary }
                .map { it.apply { parseIfChanged() }.parsed!! }
}

package org.javacs.kt

import java.io.InputStream
import java.io.OutputStream
import java.net.Socket
import java.net.ServerSocket

/**
 * Starts a TCP server socket. Blocks until the first
 * client has connected, then returns a pair of IO streams.
 */
fun tcpStartServer(port: Int): Pair<InputStream, OutputStream> = ServerSocket(port)
    .also { LOG.info("Waiting for client on port {}...", port) }
    .accept()
    .let { Pair(it.inputStream, it.outputStream) }

/**
 * Starts a TCP client socket and connects to the client at
 * the specified address, then returns a pair of IO streams.
 */
fun tcpConnectToClient(host: String, port: Int): Pair<InputStream, OutputStream> =
    run { LOG.info("Connecting to client at {}:{}...", host, port) }
    .let { Socket(host, port) }
    .let { Pair(it.inputStream, it.outputStream) }

package org.javacs.kt

import java.net.URI
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.nio.file.Paths
import org.javacs.kt.externalsources.JarClassContentProvider
import org.javacs.kt.externalsources.toKlsURI
import org.javacs.kt.util.KotlinLSException

/**
 * Fetches the content of Kotlin files identified by a URI.
 */
class URIContentProvider(
    val jarClassContentProvider: JarClassContentProvider
) {
    fun contentOf(uri: URI): String = when (uri.scheme) {
        "file" -> Paths.get(uri).toFile().readText()
        "kls" -> uri.toKlsURI()?.let { jarClassContentProvider.contentOf(it).second }
            ?: throw KotlinLSException("Could not find ${uri}")
        else -> throw KotlinLSException("Unrecognized scheme ${uri.scheme}")
    }
}

package org.javacs.kt

import java.io.PrintWriter
import java.io.StringWriter
import java.util.*
import java.util.logging.Formatter
import java.util.logging.LogRecord
import java.util.logging.Handler
import java.util.logging.Level
import java.time.Instant
import org.javacs.kt.util.DelegatePrintStream

val LOG = Logger()

private class JULRedirector(private val downstream: Logger): Handler() {
    override fun publish(record: LogRecord) {
        when (record.level) {
            Level.SEVERE -> downstream.error(record.message)
            Level.WARNING -> downstream.warn(record.message)
            Level.INFO -> downstream.info(record.message)
            Level.CONFIG -> downstream.debug(record.message)
            Level.FINE -> downstream.trace(record.message)
            else -> downstream.deepTrace(record.message)
        }
        record.thrown?.let(downstream::printStackTrace)
    }

    override fun flush() {}

    override fun close() {}
}

enum class LogLevel(val value: Int) {
    NONE(100),
    ERROR(2),
    WARN(1),
    INFO(0),
    DEBUG(-1),
    TRACE(-2),
    DEEP_TRACE(-3),
    ALL(-100)
}

class LogMessage(
    val level: LogLevel,
    val message: String
) {
    val formatted: String
        get() = "[$level] $message"
}

class Logger {
    private var outBackend: ((LogMessage) -> Unit)? = null
    private var errBackend: ((LogMessage) -> Unit)? = null
    private val outQueue: Queue<LogMessage> = ArrayDeque()
    private val errQueue: Queue<LogMessage> = ArrayDeque()
    private val errStream = DelegatePrintStream { logError(LogMessage(LogLevel.ERROR, it.trimEnd())) }
    val outStream = DelegatePrintStream { log(LogMessage(LogLevel.INFO, it.trimEnd())) }

    private val newline = System.lineSeparator()
    val logTime = false
    var level = LogLevel.INFO

    fun logError(msg: LogMessage) {
        if (errBackend == null) {
            errQueue.offer(msg)
        } else {
            errBackend?.invoke(msg)
        }
    }

    fun log(msg: LogMessage) {
        if (outBackend == null) {
            outQueue.offer(msg)
        } else {
            outBackend?.invoke(msg)
        }
    }

    private fun logWithPlaceholdersAt(msgLevel: LogLevel, msg: String, placeholders: Array<out Any?>) {
        if (level.value <= msgLevel.value) {
            log(LogMessage(msgLevel, format(insertPlaceholders(msg, placeholders))))
        }
    }

    inline fun logWithLambdaAt(msgLevel: LogLevel, msg: () -> String) {
        if (level.value <= msgLevel.value) {
            log(LogMessage(msgLevel, msg()))
        }
    }

    fun printStackTrace(throwable: Throwable) = throwable.printStackTrace(errStream)

    // Convenience logging methods using the traditional placeholder syntax

    fun error(msg: String, vararg placeholders: Any?) = logWithPlaceholdersAt(LogLevel.ERROR, msg, placeholders)

    fun warn(msg: String, vararg placeholders: Any?) = logWithPlaceholdersAt(LogLevel.WARN, msg, placeholders)

    fun info(msg: String, vararg placeholders: Any?) = logWithPlaceholdersAt(LogLevel.INFO, msg, placeholders)

    fun debug(msg: String, vararg placeholders: Any?) = logWithPlaceholdersAt(LogLevel.DEBUG, msg, placeholders)

    fun trace(msg: String, vararg placeholders: Any?) = logWithPlaceholdersAt(LogLevel.TRACE, msg, placeholders)

    fun deepTrace(msg: String, vararg placeholders: Any?) = logWithPlaceholdersAt(LogLevel.DEEP_TRACE, msg, placeholders)

    // Convenience logging methods using inlined lambdas

    inline fun error(msg: () -> String) = logWithLambdaAt(LogLevel.ERROR, msg)

    inline fun warn(msg: () -> String) = logWithLambdaAt(LogLevel.WARN, msg)

    inline fun info(msg: () -> String) = logWithLambdaAt(LogLevel.INFO, msg)

    inline fun debug(msg: () -> String) = logWithLambdaAt(LogLevel.DEBUG, msg)

    inline fun trace(msg: () -> String) = logWithLambdaAt(LogLevel.TRACE, msg)

    inline fun deepTrace(msg: () -> String) = logWithLambdaAt(LogLevel.DEEP_TRACE, msg)

    fun connectJULFrontend() {
        val rootLogger = java.util.logging.Logger.getLogger("")
        rootLogger.addHandler(JULRedirector(this))
    }

    fun connectOutputBackend(outBackend: (LogMessage) -> Unit) {
        this.outBackend = outBackend
        flushOutQueue()
    }

    fun connectErrorBackend(errBackend: (LogMessage) -> Unit) {
        this.errBackend = errBackend
        flushErrQueue()
    }

    fun connectStdioBackend() {
        connectOutputBackend { println(it.formatted) }
        connectOutputBackend { System.err.println(it.formatted) }
    }

    private fun insertPlaceholders(msg: String, placeholders: Array<out Any?>): String {
        val msgLength = msg.length
        val lastIndex = msgLength - 1
        var charIndex = 0
        var placeholderIndex = 0
        var result = StringBuilder()

        while (charIndex < msgLength) {
            val currentChar = msg.get(charIndex)
            val nextChar = if (charIndex != lastIndex) msg.get(charIndex + 1) else '?'
            if ((placeholderIndex < placeholders.size) && (currentChar == '{') && (nextChar == '}')) {
                result.append(placeholders[placeholderIndex] ?: "null")
                placeholderIndex += 1
                charIndex += 2
            } else {
                result.append(currentChar)
                charIndex += 1
            }
        }

        return result.toString()
    }

    private fun flushOutQueue() {
        while (outQueue.isNotEmpty()) {
            outBackend?.invoke(outQueue.poll())
        }
    }

    private fun flushErrQueue() {
        while (errQueue.isNotEmpty()) {
            errBackend?.invoke(errQueue.poll())
        }
    }

    private fun format(msg: String): String {
        val time = if (logTime) "${Instant.now()} " else ""
        var thread = Thread.currentThread().name

        return time + shortenOrPad(thread, 10) + msg.trimEnd()
    }

    private fun shortenOrPad(str: String, length: Int): String =
            if (str.length <= length) {
                str.padEnd(length, ' ')
            } else {
                ".." + str.substring(str.length - length + 2)
            }
}

package org.javacs.kt

import org.javacs.kt.util.filePath
import java.io.File
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Paths

// TODO: Read exclusions from gitignore/settings.json/... instead of
// hardcoding them
class SourceExclusions(private val workspaceRoots: Collection<Path>) {
	private val excludedPatterns = listOf(".*", "bin", "build", "node_modules", "target").map { FileSystems.getDefault().getPathMatcher("glob:$it") }

    constructor(workspaceRoot: Path) : this(listOf(workspaceRoot)) {}

    /** Finds all non-excluded files recursively. */
    fun walkIncluded(): Sequence<Path> = workspaceRoots.asSequence().flatMap { root ->
        root.toFile()
            .walk()
            .onEnter { isPathIncluded(it.toPath()) }
            .map { it.toPath() }
    }

    /** Tests whether the given URI is not excluded. */
    fun isURIIncluded(uri: URI) = uri.filePath?.let(this::isPathIncluded) ?: false

    /** Tests whether the given path is not excluded. */
    fun isPathIncluded(file: Path): Boolean = workspaceRoots.any { file.startsWith(it) }
        && excludedPatterns.none { pattern ->
            workspaceRoots
                .mapNotNull { if (file.startsWith(it)) it.relativize(file) else null }
                .flatMap { it } // Extract path segments
                .any(pattern::matches)
        }
}

package org.javacs.kt.commands

const val JAVA_TO_KOTLIN_COMMAND = "convertJavaToKotlin"
val ALL_COMMANDS = listOf(
	JAVA_TO_KOTLIN_COMMAND
)

package org.javacs.kt.completion

import com.google.common.cache.CacheBuilder
import org.eclipse.lsp4j.CompletionItem
import org.eclipse.lsp4j.CompletionItemKind
import org.eclipse.lsp4j.CompletionList
import org.javacs.kt.CompiledFile
import org.javacs.kt.LOG
import org.javacs.kt.CompletionConfiguration
import org.javacs.kt.util.findParent
import org.javacs.kt.util.noResult
import org.javacs.kt.util.toPath
import org.javacs.kt.util.onEachIndexed
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.JavaMethod
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.lexer.KtKeywordToken
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion
import org.jetbrains.kotlin.resolve.scopes.HierarchicalScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import java.util.concurrent.TimeUnit

private const val MAX_COMPLETION_ITEMS = 50

/** Finds completions at the specified position. */
fun completions(file: CompiledFile, cursor: Int, config: CompletionConfiguration): CompletionList {
    val partial = findPartialIdentifier(file, cursor)
    LOG.debug("Looking for completions that match '{}'", partial)

    val items = elementCompletionItems(file, cursor, config, partial).ifEmpty { keywordCompletionItems(partial) }
    val itemList = items
        .take(MAX_COMPLETION_ITEMS)
        .toList()
        .onEachIndexed { i, item -> item.sortText = i.toString().padStart(2, '0') }
    val isIncomplete = (itemList.size == MAX_COMPLETION_ITEMS)

    return CompletionList(isIncomplete, itemList)
}

/** Finds keyword completions starting with the given partial identifier. */
private fun keywordCompletionItems(partial: String): Sequence<CompletionItem> =
    (KtTokens.SOFT_KEYWORDS.getTypes() + KtTokens.KEYWORDS.getTypes()).asSequence()
        .mapNotNull { (it as? KtKeywordToken)?.value }
        .filter { it.startsWith(partial) }
        .map { CompletionItem().apply {
            label = it
            kind = CompletionItemKind.Keyword
        } }

/** Finds completions based on the element around the user's cursor. */
private fun elementCompletionItems(file: CompiledFile, cursor: Int, config: CompletionConfiguration, partial: String): Sequence<CompletionItem> {
    val surroundingElement = completableElement(file, cursor) ?: return emptySequence()
    val completions = elementCompletions(file, cursor, surroundingElement)

    val nameFilter = matchesPartialIdentifier(partial)
    val matchesName = completions.filter(nameFilter)
    val visible = matchesName.filter(isVisible(file, cursor))

    return visible.map { completionItem(it, surroundingElement, file, config) }
}

private val callPattern = Regex("(.*)\\((?:\\$\\d+)?\\)(?:\\$0)?")
private val methodSignature = Regex("""(?:fun|constructor) (?:<(?:[a-zA-Z\?\!\: ]+)(?:, [A-Z])*> )?([a-zA-Z]+\(.*\))""")

private fun completionItem(d: DeclarationDescriptor, surroundingElement: KtElement, file: CompiledFile, config: CompletionConfiguration): CompletionItem {
    val renderWithSnippets = config.snippets.enabled
        && surroundingElement !is KtCallableReferenceExpression
        && surroundingElement !is KtImportDirective
    val result = d.accept(RenderCompletionItem(renderWithSnippets), null)

    result.label = methodSignature.find(result.detail)?.groupValues?.get(1) ?: result.label

    if (isNotStaticJavaMethod(d) && (isGetter(d) || isSetter(d))) {
        val name = extractPropertyName(d)

        result.detail += " (from ${result.label})"
        result.label = name
        result.insertText = name
        result.filterText = name
    }

    val matchCall = callPattern.matchEntire(result.insertText)
    if (file.lineAfter(surroundingElement.endOffset).startsWith("(") && matchCall != null) {
        result.insertText = matchCall.groups[1]!!.value
    }

    return result
}

private fun isNotStaticJavaMethod(
    descriptor: DeclarationDescriptor
): Boolean {
    val javaMethodDescriptor = descriptor as? JavaMethodDescriptor ?: return true
    val source = javaMethodDescriptor.source as? JavaSourceElement ?: return true
    val javaElement = source.javaElement
    return javaElement is JavaMethod && !javaElement.isStatic
}

private fun extractPropertyName(d: DeclarationDescriptor): String {
    val match = Regex("(get|set)?((?:(?:is)|[A-Z])\\w*)").matchEntire(d.name.identifier)!!
    val upper = match.groups[2]!!.value

    return upper[0].toLowerCase() + upper.substring(1)
}

private fun isGetter(d: DeclarationDescriptor): Boolean =
        d is CallableDescriptor &&
        !d.name.isSpecial &&
        d.name.identifier.matches(Regex("(get|is)[A-Z]\\w+")) &&
        d.valueParameters.isEmpty()

private fun isSetter(d: DeclarationDescriptor): Boolean =
        d is CallableDescriptor &&
        !d.name.isSpecial &&
        d.name.identifier.matches(Regex("set[A-Z]\\w+")) &&
        d.valueParameters.size == 1

private fun completableElement(file: CompiledFile, cursor: Int): KtElement? {
    val el = file.parseAtPoint(cursor - 1) ?: return null
            // import x.y.?
    return el.findParent<KtImportDirective>()
            // :?
            ?: el.parent as? KtTypeElement
            // .?
            ?: el as? KtQualifiedExpression
            ?: el.parent as? KtQualifiedExpression
            // something::?
            ?: el as? KtCallableReferenceExpression
            ?: el.parent as? KtCallableReferenceExpression
            // something.foo() with cursor in the method
            ?: el.parent?.parent as? KtQualifiedExpression
            // ?
            ?: el as? KtNameReferenceExpression
}

private fun elementCompletions(file: CompiledFile, cursor: Int, surroundingElement: KtElement): Sequence<DeclarationDescriptor> {
    return when (surroundingElement) {
        // import x.y.?
        is KtImportDirective -> {
            LOG.info("Completing import '{}'", surroundingElement.text)
            val module = file.container.get<ModuleDescriptor>()
            val match = Regex("import ((\\w+\\.)*)[\\w*]*").matchEntire(surroundingElement.text) ?: return doesntLookLikeImport(surroundingElement)
            val parentDot = if (match.groupValues[1].isNotBlank()) match.groupValues[1] else "."
            val parent = parentDot.substring(0, parentDot.length - 1)
            LOG.debug("Looking for members of package '{}'", parent)
            val parentPackage = module.getPackage(FqName.fromSegments(parent.split('.')))
            parentPackage.memberScope.getContributedDescriptors().asSequence()
        }
        // :?
        is KtTypeElement -> {
            // : Outer.?
            if (surroundingElement is KtUserType && surroundingElement.qualifier != null) {
                val referenceTarget = file.referenceAtPoint(surroundingElement.qualifier!!.startOffset)?.second
                if (referenceTarget is ClassDescriptor) {
                    LOG.info("Completing members of {}", referenceTarget.fqNameSafe)
                    return referenceTarget.unsubstitutedInnerClassesScope.getContributedDescriptors().asSequence()
                } else {
                    LOG.warn("No type reference in '{}'", surroundingElement.text)
                    return emptySequence()
                }
            } else {
                // : ?
                LOG.info("Completing type identifier '{}'", surroundingElement.text)
                val scope = file.scopeAtPoint(cursor) ?: return emptySequence()
                scopeChainTypes(scope)
            }
        }
        // .?
        is KtQualifiedExpression -> {
            LOG.info("Completing member expression '{}'", surroundingElement.text)
            completeMembers(file, cursor, surroundingElement.receiverExpression, surroundingElement is KtSafeQualifiedExpression)
        }
        is KtCallableReferenceExpression -> {
            // something::?
            if (surroundingElement.receiverExpression != null) {
                LOG.info("Completing method reference '{}'", surroundingElement.text)
                completeMembers(file, cursor, surroundingElement.receiverExpression!!)
            }
            // ::?
            else {
                LOG.info("Completing function reference '{}'", surroundingElement.text)
                val scope = file.scopeAtPoint(surroundingElement.startOffset) ?: return noResult("No scope at ${file.describePosition(cursor)}", emptySequence())
                identifiers(scope)
            }
        }
        // ?
        is KtNameReferenceExpression -> {
            LOG.info("Completing identifier '{}'", surroundingElement.text)
            val scope = file.scopeAtPoint(surroundingElement.startOffset) ?: return noResult("No scope at ${file.describePosition(cursor)}", emptySequence())
            identifiers(scope)
        }
        else -> {
            LOG.info("{} {} didn't look like a type, a member, or an identifier", surroundingElement::class.simpleName, surroundingElement.text)
            emptySequence()
        }
    }
}

private fun completeMembers(file: CompiledFile, cursor: Int, receiverExpr: KtExpression, unwrapNullable: Boolean = false): Sequence<DeclarationDescriptor> {
    // thingWithType.?
    var descriptors = emptySequence<DeclarationDescriptor>()
    file.scopeAtPoint(cursor)?.let { lexicalScope ->
        file.typeOfExpression(receiverExpr, lexicalScope)?.let { expressionType ->
            val receiverType = if (unwrapNullable) try {
                TypeUtils.makeNotNullable(expressionType)
            } catch (e: Exception) {
                LOG.printStackTrace(e)
                expressionType
            } else expressionType

            LOG.debug("Completing members of instance '{}'", receiverType)
            val members = receiverType.memberScope.getContributedDescriptors().asSequence()
            val extensions = extensionFunctions(lexicalScope).filter { isExtensionFor(receiverType, it) }
            descriptors = members + extensions

            if (!isCompanionOfEnum(receiverType)) {
                return descriptors
            }
        }
    }

    // JavaClass.?
    val referenceTarget = file.referenceAtPoint(receiverExpr.endOffset - 1)?.second
    if (referenceTarget is ClassDescriptor) {
        LOG.debug("Completing static members of '{}'", referenceTarget.fqNameSafe)
        val statics = referenceTarget.staticScope.getContributedDescriptors().asSequence()
        val classes = referenceTarget.unsubstitutedInnerClassesScope.getContributedDescriptors().asSequence()
        return descriptors + statics + classes
    }

    LOG.debug("Can't find member scope for {}", receiverExpr.text)
    return emptySequence()
}

private fun isCompanionOfEnum(kotlinType: KotlinType): Boolean {
    val classDescriptor = TypeUtils.getClassDescriptor(kotlinType)
    val isCompanion = DescriptorUtils.isCompanionObject(classDescriptor)
    if (!isCompanion) {
        return false
    }
    return DescriptorUtils.isEnumClass(classDescriptor?.containingDeclaration)
}

private fun findPartialIdentifier(file: CompiledFile, cursor: Int): String {
    val line = file.lineBefore(cursor)
    if (line.matches(Regex(".*\\."))) return ""
    else if (line.matches(Regex(".*\\.\\w+"))) return line.substringAfterLast(".")
    else return Regex("\\w+").findAll(line).lastOrNull()?.value ?: ""
}

fun memberOverloads(type: KotlinType, identifier: String): Sequence<CallableDescriptor> {
    val nameFilter = equalsIdentifier(identifier)

    return type.memberScope
            .getContributedDescriptors(Companion.CALLABLES).asSequence()
            .filterIsInstance<CallableDescriptor>()
            .filter(nameFilter)
}

private fun completeTypeMembers(type: KotlinType): Sequence<DeclarationDescriptor> =
    type.memberScope.getDescriptorsFiltered(TYPES_FILTER).asSequence()

private fun scopeChainTypes(scope: LexicalScope): Sequence<DeclarationDescriptor> =
        scope.parentsWithSelf.flatMap(::scopeTypes)

private val TYPES_FILTER = DescriptorKindFilter(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS_MASK or DescriptorKindFilter.TYPE_ALIASES_MASK)

private fun scopeTypes(scope: HierarchicalScope): Sequence<DeclarationDescriptor> =
        scope.getContributedDescriptors(TYPES_FILTER).asSequence()

fun identifierOverloads(scope: LexicalScope, identifier: String): Sequence<CallableDescriptor> {
    val nameFilter = equalsIdentifier(identifier)

    return identifiers(scope)
            .filterIsInstance<CallableDescriptor>()
            .filter(nameFilter)
}

private fun extensionFunctions(scope: LexicalScope): Sequence<CallableDescriptor> =
    scope.parentsWithSelf.flatMap(::scopeExtensionFunctions)

private fun scopeExtensionFunctions(scope: HierarchicalScope): Sequence<CallableDescriptor> =
    scope.getContributedDescriptors(DescriptorKindFilter.CALLABLES).asSequence()
            .filterIsInstance<CallableDescriptor>()
            .filter { it.isExtension }

private fun identifiers(scope: LexicalScope): Sequence<DeclarationDescriptor> =
    scope.parentsWithSelf
            .flatMap(::scopeIdentifiers)
            .flatMap(::explodeConstructors)

private fun scopeIdentifiers(scope: HierarchicalScope): Sequence<DeclarationDescriptor> {
    val locals = scope.getContributedDescriptors().asSequence()
    val members = implicitMembers(scope)

    return locals + members
}

private fun explodeConstructors(declaration: DeclarationDescriptor): Sequence<DeclarationDescriptor> {
    return when (declaration) {
        is ClassDescriptor ->
            declaration.constructors.asSequence() + declaration
        else ->
            sequenceOf(declaration)
    }
}

private fun implicitMembers(scope: HierarchicalScope): Sequence<DeclarationDescriptor> {
    if (scope !is LexicalScope) return emptySequence()
    val implicit = scope.implicitReceiver ?: return emptySequence()

    return implicit.type.memberScope.getContributedDescriptors().asSequence()
}

private fun equalsIdentifier(identifier: String): (DeclarationDescriptor) -> Boolean {
    return { name(it) == identifier }
}

private fun matchesPartialIdentifier(partialIdentifier: String): (DeclarationDescriptor) -> Boolean {
    return {
        containsCharactersInOrder(name(it), partialIdentifier, false)
    }
}

private fun name(d: DeclarationDescriptor): String {
    if (d is ConstructorDescriptor)
        return d.constructedClass.name.identifier
    else
        return d.name.identifier
}

fun containsCharactersInOrder(
        candidate: CharSequence, pattern: CharSequence, caseSensitive: Boolean): Boolean {
    var iCandidate = 0
    var iPattern = 0

    while (iCandidate < candidate.length && iPattern < pattern.length) {
        var patternChar = pattern[iPattern]
        var testChar = candidate[iCandidate]

        if (!caseSensitive) {
            patternChar = Character.toLowerCase(patternChar)
            testChar = Character.toLowerCase(testChar)
        }

        if (patternChar == testChar) {
            iPattern++
            iCandidate++
        } else iCandidate++
    }

    return iPattern == pattern.length
}

private fun isVisible(file: CompiledFile, cursor: Int): (DeclarationDescriptor) -> Boolean {
    val el = file.elementAtPoint(cursor) ?: return { true }
    val from = el.parentsWithSelf
                       .mapNotNull { file.compile[BindingContext.DECLARATION_TO_DESCRIPTOR, it] }
                       .firstOrNull() ?: return { true }
    fun check(target: DeclarationDescriptor): Boolean {
        val visible = isDeclarationVisible(target, from)

        if (!visible) logHidden(target, from)

        return visible
    }

    return ::check
}

// We can't use the implementations in Visibilities because they don't work with our type of incremental compilation
// Instead, we implement our own "liberal" visibility checker that defaults to visible when in doubt
private fun isDeclarationVisible(target: DeclarationDescriptor, from: DeclarationDescriptor): Boolean =
    target.parentsWithSelf
            .filterIsInstance<DeclarationDescriptorWithVisibility>()
            .none { isNotVisible(it, from) }

private fun isNotVisible(target: DeclarationDescriptorWithVisibility, from: DeclarationDescriptor): Boolean {
    when (target.visibility) {
        Visibilities.PRIVATE, Visibilities.PRIVATE_TO_THIS -> {
            if (DescriptorUtils.isTopLevelDeclaration(target))
                return !sameFile(target, from)
            else
                return !sameParent(target, from)
        }
        Visibilities.PROTECTED -> {
            return !subclassParent(target, from)
        }
        else -> return false
    }
}

private fun sameFile(target: DeclarationDescriptor, from: DeclarationDescriptor): Boolean {
    val targetFile = DescriptorUtils.getContainingSourceFile(target)
    val fromFile = DescriptorUtils.getContainingSourceFile(from)

    if (targetFile == SourceFile.NO_SOURCE_FILE || fromFile == SourceFile.NO_SOURCE_FILE) return true
    else return targetFile.name == fromFile.name
}

private fun sameParent(target: DeclarationDescriptor, from: DeclarationDescriptor): Boolean {
    val targetParent = target.parentsWithSelf.mapNotNull(::isParentClass).firstOrNull() ?: return true
    val fromParents = from.parentsWithSelf.mapNotNull(::isParentClass).toList()

    return fromParents.any { it.fqNameSafe == targetParent.fqNameSafe }
}

private fun subclassParent(target: DeclarationDescriptor, from: DeclarationDescriptor): Boolean {
    val targetParent = target.parentsWithSelf.mapNotNull(::isParentClass).firstOrNull() ?: return true
    val fromParents = from.parentsWithSelf.mapNotNull(::isParentClass).toList()

    if (fromParents.isEmpty()) return true
    else return fromParents.any { DescriptorUtils.isSubclass(it, targetParent) }
}

private fun isParentClass(declaration: DeclarationDescriptor): ClassDescriptor? =
    if (declaration is ClassDescriptor && !DescriptorUtils.isCompanionObject(declaration))
        declaration
    else null

private fun isExtensionFor(type: KotlinType, extensionFunction: CallableDescriptor): Boolean {
    val receiverType = extensionFunction.extensionReceiverParameter?.type?.replaceArgumentsWithStarProjections() ?: return false
    return KotlinTypeChecker.DEFAULT.isSubtypeOf(type, receiverType)
        || (TypeUtils.getTypeParameterDescriptorOrNull(receiverType)?.isGenericExtensionFor(type) ?: false)
}

private fun TypeParameterDescriptor.isGenericExtensionFor(type: KotlinType): Boolean =
    upperBounds.all { KotlinTypeChecker.DEFAULT.isSubtypeOf(type, it) }

private val loggedHidden = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build<Pair<Name, Name>, Unit>()

private fun logHidden(target: DeclarationDescriptor, from: DeclarationDescriptor) {
    val key = Pair(from.name, target.name)

    loggedHidden.get(key, { doLogHidden(target, from )})
}

private fun doLogHidden(target: DeclarationDescriptor, from: DeclarationDescriptor) {
    LOG.debug("Hiding {} because it's not visible from {}", describeDeclaration(target), describeDeclaration(from))
}

private fun describeDeclaration(declaration: DeclarationDescriptor): String {
    val file = declaration.findPsi()?.containingFile?.toPath()?.fileName?.toString() ?: "<unknown-file>"
    val container = declaration.containingDeclaration?.name?.toString() ?: "<top-level>"

    return "($file $container.${declaration.name})"
}

private fun doesntLookLikeImport(importDirective: KtImportDirective): Sequence<DeclarationDescriptor> {
    LOG.debug("{} doesn't look like import a.b...", importDirective.text)

    return emptySequence()
}

private fun empty(message: String): CompletionList {
    LOG.debug(message)

    return CompletionList(true, emptyList())
}

package org.javacs.kt.completion

import org.eclipse.lsp4j.CompletionItem
import org.eclipse.lsp4j.CompletionItemKind.*
import org.eclipse.lsp4j.CompletionItemKind.Function
import org.eclipse.lsp4j.InsertTextFormat.PlainText
import org.eclipse.lsp4j.InsertTextFormat.Snippet
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.UnresolvedType

val DECL_RENDERER = DescriptorRenderer.withOptions {
    withDefinedIn = false
    modifiers = emptySet()
    classifierNamePolicy = ClassifierNamePolicy.SHORT
    parameterNameRenderingPolicy = ParameterNameRenderingPolicy.ONLY_NON_SYNTHESIZED
    typeNormalizer = {
        when (it) {
            is UnresolvedType ->  ErrorUtils.createErrorTypeWithCustomDebugName(it.presentableName)
            else -> it
        }
    }
}

private val GOOD_IDENTIFIER = Regex("[a-zA-Z]\\w*")

class RenderCompletionItem(val snippetsEnabled: Boolean) : DeclarationDescriptorVisitor<CompletionItem, Unit> {
    private val result = CompletionItem()

    private val functionInsertFormat
        get() = if (snippetsEnabled) Snippet else PlainText

    private fun escape(id: String): String =
        if (id.matches(GOOD_IDENTIFIER)) id
        else "`$id`"

    private fun setDefaults(declaration: DeclarationDescriptor) {
        result.label = declaration.label()
        result.filterText = declaration.label()
        result.insertText = escape(declaration.label()!!)
        result.insertTextFormat = PlainText
        result.detail = DECL_RENDERER.render(declaration)
    }

    override fun visitPropertySetterDescriptor(desc: PropertySetterDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Field

        return result
    }

    override fun visitConstructorDescriptor(desc: ConstructorDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Constructor
        result.insertText = functionInsertText(desc)
        result.insertTextFormat = functionInsertFormat

        return result
    }

    override fun visitReceiverParameterDescriptor(desc: ReceiverParameterDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Variable

        return result
    }

    override fun visitPackageViewDescriptor(desc: PackageViewDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Module

        return result
    }

    override fun visitFunctionDescriptor(desc: FunctionDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Function
        result.insertText = functionInsertText(desc)
        result.insertTextFormat = functionInsertFormat

        return result
    }

    private fun functionInsertText(desc: FunctionDescriptor): String {
        val name = escape(desc.label()!!)

        return if (snippetsEnabled) {
            val parameters = desc.valueParameters
            val hasTrailingLambda = parameters.lastOrNull()?.type?.isFunctionType ?: false

            if (hasTrailingLambda) {
                val parenthesizedParams = parameters.dropLast(1).ifEmpty { null }?.let { "(${valueParametersSnippet(it)})" } ?: ""
                "$name$parenthesizedParams { \${${parameters.size}:${parameters.last().name}} }"
            } else {
                "$name(${valueParametersSnippet(parameters)})"
            }
        } else {
            name
        }
    }

    private fun valueParametersSnippet(parameters: List<ValueParameterDescriptor>) = parameters
        .asSequence()
        .filterNot { it.declaresDefaultValue() }
        .mapIndexed { index, vpd -> "\${${index + 1}:${vpd.name}}" }
        .joinToString()

    override fun visitModuleDeclaration(desc: ModuleDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Module

        return result
    }

    override fun visitClassDescriptor(desc: ClassDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Class

        return result
    }

    override fun visitPackageFragmentDescriptor(desc: PackageFragmentDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Module

        return result
    }

    override fun visitValueParameterDescriptor(desc: ValueParameterDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Variable

        return result
    }

    override fun visitTypeParameterDescriptor(desc: TypeParameterDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Variable

        return result
    }

    override fun visitScriptDescriptor(desc: ScriptDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Module

        return result
    }

    override fun visitTypeAliasDescriptor(desc: TypeAliasDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Variable

        return result
    }

    override fun visitPropertyGetterDescriptor(desc: PropertyGetterDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Field

        return result
    }

    override fun visitVariableDescriptor(desc: VariableDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Variable

        return result
    }

    override fun visitPropertyDescriptor(desc: PropertyDescriptor, nothing: Unit?): CompletionItem {
        setDefaults(desc)

        result.kind = Field

        return result
    }
}

private fun DeclarationDescriptor.label(): String? {
    return when {
        this is ConstructorDescriptor -> this.containingDeclaration.name.identifier
        this.name.isSpecial -> null
        else -> this.name.identifier
    }
}

package org.javacs.kt.definition

import org.eclipse.lsp4j.Location
import org.eclipse.lsp4j.Range
import java.net.URI
import java.nio.file.Path
import java.nio.file.Files
import org.javacs.kt.CompiledFile
import org.javacs.kt.LOG
import org.javacs.kt.ExternalSourcesConfiguration
import org.javacs.kt.externalsources.JarClassContentProvider
import org.javacs.kt.externalsources.toKlsURI
import org.javacs.kt.externalsources.KlsURI
import org.javacs.kt.position.location
import org.javacs.kt.position.isZero
import org.javacs.kt.position.position
import org.javacs.kt.util.partitionAroundLast
import org.javacs.kt.util.TemporaryDirectory
import org.javacs.kt.util.parseURI
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor

private val cachedTempFiles = mutableMapOf<KlsURI, Path>()
private val definitionPattern = Regex("(?:class|interface|object|fun)\\s+(\\w+)")

fun goToDefinition(
    file: CompiledFile,
    cursor: Int,
    jarClassContentProvider: JarClassContentProvider,
    tempDir: TemporaryDirectory,
    config: ExternalSourcesConfiguration
): Location? {
    val (_, target) = file.referenceAtPoint(cursor) ?: return null

    LOG.info("Found declaration descriptor {}", target)
    var destination = location(target)
    val psi = target.findPsi()

    if (psi is KtNamedDeclaration) {
        destination = psi.nameIdentifier?.let(::location) ?: destination
    }

    if (destination != null) {
        val rawClassURI = destination.uri

        if (isInsideJar(rawClassURI)) {
            parseURI(rawClassURI).toKlsURI()?.let { klsURI ->
                val (klsSourceURI, content) = jarClassContentProvider.contentOf(klsURI)

                if (config.useKlsScheme) {
                    // Defer decompilation until a jarClassContents request is sent
                    destination.uri = klsSourceURI.toString()
                } else {
                    // Return the path to a temporary file
                    // since the client has not opted into
                    // or does not support KLS URIs
                    val tmpFile = cachedTempFiles[klsSourceURI] ?: run {
                        val (name, extension) = klsSourceURI.fileName.partitionAroundLast(".")
                        tempDir.createTempFile(name, extension)
                            .also {
                                it.toFile().writeText(content)
                                cachedTempFiles[klsSourceURI] = it
                            }
                    }

                    destination.uri = tmpFile.toUri().toString()
                }

                if (destination.range.isZero) {
                    // Try to find the definition inside the source directly
                    val name = when (target) {
                        is ConstructorDescriptor -> target.constructedClass.name.toString()
                        else -> target.name.toString()
                    }
                    definitionPattern.findAll(content)
                        .map { it.groups[1]!! }
                        .find { it.value == name }
                        ?.let { it.range }
                        ?.let { destination.range = Range(position(content, it.first), position(content, it.last)) }
                }
            }
        }
    }

    return destination
}

private fun isInsideJar(uri: String) = uri.contains(".jar!")

package org.javacs.kt.diagnostic

import org.eclipse.lsp4j.DiagnosticSeverity
import org.eclipse.lsp4j.Diagnostic as LangServerDiagnostic
import org.javacs.kt.position.range
import org.javacs.kt.util.toPath
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.Diagnostic as KotlinDiagnostic
import java.nio.file.Path
import java.net.URI

fun convertDiagnostic(diagnostic: KotlinDiagnostic): List<Pair<URI, LangServerDiagnostic>> {
    val uri = diagnostic.psiFile.toPath().toUri()
    val content = diagnostic.psiFile.text

    return diagnostic.textRanges.map {
        val d = LangServerDiagnostic(
                range(content, it),
                message(diagnostic),
                severity(diagnostic.severity),
                "kotlin",
                code(diagnostic))
        Pair(uri, d)
    }
}

private fun code(diagnostic: KotlinDiagnostic) =
        diagnostic.factory.name

private fun message(diagnostic: KotlinDiagnostic) =
        DefaultErrorMessages.render(diagnostic)

private fun severity(severity: Severity): DiagnosticSeverity =
        when (severity) {
            Severity.INFO -> DiagnosticSeverity.Information
            Severity.ERROR -> DiagnosticSeverity.Error
            Severity.WARNING -> DiagnosticSeverity.Warning
        }

package org.javacs.kt.docs

import org.javacs.kt.util.preOrderTraversal
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils

fun findDoc(declaration: DeclarationDescriptorWithSource): KDocTag? {
    val source = DescriptorToSourceUtils.descriptorToDeclaration(declaration)?.navigationElement

    return when (source) {
        is KtParameter -> {
            var container = source.parents.filterIsInstance<KtDeclaration>().firstOrNull() ?: return null
            if (container is KtPrimaryConstructor)
                container = container.parents.filterIsInstance<KtDeclaration>().firstOrNull() ?: return null
            val doc = container.docComment ?: return null
            val descendants = doc.preOrderTraversal()
            val tags = descendants.filterIsInstance<KDocTag>()
            val params = tags.filter { it.knownTag == KDocKnownTag.PARAM }
            val matchName = params.filter { it.getSubjectName() == declaration.name.toString() }

            return matchName.firstOrNull()
        }
        is KtPrimaryConstructor -> {
            val container = source.parents.filterIsInstance<KtDeclaration>().firstOrNull() ?: return null
            val doc = container.docComment ?: return null
            doc.findSectionByTag(KDocKnownTag.CONSTRUCTOR) ?: doc.getDefaultSection()
        }
        is KtDeclaration -> {
            val doc = source.docComment ?: return null
            doc.getDefaultSection()
        }
        else -> null
    }
}

package org.javacs.kt.externalsources

import java.nio.file.Path

interface Decompiler : SourceJarProvider {
	fun decompileClass(compiledClass: Path): Path

	fun decompileJar(compiledJar: Path): Path

	override fun fetchSourceJar(compiledJar: Path) = decompileJar(compiledJar)
}

package org.javacs.kt.externalsources

import java.nio.file.Path
import java.nio.file.Files
import org.javacs.kt.util.KotlinLSException
import org.javacs.kt.util.replaceExtensionWith
import org.javacs.kt.util.withCustomStdout
import org.javacs.kt.LOG
import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler

class FernflowerDecompiler : Decompiler {
	private val outputDir by lazy(::createOutputDirectory)

	override fun decompileClass(compiledClass: Path) = decompile(compiledClass, ".java")

	override fun decompileJar(compiledJar: Path) = decompile(compiledJar, ".jar")

	fun decompile(compiledClassOrJar: Path, newFileExtension: String): Path {
		invokeDecompiler(compiledClassOrJar, outputDir)
		val srcOutName = compiledClassOrJar.fileName.replaceExtensionWith(newFileExtension)
		val srcOutPath = outputDir.resolve(srcOutName)

		if (!Files.exists(srcOutPath)) {
			throw KotlinLSException("Could not decompile ${compiledClassOrJar.fileName}: Fernflower did not generate sources at ${srcOutPath.fileName}")
		}

		return srcOutPath
	}

	private fun invokeDecompiler(input: Path, output: Path) {
		LOG.info("Decompiling {} using Fernflower...", input.fileName)
		withCustomStdout(LOG.outStream) {
			ConsoleDecompiler.main(arrayOf(input.toString(), output.toString()))
		}
	}

	private fun createOutputDirectory(): Path {
		val out = Files.createTempDirectory("fernflowerOut")
		Runtime.getRuntime().addShutdownHook(Thread {
			// Deletes the output directory and all contained (decompiled)
			// JARs when the JVM terminates
			out.toFile().deleteRecursively()
		})
		return out
	}
}

package org.javacs.kt.externalsources

import org.javacs.kt.CompilerClassPath
import org.javacs.kt.ExternalSourcesConfiguration
import org.javacs.kt.LOG
import org.javacs.kt.util.describeURI
import org.javacs.kt.util.KotlinLSException
import org.javacs.kt.util.TemporaryDirectory
import org.javacs.kt.j2k.convertJavaToKotlin
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.nio.file.Files
import java.util.LinkedHashMap

/**
 * Provides the source code for classes located inside
 * compiled or source JARs.
 */
class JarClassContentProvider(
    private val config: ExternalSourcesConfiguration,
    private val cp: CompilerClassPath,
    private val tempDir: TemporaryDirectory,
    private val decompiler: Decompiler = FernflowerDecompiler()
) {
    /** Maps recently used (source-)KLS-URIs to their source contents (e.g. decompiled code). */
    private val cachedContents = object : LinkedHashMap<String, String>() {
        override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, String>) = size > 5
    }

    /**
     * Fetches the contents of a compiled class/source file in a JAR
     * and another URI which can be used to refer to these extracted
     * contents.
     */
    public fun contentOf(uri: KlsURI): Pair<KlsURI, String> {
        val sourceURI = uri.withFileExtension(if (config.autoConvertToKotlin) "kt" else "java")
        val key = sourceURI.toString()
        val contents: String = cachedContents[key] ?: run {
            LOG.info("Finding contents of {}", describeURI(uri.uri))
            tryReadContentOf(uri)
                ?: tryReadContentOf(uri.withFileExtension("class"))
                ?: tryReadContentOf(uri.withFileExtension("java"))
                ?: tryReadContentOf(uri.withFileExtension("kt"))
                ?: throw KotlinLSException("Could not find $uri")
        }.also { cachedContents[key] = it }
        return Pair(sourceURI, contents)
    }

    private fun convertToKotlinIfNeeded(javaCode: String): String = if (config.autoConvertToKotlin) {
        convertJavaToKotlin(javaCode, cp.compiler)
    } else {
        javaCode
    }

    private fun tryReadContentOf(uri: KlsURI): String? = try {
        when (uri.fileExtension) {
            "class" -> uri.extractToTemporaryFile(tempDir)
                .let(decompiler::decompileClass)
                .let { Files.newInputStream(it) }
                .bufferedReader()
                .use(BufferedReader::readText)
                .let(this::convertToKotlinIfNeeded)
            "java" -> convertToKotlinIfNeeded(uri.readContents())
            else -> uri.readContents() // e.g. for Kotlin source files
        }
    } catch (e: FileNotFoundException) { null }
}

package org.javacs.kt.externalsources

import org.javacs.kt.j2k.convertJavaToKotlin
import org.javacs.kt.util.partitionAroundLast
import org.javacs.kt.util.TemporaryDirectory
import java.net.URI
import java.net.URL
import java.net.JarURLConnection
import java.io.BufferedReader
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.Files

fun URI.toKlsURI(): KlsURI? = when (scheme) {
    "kls" -> KlsURI(URI("kls:$schemeSpecificPart"))
    "file" -> KlsURI(URI("kls:$this"))
    else -> null
}

/**
 * Identifies a class or source file inside a JAR archive using a Uniform
 * Resource Identifier (URI) with a "kls" (Kotlin language server) scheme.
 * The URI should be structured as follows:
 *
 *

 * kls:file:///path/to/jarFile.jar!/path/to/jvmClass.class
 *

 *
 * Other file extensions for classes (such as .kt and .java) are supported too, in
 * which case the file will directly be used without invoking the decompiler.
 */
data class KlsURI(val uri: URI) {
    val fileName: String
        get() = uri.toString().substringAfterLast("/")
    val fileExtension: String?
        get() = fileName
            .split(".")
            .takeIf { it.size > 1 }
            ?.lastOrNull()
    val isCompiled: Boolean
        get() = fileExtension == "class"

    fun withFileExtension(newExtension: String): KlsURI {
        val (parentURI, fileName) = uri.toString().partitionAroundLast("/")
        val newURI = "$parentURI${fileName.split(".").first()}.$newExtension"
        return KlsURI(URI(newURI))
    }

    private fun toFileURI(): URI = URI(uri.schemeSpecificPart)

    private fun toJarURL(): URL = URL("jar:${uri.schemeSpecificPart}")

    private fun openJarURLConnection() = toJarURL().openConnection() as JarURLConnection

    private inline fun <T> withJarURLConnection(action: (JarURLConnection) -> T): T {
        val connection = openJarURLConnection()
        val result = action(connection)
        // https://bugs.openjdk.java.net/browse/JDK-8080094
        if (connection.useCaches) {
            connection.jarFile.close()
        }
        return result
    }

    fun readContents(): String = withJarURLConnection {
        it.jarFile
            .getInputStream(it.jarEntry)
            .bufferedReader()
            .use(BufferedReader::readText)
    }

    fun extractToTemporaryFile(dir: TemporaryDirectory): Path = withJarURLConnection {
        val name = it.jarEntry.name.substringAfterLast("/").split(".")
        val tmpFile = dir.createTempFile(name[0], ".${name[1]}")

        it.jarFile
            .getInputStream(it.jarEntry)
            .use { input -> Files.newOutputStream(tmpFile).use { input.copyTo(it) } }

        tmpFile
    }

    override fun toString(): String = uri.toString()
}

package org.javacs.kt.externalsources

import java.nio.file.Path

interface SourceJarProvider {
    fun fetchSourceJar(compiledJar: Path): Path
}

package org.javacs.kt.formatting

import org.eclipse.lsp4j.FormattingOptions
import com.pinterest.ktlint.core.KtLint
import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider

fun formatKotlinCode(
	code: String,
	lintCallback: (error: LintError, corrected: Boolean) -> Unit = { _, _ -> Unit },
    isScript: Boolean = false,
    options: FormattingOptions = FormattingOptions(4, true)
): String = KtLint.format(
	KtLint.Params(
		text = code,
		script = isScript,
        ruleSets = listOf(StandardRuleSetProvider().get()),
        userData = mapOf(
            "indent_size" to options.tabSize.toString(),
            "indent_style" to if (options.isInsertSpaces) "space" else "tab"
        ),
		cb = lintCallback
	)
)

package org.javacs.kt.hover

import org.eclipse.lsp4j.Hover
import org.eclipse.lsp4j.MarkedString
import org.eclipse.lsp4j.Range
import org.eclipse.lsp4j.jsonrpc.messages.Either
import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.RenderingFormat
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.javacs.kt.CompiledFile
import org.javacs.kt.completion.DECL_RENDERER
import org.javacs.kt.position.position
import org.javacs.kt.util.findParent
import org.javacs.kt.signaturehelp.getDocString

fun hoverAt(file: CompiledFile, cursor: Int): Hover? {
    val (ref, target) = file.referenceAtPoint(cursor) ?: return typeHoverAt(file, cursor)
    val javaDoc = getDocString(file, cursor)
    val location = ref.textRange
    val hoverText = DECL_RENDERER.render(target)
    val hover = Either.forRight<String, MarkedString>(MarkedString("kotlin", hoverText))
    val range = Range(
            position(file.content, location.startOffset),
            position(file.content, location.endOffset))
    return Hover(listOf(hover, Either.forLeft(javaDoc)), range)
}

private fun typeHoverAt(file: CompiledFile, cursor: Int): Hover? {
    val expression = file.parseAtPoint(cursor)?.findParent<KtExpression>() ?: return null
    var javaDoc: String = ""
    if (expression.children.size > 0 && expression.children[0] is KDoc)
        javaDoc = renderJavaDoc(expression.children[0].text)
    val scope = file.scopeAtPoint(cursor) ?: return null
    val hoverText = renderTypeOf(expression, file.bindingContextOf(expression, scope)) ?: return null
    return Hover(listOf(Either.forRight(MarkedString("kotlin", hoverText)), Either.forLeft(javaDoc)))
}

// Source: https://github.com/JetBrains/kotlin/blob/master/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinExpressionTypeProvider.kt

private val TYPE_RENDERER: DescriptorRenderer by lazy { DescriptorRenderer.COMPACT.withOptions {
    textFormat = RenderingFormat.PLAIN
    classifierNamePolicy = object: ClassifierNamePolicy {
        override fun renderClassifier(classifier: ClassifierDescriptor, renderer: DescriptorRenderer): String {
            if (DescriptorUtils.isAnonymousObject(classifier)) {
                return "<anonymous object>"
            }
            return ClassifierNamePolicy.SHORT.renderClassifier(classifier, renderer)
        }
    }
} }

private fun renderJavaDoc(text: String): String {
    val split = text.split('\n')
    return split.mapIndexed { i, it ->
        var ret: String
        if (i == 0) ret = it.substring(it.indexOf("/**") + 3) // get rid of the start comment characters
        else if (i == split.size - 1) ret = it.substring(it.indexOf("*/") + 2) // get rid of the end comment characters
        else ret = it.substring(it.indexOf('*') + 1) // get rid of any leading *
        ret
    }.joinToString("\n")
}

private fun renderTypeOf(element: KtExpression, bindingContext: BindingContext): String? {
    if (element is KtCallableDeclaration) {
        val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, element]
        if (descriptor != null) {
            when (descriptor) {
                is CallableDescriptor -> return descriptor.returnType?.let(TYPE_RENDERER::renderType)
            }
        }
    }

    val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, element]?.type ?: element.getType(bindingContext)
    val result = expressionType?.let { TYPE_RENDERER.renderType(it) } ?: return null

    val smartCast = bindingContext[BindingContext.SMARTCAST, element]
    if (smartCast != null && element is KtReferenceExpression) {
        val declaredType = (bindingContext[BindingContext.REFERENCE_TARGET, element] as? CallableDescriptor)?.returnType
        if (declaredType != null) {
            return result + " (smart cast from " + TYPE_RENDERER.renderType(declaredType) + ")"
        }
    }
    return result
}

package org.javacs.kt.j2k

import org.javacs.kt.LOG
import org.jetbrains.kotlin.com.intellij.psi.*
import org.jetbrains.kotlin.com.intellij.psi.javadoc.*

/**
 * A Psi visitor that converts Java elements into
 * Kotlin code.
 */
class JavaElementConverter(
    private val indentLevel: Int = 0,
    private val indentSize: Int = 4 // spaces
) : JavaElementVisitor() {
    /**
     * Contains the translated code. If code has multiple lines,
     * the first line is *not* indented, all subsequent lines are
     * indented by 'indentLevel' levels.
     */
    var translatedKotlinCode: String? = null
        private set
    private val indent: String = "".padStart(indentLevel * indentSize, ' ')

    // =================
    // Extension methods
    // =================

    private val String?.spacePrefixed: String
        get() = this?.let { " $it" } ?: ""

    /** Convenience method to perform construction, visit and translation in one call. */
    private fun PsiElement?.translate(indentDelta: Int = 0): String? = JavaElementConverter(indentLevel + indentDelta)
        .also { this?.accept(it) }
        .translatedKotlinCode

    /** Fetches the indented indent. */
    private fun nextIndent(indentDelta: Int = 1): String = ""
        .padStart((indentLevel + indentDelta) * indentSize, ' ')

    /**
     * Constructs a code block from a list of statements.
     * Note that the 'indentDelta' refers to the layer
     * of indentation *inside* the block.
     */
    private fun Sequence<String>.buildCodeBlock(indentDelta: Int = 1, separatorNewlines: Int = 1): String {
        val indentedStatements = this
            .map { "${nextIndent(indentDelta)}$it" }
            .joinToString(separator = "\n".repeat(separatorNewlines))
        return "{\n$indentedStatements\n${nextIndent(indentDelta - 1)}}"
    }

    private fun List<String>.buildCodeBlock(indentDelta: Int = 1, separatorNewlines: Int = 1): String = asSequence().buildCodeBlock(indentDelta, separatorNewlines)

    /** Converts a PsiType to its Kotlin representation. */
    private fun PsiType.translateType(): String = accept(JavaTypeConverter)

    /** Converts a list of type arguments to its Kotlin representation. */
    private fun PsiCallExpression.translateTypeArguments(): String = "<${typeArgumentList.translate()}>"

    /** Fetches the child statements of a potentially composite statement. */
    private val PsiStatement.containedStatements: Sequence<PsiStatement> get() = if (this is PsiBlockStatement) {
        codeBlock.statements.asSequence()
    } else {
        sequenceOf(this)
    }

    private fun j2kTODO(type: String) {
        LOG.warn("J2K can not convert $type yet")
        translatedKotlinCode = "/*$type*/"
    }

    // =================
    // Visitor methods
    // =================

    override fun visitAnonymousClass(aClass: PsiAnonymousClass) {
        super.visitAnonymousClass(aClass)
        j2kTODO("AnonymousClass")
    }

    override fun visitArrayAccessExpression(expression: PsiArrayAccessExpression) {
        val translatedArray = expression.arrayExpression.translate()
        val translatedIndex = expression.indexExpression.translate()
        translatedKotlinCode = "$translatedArray[$translatedIndex]"
    }

    override fun visitArrayInitializerExpression(expression: PsiArrayInitializerExpression) {
        super.visitArrayInitializerExpression(expression)
        j2kTODO("ArrayInitializerExpression")
    }

    override fun visitAssertStatement(statement: PsiAssertStatement) {
        val translatedCondition = statement.assertCondition.translate()
        val translatedDescription = statement.assertDescription.translate()?.let { " { $it }" } ?: ""
        translatedKotlinCode = "assert($translatedCondition)$translatedDescription"
    }

    override fun visitAssignmentExpression(expression: PsiAssignmentExpression) {
        translatedKotlinCode = "${expression.lExpression.translate()} ${expression.operationSign.text} ${expression.rExpression.translate()}"
    }

    override fun visitBinaryExpression(expression: PsiBinaryExpression) {
        translatedKotlinCode = "${expression.lOperand.translate()} ${expression.operationSign.text} ${expression.rOperand.translate()}"
    }

    override fun visitBlockStatement(statement: PsiBlockStatement) {
        visitCodeBlock(statement.codeBlock)
    }

    override fun visitBreakStatement(statement: PsiBreakStatement) {
        // TODO: Break labels
        translatedKotlinCode = "break"
    }

    override fun visitClass(aClass: PsiClass) {
        val translatedTypeParams = aClass.typeParameterList.translate()
            ?.let { if (it.isNotEmpty()) "<$it>" else null }
            ?: ""

        val translatedSuperTypes = sequenceOf(aClass.extendsList.translate(), aClass.implementsList.translate())
            .filterNotNull()
            .filter { it.isNotEmpty() }
            .joinToString(separator = ", ")
            .let { if (it.isNotEmpty()) " : $it" else "" }

        val (staticMembers, instanceMembers) = aClass.children
            .mapNotNull { it as? PsiMember }
            .partition { it.hasModifierProperty(PsiModifier.STATIC) }

        val translatedInstanceMembers = instanceMembers
            .mapNotNull { it.translate(indentDelta = 1) }

        val translatedCompanion = if (!staticMembers.isEmpty()) {
            val translatedCompanionBlock = staticMembers
                .map { "@JvmStatic ${it.translate(indentDelta = 2)}" }
                .buildCodeBlock(indentDelta = 2)
                .spacePrefixed
            "companion object$translatedCompanionBlock"
        } else ""

        val translatedBody = (listOf(translatedCompanion) + translatedInstanceMembers)
            .buildCodeBlock(indentDelta = 1, separatorNewlines = 2)
            .spacePrefixed

        translatedKotlinCode = "class ${aClass.name}$translatedTypeParams$translatedSuperTypes$translatedBody"
    }

    override fun visitClassInitializer(initializer: PsiClassInitializer) {
        translatedKotlinCode = "init${initializer.body.translate().spacePrefixed}"
    }

    override fun visitClassObjectAccessExpression(expression: PsiClassObjectAccessExpression) {
        super.visitClassObjectAccessExpression(expression)
        j2kTODO("ClassObjectAccessExpression")
    }

    override fun visitCodeBlock(block: PsiCodeBlock) {
        val translated = block.statements.mapNotNull { it.translate(indentDelta = 1) }
        translatedKotlinCode = translated.buildCodeBlock()
    }

    override fun visitConditionalExpression(expression: PsiConditionalExpression) {
        val translatedCondition = expression.condition.translate()
        val translatedThen = expression.thenExpression.translate()
        val translatedElse = expression.elseExpression.translate()
        translatedKotlinCode = "if ($translatedCondition) $translatedThen else $translatedElse"
    }

    override fun visitContinueStatement(statement: PsiContinueStatement) {
        translatedKotlinCode = "continue${statement.labelIdentifier.translate().spacePrefixed}"
    }

    override fun visitDeclarationStatement(statement: PsiDeclarationStatement) {
        translatedKotlinCode = statement.declaredElements
            .map { it.translate() }
            .joinToString(separator = "\n")
    }

    override fun visitDocComment(comment: PsiDocComment) {
        translatedKotlinCode = comment.text
    }

    override fun visitDocTag(tag: PsiDocTag) {
        super.visitDocTag(tag)
        j2kTODO("DocTag")
    }

    override fun visitDocTagValue(value: PsiDocTagValue) {
        super.visitDocTagValue(value)
        j2kTODO("DocTagValue")
    }

    override fun visitDoWhileStatement(statement: PsiDoWhileStatement) {
        translatedKotlinCode = "do ${statement.body.translate()} while (${statement.condition.translate()})"
    }

    override fun visitEmptyStatement(statement: PsiEmptyStatement) {
        translatedKotlinCode = ""
    }

    override fun visitExpression(expression: PsiExpression) {
        translatedKotlinCode = expression.text // Perform no conversion if no concrete visitor could be found
    }

    override fun visitExpressionList(list: PsiExpressionList) {
        translatedKotlinCode = list.expressions.asSequence().map { it.translate() }.joinToString(separator = ", ")
    }

    override fun visitExpressionListStatement(statement: PsiExpressionListStatement) {
        visitExpressionList(statement.expressionList)
    }

    override fun visitExpressionStatement(statement: PsiExpressionStatement) {
        translatedKotlinCode = statement.expression.translate()
    }

    override fun visitField(field: PsiField) {
        visitVariable(field)
    }

    override fun visitForStatement(statement: PsiForStatement) {
        LOG.info("Body: ${statement.body}")
        val translatedBody = ((statement.body?.containedStatements ?: emptySequence()) + sequenceOf(statement.update))
            .mapNotNull { it.translate(indentDelta = 1) }
            .buildCodeBlock()
        translatedKotlinCode = "${statement.initialization.translate()}\n${indent}while (${statement.condition.translate()}) $translatedBody"
    }

    override fun visitForeachStatement(statement: PsiForeachStatement) {
        val translatedParameter = statement.iterationParameter.name
        val translatedIterated = statement.iteratedValue.translate()
        val translatedBody = statement.body.translate()
        translatedKotlinCode = "for ($translatedParameter in $translatedIterated) $translatedBody"
    }

    override fun visitIdentifier(identifier: PsiIdentifier) {
        translatedKotlinCode = identifier.text
    }

    override fun visitIfStatement(statement: PsiIfStatement) {
        val translatedIf = "if (${statement.condition.translate()})${statement.thenBranch.translate().spacePrefixed}"
        val translatedElse = statement.elseBranch.translate()?.let { "else $it" }.spacePrefixed
        translatedKotlinCode = translatedIf + translatedElse
    }

    override fun visitImportList(list: PsiImportList) {
        translatedKotlinCode = list.allImportStatements
            .mapNotNull { it.translate() }
            .joinToString(separator = "\n")
    }

    override fun visitImportStatement(statement: PsiImportStatement) {
        statement.qualifiedName?.let {
            if (!isPlatformImport(it)) {
                translatedKotlinCode = "import $it"
            }
        }
    }

    override fun visitImportStaticStatement(statement: PsiImportStaticStatement) {
        translatedKotlinCode = "import ${statement.referenceName}"
    }

    override fun visitInlineDocTag(tag: PsiInlineDocTag) {
        super.visitInlineDocTag(tag)
        j2kTODO("InlineDocTag")
    }

    override fun visitInstanceOfExpression(expression: PsiInstanceOfExpression) {
        translatedKotlinCode = "${expression.operand.translate()} is ${expression.checkType?.type?.translateType()}"
    }

    override fun visitJavaToken(token: PsiJavaToken) {
        translatedKotlinCode = token.text
    }

    override fun visitKeyword(keyword: PsiKeyword) {
        super.visitKeyword(keyword)
        j2kTODO("Keyword")
    }

    override fun visitLabeledStatement(statement: PsiLabeledStatement) {
        super.visitLabeledStatement(statement)
        j2kTODO("LabeledStatement")
    }

    override fun visitLiteralExpression(expression: PsiLiteralExpression) {
        val value = expression.value
        translatedKotlinCode = when (value) {
            is String -> "\"$value\""
            else -> value.toString()
        }
    }

    override fun visitLocalVariable(variable: PsiLocalVariable) {
        visitVariable(variable)
    }

    override fun visitMethod(method: PsiMethod) {
        // TODO: Type parameters, annotations, modifiers, ...
        val name = method.name
        val translatedParamList = method.parameterList.translate()
        val translatedReturnType = method.returnType?.translateType()
            ?.let { if (it == "Unit") null else it }
            ?.let { ": $it" }
            ?: ""
        val translatedBody = method.body.translate().spacePrefixed
        translatedKotlinCode = "fun $name($translatedParamList)$translatedReturnType$translatedBody"
    }

    override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
        val name = expression.methodExpression.translate()
        val translatedArgList = expression.argumentList.translate()
        translatedKotlinCode = "$name($translatedArgList)"
    }

    override fun visitCallExpression(callExpression: PsiCallExpression) {
        super.visitCallExpression(callExpression)
        j2kTODO("CallExpression")
    }

    override fun visitModifierList(list: PsiModifierList) {
        super.visitModifierList(list)
        j2kTODO("ModifierList")
    }

    override fun visitNewExpression(expression: PsiNewExpression) {
        val qualifier = expression.qualifier?.translate()?.let { "$it." } ?: ""
        val name = expression.type?.translateType()

        val arrayDimensions = expression.arrayDimensions
        val translatedArgs = if (arrayDimensions.isEmpty()) {
            "(${expression.argumentList.translate()})"
        } else {
            arrayDimensions.map { "[$it]" }.joinToString()
        }

        translatedKotlinCode = "$qualifier$name$translatedArgs"
    }

    override fun visitPackage(aPackage: PsiPackage) {
        translatedKotlinCode = aPackage.qualifiedName
    }

    override fun visitPackageStatement(statement: PsiPackageStatement) {
        translatedKotlinCode = "package ${statement.packageName}"
    }

    override fun visitParameter(parameter: PsiParameter) {
        // TODO: Varargs, ...
        translatedKotlinCode = "${parameter.name}: ${parameter.type.translateType()}"
    }

    override fun visitReceiverParameter(parameter: PsiReceiverParameter) {
        super.visitReceiverParameter(parameter)
        j2kTODO("ReceiverParameter")
    }

    override fun visitParameterList(list: PsiParameterList) {
        translatedKotlinCode = list.parameters
            .mapNotNull { it.translate() }
            .joinToString(separator = ", ")
    }

    override fun visitParenthesizedExpression(expression: PsiParenthesizedExpression) {
        translatedKotlinCode = "(${expression.expression.translate()})"
    }

    override fun visitUnaryExpression(expression: PsiUnaryExpression) {
        // Implemented by prefix/postfix expression
        super.visitUnaryExpression(expression)
        j2kTODO("UnaryExpression")
    }

    override fun visitPostfixExpression(expression: PsiPostfixExpression) {
        translatedKotlinCode = "${expression.operand.translate()}${expression.operationSign.text}"
    }

    override fun visitPrefixExpression(expression: PsiPrefixExpression) {
        translatedKotlinCode = "${expression.operationSign.text}${expression.operand.translate()}"
    }

    override fun visitReferenceElement(reference: PsiJavaCodeReferenceElement) {
        translatedKotlinCode = reference.referenceNameElement.translate()
    }

    override fun visitImportStaticReferenceElement(reference: PsiImportStaticReferenceElement) {
        super.visitImportStaticReferenceElement(reference)
        j2kTODO("ImportStaticReferenceElement")
    }

    override fun visitReferenceExpression(expression: PsiReferenceExpression) {
        val qualifier = expression.qualifier?.translate()?.let { "$it." } ?: ""
        val name = expression.referenceNameElement.translate()
        translatedKotlinCode = "$qualifier$name"
    }

    override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) {
        val qualifier = expression.qualifierType.translate()
            ?: expression.qualifierExpression.translate()
        val memberName = expression.potentiallyApplicableMember?.name
        translatedKotlinCode = "$qualifier::$memberName"
    }

    override fun visitReferenceList(list: PsiReferenceList) {
        translatedKotlinCode = list.referenceElements
            .map { it.translate() }
            .joinToString(separator = ", ")
    }

    override fun visitReferenceParameterList(list: PsiReferenceParameterList) {
        translatedKotlinCode = list.typeArguments
            .map { it.translateType() }
            .joinToString(separator = ", ")
    }

    override fun visitTypeParameterList(list: PsiTypeParameterList) {
        translatedKotlinCode = list.typeParameters
            .map { it.translate() }
            .joinToString(separator = ", ")
    }

    override fun visitReturnStatement(statement: PsiReturnStatement) {
        translatedKotlinCode = "return${statement.returnValue.translate().spacePrefixed}"
    }

    override fun visitStatement(statement: PsiStatement) {
        super.visitStatement(statement)
        j2kTODO("Statement")
    }

    override fun visitSuperExpression(expression: PsiSuperExpression) {
        val translatedQualifier = expression.qualifier.translate()?.let { "@$it" } ?: ""
        translatedKotlinCode = "super$translatedQualifier"
    }

    override fun visitSwitchLabelStatement(statement: PsiSwitchLabelStatement) {
        super.visitSwitchLabelStatement(statement)
        j2kTODO("SwitchLabelStatement")
    }

    override fun visitSwitchLabeledRuleStatement(statement: PsiSwitchLabeledRuleStatement) {
        super.visitSwitchLabeledRuleStatement(statement)
        j2kTODO("SwitchLabeledRuleStatement")
    }

    override fun visitSwitchStatement(statement: PsiSwitchStatement) {
        super.visitSwitchStatement(statement)
        j2kTODO("SwitchStatement")
    }

    override fun visitSynchronizedStatement(statement: PsiSynchronizedStatement) {
        val translatedLock = statement.lockExpression.translate()?.let { "($it)" } ?: ""
        val translatedBody = statement.body.translate()
        translatedKotlinCode = "synchronized$translatedLock $translatedBody"
    }

    override fun visitThisExpression(expression: PsiThisExpression) {
        val translatedQualifier = expression.qualifier.translate()?.let { "@$it" } ?: ""
        translatedKotlinCode = "this$translatedQualifier"
    }

    override fun visitThrowStatement(statement: PsiThrowStatement) {
        translatedKotlinCode = "throw ${statement.exception.translate()}"
    }

    override fun visitTryStatement(statement: PsiTryStatement) {
        val translatedTryBlock = statement.tryBlock.translate()
        val translatedCatches = statement.catchBlocks.asSequence()
            .mapIndexed { i, block -> " catch (${statement.catchBlockParameters[i].translate()}) ${block.translate()}" }
            .joinToString()
        val translatedFinally = statement.finallyBlock.translate()?.let { " finally $it" } ?: ""
        translatedKotlinCode = "try $translatedTryBlock$translatedCatches$translatedFinally"
    }

    override fun visitCatchSection(section: PsiCatchSection) {
        super.visitCatchSection(section)
        j2kTODO("CatchSection")
    }

    override fun visitResourceList(resourceList: PsiResourceList) {
        super.visitResourceList(resourceList)
        j2kTODO("ResourceList")
    }

    override fun visitResourceVariable(variable: PsiResourceVariable) {
        super.visitResourceVariable(variable)
        j2kTODO("ResourceVariable")
    }

    override fun visitResourceExpression(expression: PsiResourceExpression) {
        super.visitResourceExpression(expression)
        j2kTODO("ResourceExpression")
    }

    override fun visitTypeElement(type: PsiTypeElement) {
        translatedKotlinCode = type.type.translateType()
    }

    override fun visitTypeCastExpression(expression: PsiTypeCastExpression) {
        translatedKotlinCode = "${expression.operand.translate()} as ${expression.castType?.type?.translateType()}"
    }

    override fun visitVariable(variable: PsiVariable) {
        // TODO: Nullability
        val keyword = if (variable.hasModifierProperty(PsiModifier.FINAL)) "val" else "var"
        val translatedInitializer = variable.initializer.translate()?.let { " = $it" } ?: ""
        translatedKotlinCode = "$keyword ${variable.name}: ${variable.type.translateType()}$translatedInitializer"
    }

    override fun visitWhileStatement(statement: PsiWhileStatement) {
        translatedKotlinCode = "while (${statement.condition.translate()}) ${statement.body.translate()}"
    }

    override fun visitJavaFile(file: PsiJavaFile) {
        // This is the entry point for the J2K converter
        translatedKotlinCode = file.children.asSequence()
            .mapNotNull { it.translate() }
            .filter { it.isNotEmpty() }
            .joinToString(separator = "\n\n")
    }

    override fun visitImplicitVariable(variable: ImplicitVariable) {
        super.visitImplicitVariable(variable)
        j2kTODO("ImplicitVariable")
    }

    override fun visitDocToken(token: PsiDocToken) {
        super.visitDocToken(token)
        j2kTODO("DocToken")
    }

    override fun visitTypeParameter(classParameter: PsiTypeParameter) {
        val translatedExtends = classParameter.extendsList.translate()
            ?.let { if (it.isNotEmpty()) " : $it" else "" }
        translatedKotlinCode = "${classParameter.name}$translatedExtends"
    }

    override fun visitAnnotation(annotation: PsiAnnotation) {
        super.visitAnnotation(annotation)
        j2kTODO("Annotation")
    }

    override fun visitAnnotationParameterList(list: PsiAnnotationParameterList) {
        super.visitAnnotationParameterList(list)
        j2kTODO("AnnotationParameterList")
    }

    override fun visitAnnotationArrayInitializer(initializer: PsiArrayInitializerMemberValue) {
        super.visitAnnotationArrayInitializer(initializer)
        j2kTODO("AnnotationArrayInitializer")
    }

    override fun visitNameValuePair(pair: PsiNameValuePair) {
        super.visitNameValuePair(pair)
        j2kTODO("NameValuePair")
    }

    override fun visitAnnotationMethod(method: PsiAnnotationMethod) {
        super.visitAnnotationMethod(method)
        j2kTODO("AnnotationMethod")
    }

    override fun visitEnumConstant(enumConstant: PsiEnumConstant) {
        super.visitEnumConstant(enumConstant)
        j2kTODO("EnumConstant")
    }

    override fun visitEnumConstantInitializer(enumConstantInitializer: PsiEnumConstantInitializer) {
        super.visitEnumConstantInitializer(enumConstantInitializer)
        j2kTODO("EnumConstantInitializer")
    }

    override fun visitCodeFragment(codeFragment: JavaCodeFragment) {
        super.visitCodeFragment(codeFragment)
        j2kTODO("CodeFragment")
    }

    override fun visitPolyadicExpression(expression: PsiPolyadicExpression) {
        super.visitPolyadicExpression(expression)
        j2kTODO("PolyadicExpression")
    }

    override fun visitLambdaExpression(expression: PsiLambdaExpression) {
        val translatedParams: String = expression.parameterList.parameters
            .map { it.name }
            .joinToString(separator = ", ")
            .ifEmpty { null }
            ?.let { " $it ->" }
            ?: ""

        val body = expression.body
        val translatedBody: String = when (body) {
            null -> " "
            is PsiExpression -> " ${body.translate()} "
            is PsiCodeBlock -> {
                val indentedStatements = body.statements
                    .asSequence()
                    .map { "${nextIndent()}${it.translate()}" }
                    .joinToString(separator = "\n")
                "\n$indentedStatements\n$indent"
            }
            else -> " ? "
        }

        translatedKotlinCode = "{$translatedParams$translatedBody}"
    }

    override fun visitSwitchExpression(expression: PsiSwitchExpression) {
        super.visitSwitchExpression(expression)
        j2kTODO("SwitchExpression")
    }

    override fun visitModule(module: PsiJavaModule) {
        super.visitModule(module)
        j2kTODO("Module")
    }

    override fun visitModuleReferenceElement(refElement: PsiJavaModuleReferenceElement) {
        super.visitModuleReferenceElement(refElement)
        j2kTODO("ModuleReferenceElement")
    }

    override fun visitModuleStatement(statement: PsiStatement) {
        super.visitModuleStatement(statement)
        j2kTODO("ModuleStatement")
    }

    override fun visitRequiresStatement(statement: PsiRequiresStatement) {
        super.visitRequiresStatement(statement)
        j2kTODO("RequiresStatement")
    }

    override fun visitPackageAccessibilityStatement(statement: PsiPackageAccessibilityStatement) {
        super.visitPackageAccessibilityStatement(statement)
        j2kTODO("PackageAccessibilityStatement")
    }

    override fun visitUsesStatement(statement: PsiUsesStatement) {
        super.visitUsesStatement(statement)
        j2kTODO("UsesStatement")
    }

    override fun visitProvidesStatement(statement: PsiProvidesStatement) {
        super.visitProvidesStatement(statement)
        j2kTODO("ProvidesStatement")
    }
}

package org.javacs.kt.j2k

import org.jetbrains.kotlin.com.intellij.lang.java.JavaLanguage
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
import org.jetbrains.kotlin.com.intellij.openapi.project.Project
// import org.jetbrains.kotlin.j2k.JavaToKotlinTranslator
import org.javacs.kt.LOG
import org.javacs.kt.Compiler
import org.javacs.kt.CompilationKind
import org.javacs.kt.util.nonNull

fun convertJavaToKotlin(javaCode: String, compiler: Compiler): String {
    val psiFactory = compiler.psiFileFactoryFor(CompilationKind.DEFAULT)
    val javaAST = psiFactory.createFileFromText("snippet.java", JavaLanguage.INSTANCE, javaCode)
    LOG.info("Parsed {} to {}", javaCode, javaAST)

	return JavaElementConverter().also(javaAST::accept).translatedKotlinCode ?: run {
        LOG.warn("Could not translate code")
        ""
    }
}

package org.javacs.kt.j2k

import org.javacs.kt.LOG
import org.jetbrains.kotlin.com.intellij.psi.*

object JavaTypeConverter : PsiTypeVisitor<String>() {
    override fun visitType(type: PsiType): String {
        return type.presentableText
    }

    override fun visitPrimitiveType(primitiveType: PsiPrimitiveType): String = when (primitiveType.canonicalText) {
        "void" -> "Unit"
        else -> primitiveType.canonicalText.capitalize()
    }

    override fun visitArrayType(arrayType: PsiArrayType): String = when (try {
        arrayType.componentType.canonicalText
    } catch (e: IllegalStateException) {
        LOG.warn("Error while fetching text representation of array type: {}", e)
        "?"
    }) {
        "byte" -> "ByteArray"
        "short" -> "ShortArray"
        "int" -> "IntArray"
        "long" -> "LongArray"
        "char" -> "CharArray"
        "boolean" -> "BooleanArray"
        "float" -> "FloatArray"
        "double" -> "DoubleArray"
        else -> "Array<${arrayType.componentType.accept(this)}>"
    }

    override fun visitClassType(classType: PsiClassType): String {
        val translatedTypeArgs = classType.parameters.asSequence()
            .map { it.accept(this) }
            .joinToString(separator = ", ")
            .let { if (it.isNotEmpty()) "<$it>" else "" }
        return "${platformType(classType.className)}$translatedTypeArgs"
    }

    override fun visitCapturedWildcardType(capturedWildcardType: PsiCapturedWildcardType): String {
        return super.visitCapturedWildcardType(capturedWildcardType) ?: "?"
    }

    override fun visitWildcardType(wildcardType: PsiWildcardType): String =
        if (wildcardType.isSuper()) {
            "in ${wildcardType.bound?.accept(this)}"
        } else if (wildcardType.isExtends()) {
            "out ${wildcardType.bound?.accept(this)}"
        } else {
            super.visitWildcardType(wildcardType) ?: "?"
        }

    override fun visitEllipsisType(ellipsisType: PsiEllipsisType): String {
        return super.visitEllipsisType(ellipsisType) ?: "?"
    }

    override fun visitDisjunctionType(disjunctionType: PsiDisjunctionType): String {
        return super.visitDisjunctionType(disjunctionType) ?: "?"
    }

    override fun visitIntersectionType(intersectionType: PsiIntersectionType): String {
        return super.visitIntersectionType(intersectionType) ?: "?"
    }

    override fun visitDiamondType(diamondType: PsiDiamondType): String {
        return super.visitDiamondType(diamondType) ?: "?"
    }

    override fun visitLambdaExpressionType(lambdaExpressionType: PsiLambdaExpressionType): String {
        return super.visitLambdaExpressionType(lambdaExpressionType) ?: "?"
    }

    override fun visitMethodReferenceType(methodReferenceType: PsiMethodReferenceType): String {
        return super.visitMethodReferenceType(methodReferenceType)
    }
}

package org.javacs.kt.j2k

private val platformImports = setOf(
    "java.util.List",
    "java.util.Set"
)

fun isPlatformImport(fqJavaType: String) =
    platformImports.contains(fqJavaType)

fun platformType(javaType: String): String = when (javaType) {
    "List" -> "MutableList"
    "Set" -> "MutableSet"
    "Collection" -> "MutableCollection"
    "Integer" -> "Int"
    "Character" -> "Char"
    else -> javaType
}

package org.javacs.kt.position

import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import org.eclipse.lsp4j.Location
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import org.javacs.kt.LOG
import org.javacs.kt.util.toPath
import org.jetbrains.kotlin.descriptors.SourceFile
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
import kotlin.math.max

fun extractRange(content: String, range: Range) =
    content.substring(
        offset(content, range.start),
        offset(content, range.end)
    )

fun offset(content: String, position: Position) =
    offset(content, position.line, position.character)

/**
 * Convert from 0-based line and column to 0-based offset
 */
fun offset(content: String, line: Int, char: Int): Int {
    assert(!content.contains('\r'))

    val reader = content.reader()
    var offset = 0

    var lineOffset = 0
    while (lineOffset < line) {
        val nextChar = reader.read()

        if (nextChar == -1)
            throw RuntimeException("Reached end of file before reaching line $line")

        if (nextChar.toChar() == '\n')
            lineOffset++

        offset++
    }

    var charOffset = 0
    while (charOffset < char) {
        val nextChar = reader.read()

        if (nextChar == -1)
            throw RuntimeException("Reached end of file before reaching char $char")

        charOffset++
        offset++
    }

    return offset
}

fun position(content: String, offset: Int): Position {
    val reader = content.reader()
    var line = 0
    var char = 0

    var find = 0
    while (find < offset) {
        val nextChar = reader.read()

        if (nextChar == -1)
            throw RuntimeException("Reached end of file before reaching offset $offset")

        find++
        char++

        if (nextChar.toChar() == '\n') {
            line++
            char = 0
        }
    }

    return Position(line, char)
}

fun range(content: String, range: TextRange) =
        Range(position(content, range.startOffset), position(content, range.endOffset))

fun location(declaration: DeclarationDescriptor): Location? {
    val psiLocation = declaration.findPsi()?.let(::location)
    if (psiLocation != null) return psiLocation

    if (declaration is DeclarationDescriptorWithSource) {
        val sourceFile = declaration.source.containingFile
        when (sourceFile) {
            is PsiSourceFile -> {
                val file = sourceFile.psiFile.toURIString()
                return Location(file, Range(Position(0, 0), Position(0, 0)))
            }
            SourceFile.NO_SOURCE_FILE -> Unit // If no source file is present, do nothing
            else -> LOG.info("Source type of {} not recognized", sourceFile)
        }
    } else {
        LOG.info("{} does not have a source", declaration)
    }

    return null
}

val Position.isZero: Boolean
    get() = (line == 0) && (character == 0)

val Range.isZero: Boolean
    get() = start.isZero && end.isZero

fun location(expr: PsiElement): Location? {
    val content = try { expr.containingFile?.text } catch (e: NullPointerException) { null }
    val file = expr.containingFile.toURIString()
    return content?.let { Location(file, range(it, expr.textRange)) }
}

fun PsiFile.toURIString() = toPath().toUri().toString()

/**
 * Region that has been changed
 */
fun changedRegion(oldContent: String, newContent: String): Pair<TextRange, TextRange>? {
    if (oldContent == newContent) return null

    val prefix = oldContent.commonPrefixWith(newContent).length
    val suffix = oldContent.commonSuffixWith(newContent).length
    val oldEnd = max(oldContent.length - suffix, prefix)
    val newEnd = max(newContent.length - suffix, prefix)

    return Pair(TextRange(prefix, oldEnd), TextRange(prefix, newEnd))
}

package org.javacs.kt.references

import org.eclipse.lsp4j.Location
import org.javacs.kt.LOG
import org.javacs.kt.SourcePath
import org.javacs.kt.position.location
import org.javacs.kt.util.emptyResult
import org.javacs.kt.util.findParent
import org.javacs.kt.util.preOrderTraversal
import org.javacs.kt.util.toPath
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.util.OperatorNameConventions
import java.nio.file.Path

fun findReferences(file: Path, cursor: Int, sp: SourcePath): List<Location> {
    return doFindReferences(file, cursor, sp)
            .map { location(it) }
            .filterNotNull()
            .toList()
}

private fun doFindReferences(file: Path, cursor: Int, sp: SourcePath): Collection<KtElement> {
    val recover = sp.currentVersion(file.toUri())
    val element = recover.elementAtPoint(cursor)?.findParent<KtNamedDeclaration>() ?: return emptyResult("No declaration at ${recover.describePosition(cursor)}")
    val declaration = recover.compile[BindingContext.DECLARATION_TO_DESCRIPTOR, element] ?: return emptyResult("Declaration ${element.fqName} has no descriptor")
    val maybes = possibleReferences(declaration, sp).map { it.toPath() }
    LOG.debug("Scanning {} files for references to {}", maybes.size, element.fqName)
    val recompile = sp.compileFiles(maybes.map(Path::toUri))

    return when {
        isComponent(declaration) -> findComponentReferences(element, recompile) + findNameReferences(element, recompile)
        isIterator(declaration) -> findIteratorReferences(element, recompile) + findNameReferences(element, recompile)
        isPropertyDelegate(declaration) -> findDelegateReferences(element, recompile) + findNameReferences(element, recompile)
        else -> findNameReferences(element, recompile)
    }
}

private fun findNameReferences(element: KtNamedDeclaration, recompile: BindingContext): List<KtReferenceExpression> {
    val references = recompile.getSliceContents(BindingContext.REFERENCE_TARGET)

    return references.filter { matchesReference(it.value, element) }.map { it.key }
}

private fun findDelegateReferences(element: KtNamedDeclaration, recompile: BindingContext): List<KtElement> {
    val references = recompile.getSliceContents(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL)

    return references
            .filter { matchesReference(it.value.candidateDescriptor, element) }
            .map { it.value.call.callElement }
}

private fun findIteratorReferences(element: KtNamedDeclaration, recompile: BindingContext): List<KtElement> {
    val references = recompile.getSliceContents(BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL)

    return references
            .filter { matchesReference( it.value.candidateDescriptor, element) }
            .map { it.value.call.callElement }
}

private fun findComponentReferences(element: KtNamedDeclaration, recompile: BindingContext): List<KtElement> {
    val references = recompile.getSliceContents(BindingContext.COMPONENT_RESOLVED_CALL)

    return references
            .filter { matchesReference(it.value.candidateDescriptor, element) }
            .map { it.value.call.callElement }
}

// TODO use imports to limit search
private fun possibleReferences(declaration: DeclarationDescriptor, sp: SourcePath): Set<KtFile> {
    if (declaration is ClassConstructorDescriptor) {
        return possibleNameReferences(declaration.constructedClass.name, sp)
    }
    if (isComponent(declaration)) {
        return possibleComponentReferences(sp) + possibleNameReferences(declaration.name, sp)
    }
    if (isPropertyDelegate(declaration)) {
        return hasPropertyDelegates(sp) + possibleNameReferences(declaration.name, sp)
    }
    if (isGetSet(declaration)) {
        return possibleGetSets(sp) + possibleNameReferences(declaration.name, sp)
    }
    if (isIterator(declaration)) {
        return hasForLoops(sp) + possibleNameReferences(declaration.name, sp)
    }
    if (declaration is FunctionDescriptor && declaration.isOperator && declaration.name == OperatorNameConventions.INVOKE) {
        return possibleInvokeReferences(declaration, sp) + possibleNameReferences(declaration.name, sp)
    }
    if (declaration is FunctionDescriptor) {
        val operators = operatorNames(declaration.name)

        return possibleTokenReferences(operators, sp) + possibleNameReferences(declaration.name, sp)
    }
    return possibleNameReferences(declaration.name, sp)
}

private fun isPropertyDelegate(declaration: DeclarationDescriptor) =
        declaration is FunctionDescriptor &&
        declaration.isOperator &&
        (declaration.name == OperatorNameConventions.GET_VALUE || declaration.name == OperatorNameConventions.SET_VALUE)

private fun hasPropertyDelegates(sp: SourcePath): Set<KtFile> =
        sp.all().filter(::hasPropertyDelegate).toSet()

fun hasPropertyDelegate(source: KtFile): Boolean =
        source.preOrderTraversal().filterIsInstance<KtPropertyDelegate>().any()

private fun isIterator(declaration: DeclarationDescriptor) =
        declaration is FunctionDescriptor &&
        declaration.isOperator &&
        declaration.name == OperatorNameConventions.ITERATOR

private fun hasForLoops(sp: SourcePath): Set<KtFile> =
        sp.all().filter(::hasForLoop).toSet()

private fun hasForLoop(source: KtFile): Boolean =
        source.preOrderTraversal().filterIsInstance<KtForExpression>().any()

private fun isGetSet(declaration: DeclarationDescriptor) =
        declaration is FunctionDescriptor &&
        declaration.isOperator &&
        (declaration.name == OperatorNameConventions.GET || declaration.name == OperatorNameConventions.SET)

private fun possibleGetSets(sp: SourcePath): Set<KtFile> =
        sp.all().filter(::possibleGetSet).toSet()

private fun possibleGetSet(source: KtFile) =
        source.preOrderTraversal().filterIsInstance<KtArrayAccessExpression>().any()

private fun possibleInvokeReferences(declaration: FunctionDescriptor, sp: SourcePath) =
        sp.all().filter { possibleInvokeReference(declaration, it) }.toSet()

// TODO this is not very selective
private fun possibleInvokeReference(@Suppress("UNUSED_PARAMETER") declaration: FunctionDescriptor, source: KtFile): Boolean =
        source.preOrderTraversal().filterIsInstance<KtCallExpression>().any()

private fun isComponent(declaration: DeclarationDescriptor): Boolean =
        declaration is FunctionDescriptor &&
        declaration.isOperator &&
        OperatorNameConventions.COMPONENT_REGEX.matches(declaration.name.identifier)

private fun possibleComponentReferences(sp: SourcePath): Set<KtFile> =
        sp.all().filter { possibleComponentReference(it) }.toSet()

private fun possibleComponentReference(source: KtFile): Boolean =
        source.preOrderTraversal()
                .filterIsInstance<KtDestructuringDeclarationEntry>()
                .any()

private fun possibleTokenReferences(find: List<KtSingleValueToken>, sp: SourcePath): Set<KtFile> =
        sp.all().filter { possibleTokenReference(find, it) }.toSet()

private fun possibleTokenReference(find: List<KtSingleValueToken>, source: KtFile): Boolean =
        source.preOrderTraversal()
                .filterIsInstance<KtOperationReferenceExpression>()
                .any { it.operationSignTokenType in find }

private fun possibleNameReferences(declaration: Name, sp: SourcePath): Set<KtFile> =
        sp.all().filter { possibleNameReference(declaration, it) }.toSet()

private fun possibleNameReference(declaration: Name, source: KtFile): Boolean =
        source.preOrderTraversal()
                .filterIsInstance<KtSimpleNameExpression>()
                .any { it.getReferencedNameAsName() == declaration }

private fun matchesReference(found: DeclarationDescriptor, search: KtNamedDeclaration): Boolean {
    if (found is ConstructorDescriptor && found.isPrimary)
        return search is KtClass && found.constructedClass.fqNameSafe == search.fqName
    else
        return found.findPsi() == search
}

private fun operatorNames(name: Name): List<KtSingleValueToken> =
        when (name) {
            OperatorNameConventions.EQUALS -> listOf(KtTokens.EQEQ)
            OperatorNameConventions.COMPARE_TO -> listOf(KtTokens.GT, KtTokens.LT, KtTokens.LTEQ, KtTokens.GTEQ)
            else -> {
                val token = OperatorConventions.UNARY_OPERATION_NAMES.inverse()[name] ?:
                            OperatorConventions.BINARY_OPERATION_NAMES.inverse()[name] ?:
                            OperatorConventions.ASSIGNMENT_OPERATIONS.inverse()[name] ?:
                            OperatorConventions.BOOLEAN_OPERATIONS.inverse()[name]
                listOfNotNull(token)
            }
        }

package org.javacs.kt.signaturehelp

import org.eclipse.lsp4j.ParameterInformation
import org.eclipse.lsp4j.SignatureHelp
import org.eclipse.lsp4j.MarkupContent
import org.eclipse.lsp4j.SignatureInformation
import org.eclipse.lsp4j.jsonrpc.messages.Either
import org.javacs.kt.CompiledFile
import org.javacs.kt.completion.DECL_RENDERER
import org.javacs.kt.completion.identifierOverloads
import org.javacs.kt.completion.memberOverloads
import org.javacs.kt.docs.findDoc
import org.javacs.kt.util.findParent
import org.javacs.kt.util.nullResult
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.javacs.kt.LOG

fun fetchSignatureHelpAt(file: CompiledFile, cursor: Int): SignatureHelp? {
    val (signatures, activeDeclaration, activeParameter) = getSignatureTriplet(file, cursor) ?: return nullResult("No call around ${file.describePosition(cursor)}")
    return SignatureHelp(signatures, activeDeclaration, activeParameter)
}

/**
 * Returns the doc string of the first found CallableDescriptor
 *
 * Avoids fetching the SignatureHelp triplet due to an OutOfBoundsException that can occur due to the offset difference math.
 * When hovering, the cursor param is set to the doc offset where the mouse is hovering over, rather than where the actual cursor is,
 * hence this is seen to cause issues when slicing the param list string
 */
fun getDocString(file: CompiledFile, cursor: Int): String {
    val signatures = getSignatures(file, cursor)
    if (signatures == null || signatures.size == 0 || signatures[0].documentation == null)
        return ""
    return if (signatures[0].documentation.isLeft()) signatures[0].documentation.left else ""
}

// TODO better function name?
private fun getSignatureTriplet(file: CompiledFile, cursor: Int): Triple<List<SignatureInformation>, Int, Int>? {
    val call = file.parseAtPoint(cursor)?.findParent<KtCallExpression>() ?: return null
    val candidates = candidates(call, file)
    val activeDeclaration = activeDeclaration(call, candidates)
    val activeParameter = activeParameter(call, cursor)
    val signatures = candidates.map(::toSignature)

    return Triple(signatures, activeDeclaration, activeParameter)
}

private fun getSignatures(file: CompiledFile, cursor: Int): List<SignatureInformation>? {
    val call = file.parseAtPoint(cursor)?.findParent<KtCallExpression>() ?: return null
    val candidates = candidates(call, file)
    return candidates.map(::toSignature)
}

private fun toSignature(desc: CallableDescriptor): SignatureInformation {
    val label = DECL_RENDERER.render(desc)
    val params = desc.valueParameters.map(::toParameter)
    val docstring = docstring(desc)

    return SignatureInformation(label, docstring, params)
}

private fun toParameter(param: ValueParameterDescriptor): ParameterInformation {
    val label = DECL_RENDERER.renderValueParameters(listOf(param), false)
    val removeParens = label.substring(1, label.length - 1)
    val docstring = docstring(param)

    return ParameterInformation(removeParens, docstring)
}

private fun docstring(declaration: DeclarationDescriptorWithSource): String {
    val doc = findDoc(declaration) ?: return ""

    return doc.getContent().trim()
}

private fun candidates(call: KtCallExpression, file: CompiledFile): List<CallableDescriptor> {
    val target = call.calleeExpression!!
    val identifier = target.text // TODO when call is foo.bar(), is this bar or foo.bar ?
    val dotParent = target.findParent<KtDotQualifiedExpression>()
    if (dotParent != null) {
        val type = file.typeAtPoint(dotParent.receiverExpression.startOffset) ?: return emptyList()

        return memberOverloads(type, identifier).toList()
    }
    val idParent = target.findParent<KtNameReferenceExpression>()
    if (idParent != null) {
        val scope = file.scopeAtPoint(idParent.startOffset) ?: return emptyList()

        return identifierOverloads(scope, identifier).toList()
    }
    return emptyList()
}

private fun activeDeclaration(call: KtCallExpression, candidates: List<CallableDescriptor>): Int {
    return candidates.indexOfFirst { isCompatibleWith(call, it) }
}

private fun isCompatibleWith(call: KtCallExpression, candidate: CallableDescriptor): Boolean {
    val argumentList = call.valueArgumentList ?: return true
    val nArguments = argumentList.text.count { it == ',' } + 1
    if (nArguments > candidate.valueParameters.size)
        return false

    for (arg in call.valueArguments) {
        if (arg.isNamed()) {
            if (candidate.valueParameters.none { arg.name == it.name.identifier })
                return false
        }
        // TODO consider types as well
    }

    return true
}

private fun activeParameter(call: KtCallExpression, cursor: Int): Int {
    val args = call.valueArgumentList ?: return -1
    val text = args.text
    if (text.length == 2)
        return 0
    val min = Math.min(args.textRange.startOffset, cursor)
    val max = Math.max(args.textRange.startOffset, cursor)
    val beforeCursor = text.subSequence(0, max-min)
    return beforeCursor.count { it == ','}
}

package org.javacs.kt.symbols

import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.eclipse.lsp4j.Location
import org.eclipse.lsp4j.SymbolInformation
import org.eclipse.lsp4j.SymbolKind
import org.eclipse.lsp4j.DocumentSymbol
import org.eclipse.lsp4j.jsonrpc.messages.Either
import org.javacs.kt.SourcePath
import org.javacs.kt.completion.containsCharactersInOrder
import org.javacs.kt.position.range
import org.javacs.kt.util.preOrderTraversal
import org.javacs.kt.util.toPath
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents

fun documentSymbols(file: KtFile): List<Either<SymbolInformation, DocumentSymbol>> =
        doDocumentSymbols(file).map { Either.forRight<SymbolInformation, DocumentSymbol>(it) }

private fun doDocumentSymbols(element: PsiElement): List<DocumentSymbol> {
    val children = element.children.flatMap(::doDocumentSymbols)

    return pickImportantElements(element, true)?.let { currentDecl ->
        val file = element.containingFile
        val span = range(file.text, currentDecl.textRange)
        val nameIdentifier = currentDecl.nameIdentifier
        val nameSpan = nameIdentifier?.let { range(file.text, it.textRange) } ?: span
        val symbol = DocumentSymbol(currentDecl.name ?: "<anonymous>", symbolKind(currentDecl), span, nameSpan, null, children)
        listOf(symbol)
    } ?: children
}

private const val MAX_SYMBOLS = 50

fun workspaceSymbols(query: String, sp: SourcePath): List<SymbolInformation> =
        doWorkspaceSymbols(sp)
                .filter { containsCharactersInOrder(it.name!!, query, false) }
                .mapNotNull(::symbolInformation)
                .take(MAX_SYMBOLS)
                .toList()

private fun doWorkspaceSymbols(sp: SourcePath): Sequence<KtNamedDeclaration> =
        sp.all().asSequence().flatMap(::fileSymbols)

private fun fileSymbols(file: KtFile): Sequence<KtNamedDeclaration> =
        file.preOrderTraversal().mapNotNull { pickImportantElements(it, false) }

private fun pickImportantElements(node: PsiElement, includeLocals: Boolean): KtNamedDeclaration? =
        when (node) {
            is KtClassOrObject -> if (node.name == null) null else node
            is KtTypeAlias -> node
            is KtConstructor<*> -> node
            is KtNamedFunction -> if (!node.isLocal || includeLocals) node else null
            is KtProperty -> if (!node.isLocal || includeLocals) node else null
            is KtVariableDeclaration -> if (includeLocals) node else null
            else -> null
        }

private fun symbolInformation(d: KtNamedDeclaration): SymbolInformation? {
    val name = d.name ?: return null

    return SymbolInformation(name, symbolKind(d), symbolLocation(d), symbolContainer(d))
}

private fun symbolKind(d: KtNamedDeclaration): SymbolKind =
        when (d) {
            is KtClassOrObject -> SymbolKind.Class
            is KtTypeAlias -> SymbolKind.Interface
            is KtConstructor<*> -> SymbolKind.Constructor
            is KtNamedFunction -> SymbolKind.Function
            is KtProperty -> SymbolKind.Property
            is KtVariableDeclaration -> SymbolKind.Variable
            else -> throw IllegalArgumentException("Unexpected symbol $d")
        }

private fun symbolLocation(d: KtNamedDeclaration): Location {
    val file = d.containingFile
    val uri = file.toPath().toUri().toString()
    val range = range(file.text, d.textRange)

    return Location(uri, range)
}

private fun symbolContainer(d: KtNamedDeclaration): String? =
        d.parents
                .filterIsInstance<KtNamedDeclaration>()
                .firstOrNull()
                ?.fqName.toString()

/*
 * Source: https://github.com/JetBrains/kotlin-eclipse/blob/d66b9915a6803ac7adb955af6a8dae76f28ed7e5/kotlin-eclipse-core/src/org/jetbrains/kotlin/core/model/KotlinNullableNotNullManager.kt
 * Licensed under http://www.apache.org/licenses/LICENSE-2.0
 */
package org.javacs.kt.util

import org.jetbrains.kotlin.com.intellij.codeInsight.NullabilityAnnotationInfo
import org.jetbrains.kotlin.com.intellij.codeInsight.NullableNotNullManager
import org.jetbrains.kotlin.com.intellij.openapi.project.Project
import org.jetbrains.kotlin.com.intellij.psi.PsiAnnotation
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiModifierListOwner

class KotlinNullableNotNullManager(project: Project) : NullableNotNullManager(project) {
    private val nullablesList = mutableListOf<String>()
    private val notNullsList = mutableListOf<String>()

    override fun getNullables(): List<String> = nullablesList

    override fun setInstrumentedNotNulls(names: MutableList<String>) {}

    override fun getInstrumentedNotNulls(): List<String> = emptyList()

    override fun isJsr305Default(annotation: PsiAnnotation, placeTargetTypes: Array<out PsiAnnotation.TargetType>): NullabilityAnnotationInfo? = null

    override fun setNullables(vararg annotations: String) {
        nullablesList.clear()
        nullablesList.addAll(annotations)
    }

    override fun getDefaultNotNull(): String = "NotNull"

    override fun getNotNulls(): List<String> = notNullsList

    override fun getDefaultNullable(): String = "Nullable"

    override fun setDefaultNotNull(defaultNotNull: String) {
    }

    override fun setNotNulls(vararg annotations: String) {
        notNullsList.clear()
        notNullsList.addAll(annotations)
    }

    //    For now we get unresolved psi elements and as a result annotations qualified names are short
    init {
        setNotNulls("NotNull")
        setNullables("Nullable")
    }

    override fun setDefaultNullable(defaultNullable: String) {
    }

    override fun hasHardcodedContracts(element: PsiElement): Boolean = false

    override fun isNotNull(owner: PsiModifierListOwner, checkBases: Boolean): Boolean {
        val notNullAnnotations = notNulls.toSet()
        return owner.modifierList?.annotations?.any { annotation ->
            annotation.qualifiedName in notNullAnnotations
        } ?: false
    }

    override fun isNullable(owner: PsiModifierListOwner, checkBases: Boolean) = !isNotNull(owner, checkBases)
}

package org.javacs.kt.util

import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.javacs.kt.LOG

object LoggingMessageCollector: MessageCollector {
	override fun clear() {}

	override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
		LOG.debug("Kotlin compiler: [{}] {} @ {}", severity, message, location)
	}

	override fun hasErrors() = false
}

package org.javacs.kt.util

import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.com.intellij.psi.PsiFile
import java.nio.file.Path

inline fun<reified Find> PsiElement.findParent() =
        this.parentsWithSelf.filterIsInstance<Find>().firstOrNull()

fun PsiElement.preOrderTraversal(): Sequence<PsiElement> {
    val root = this

    return sequence {
        yield(root)

        for (child in root.children) {
            yieldAll(child.preOrderTraversal())
        }
    }
}

fun PsiFile.toPath(): Path =
        winCompatiblePathOf(this.originalFile.viewProvider.virtualFile.path)

package org.javacs.kt.classpath

import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
import java.util.function.BiPredicate
import org.javacs.kt.util.tryResolving
import org.javacs.kt.util.findCommandOnPath
import java.nio.file.Paths

/** Backup classpath that find Kotlin in the user's Maven/Gradle home or kotlinc's libraries folder. */
object BackupClassPathResolver : ClassPathResolver {
    override val resolverType: String = "Backup"
    override val classpath: Set<Path> get() = findKotlinStdlib()?.let { setOf(it) }.orEmpty()
}

fun findKotlinStdlib(): Path? =
    findLocalArtifact("org.jetbrains.kotlin", "kotlin-stdlib")
    ?: findKotlinCliCompilerLibrary("kotlin-stdlib")

private fun findLocalArtifact(group: String, artifact: String) =
    tryResolving("$artifact using Maven") { tryFindingLocalArtifactUsing(group, artifact, findLocalArtifactDirUsingMaven(group, artifact)) }
    ?: tryResolving("$artifact using Gradle") { tryFindingLocalArtifactUsing(group, artifact, findLocalArtifactDirUsingGradle(group, artifact)) }

private fun tryFindingLocalArtifactUsing(@Suppress("UNUSED_PARAMETER") group: String, artifact: String, artifactDirResolution: LocalArtifactDirectoryResolution): Path? {
    val isCorrectArtifact = BiPredicate<Path, BasicFileAttributes> { file, _ ->
        val name = file.fileName.toString()
        when (artifactDirResolution.buildTool) {
            "Maven" -> {
                val version = file.parent.fileName.toString()
                val expected = "${artifact}-${version}.jar"
                name == expected
            }
            else -> name.startsWith(artifact) && name.endsWith(".jar")
        }
    }
    return Files.list(artifactDirResolution.artifactDir)
        .sorted(::compareVersions)
        .findFirst()
        .orElse(null)
        ?.let {
            Files.find(artifactDirResolution.artifactDir, 3, isCorrectArtifact)
                .findFirst()
                .orElse(null)
        }
}

private data class LocalArtifactDirectoryResolution(val artifactDir: Path?, val buildTool: String)

/** Tries to find the Kotlin command line compiler's standard library. */
private fun findKotlinCliCompilerLibrary(name: String): Path? =
    findCommandOnPath("kotlinc")
        ?.toRealPath()
        ?.parent // bin
        ?.parent // libexec
        ?.resolve("lib")
        ?.takeIf { Files.exists(it) }
        ?.let(Files::list)
        ?.filter { it.fileName.toString() == "$name.jar" }
        ?.findFirst()
        ?.orElse(null)

private fun Path.existsOrNull() =
    if (Files.exists(this)) this else null

private fun findLocalArtifactDirUsingMaven(group: String, artifact: String) =
    LocalArtifactDirectoryResolution(mavenHome.resolve("repository")
        ?.resolve(group.replace('.', File.separatorChar))
        ?.resolve(artifact)
        ?.existsOrNull(), "Maven")

private fun findLocalArtifactDirUsingGradle(group: String, artifact: String) =
    LocalArtifactDirectoryResolution(gradleCaches
        ?.resolve(group)
        ?.resolve(artifact)
        ?.existsOrNull(), "Gradle")

// TODO: Resolve the gradleCaches dynamically instead of hardcoding this path
private val gradleCaches by lazy {
    gradleHome.resolve("caches")
        .resolveStartingWith("modules")
        .resolveStartingWith("files")
}

private fun Path.resolveStartingWith(prefix: String) = Files.list(this).filter { it.fileName.toString().startsWith(prefix) }.findFirst().orElse(null)

private fun compareVersions(left: Path, right: Path): Int {
    val leftVersion = extractVersion(left)
    val rightVersion = extractVersion(right)

    for (i in 0 until Math.min(leftVersion.size, rightVersion.size)) {
        val leftRev = leftVersion[i].reversed()
        val rightRev = rightVersion[i].reversed()
        val compare = leftRev.compareTo(rightRev)
        if (compare != 0)
            return -compare
    }

    return -leftVersion.size.compareTo(rightVersion.size)
}
private fun extractVersion(artifactVersionDir: Path): List<String> {
    return artifactVersionDir.toString().split(".")
}

package org.javacs.kt.classpath

import org.javacs.kt.LOG
import java.nio.file.Path

/** A source for creating class paths */
interface ClassPathResolver {
    val resolverType: String

    val classpath: Set<Path> // may throw exceptions
    val classpathOrEmpty: Set<Path> // does not throw exceptions
        get() = try {
            classpath
        } catch (e: Exception) {
            LOG.warn("Could not resolve classpath using {}: {}", resolverType, e.message)
            emptySet<Path>()
        }

    val buildScriptClasspath: Set<Path>
        get() = emptySet<Path>()
    val buildScriptClasspathOrEmpty: Set<Path>
        get() = try {
            buildScriptClasspath
        } catch (e: Exception) {
            LOG.warn("Could not resolve buildscript classpath using {}: {}", resolverType, e.message)
            emptySet<Path>()
        }

    companion object {
        /** A default empty classpath implementation */
        val empty = object : ClassPathResolver {
            override val resolverType = "[]"
            override val classpath = emptySet<Path>()
        }
    }
}

val Sequence<ClassPathResolver>.joined get() = fold(ClassPathResolver.empty) { accum, next -> accum + next }

val Collection<ClassPathResolver>.joined get() = fold(ClassPathResolver.empty) { accum, next -> accum + next }

/** Combines two classpath resolvers. */
operator fun ClassPathResolver.plus(other: ClassPathResolver): ClassPathResolver = UnionClassPathResolver(this, other)

/** Uses the left-hand classpath if not empty, otherwise uses the right. */
infix fun ClassPathResolver.or(other: ClassPathResolver): ClassPathResolver = FirstNonEmptyClassPathResolver(this, other)

/** The union of two class path resolvers. */
internal class UnionClassPathResolver(val lhs: ClassPathResolver, val rhs: ClassPathResolver) : ClassPathResolver {
    override val resolverType: String get() = "(${lhs.resolverType} + ${rhs.resolverType})"
    override val classpath get() = lhs.classpath + rhs.classpath
    override val classpathOrEmpty get() = lhs.classpathOrEmpty + rhs.classpathOrEmpty
    override val buildScriptClasspath get() = lhs.buildScriptClasspath + rhs.buildScriptClasspath
    override val buildScriptClasspathOrEmpty get() = lhs.buildScriptClasspathOrEmpty + rhs.buildScriptClasspathOrEmpty
}

internal class FirstNonEmptyClassPathResolver(val lhs: ClassPathResolver, val rhs: ClassPathResolver) : ClassPathResolver {
    override val resolverType: String get() = "(${lhs.resolverType} or ${rhs.resolverType})"
    override val classpath get() = lhs.classpath.takeIf { it.isNotEmpty() } ?: rhs.classpath
    override val classpathOrEmpty get() = lhs.classpathOrEmpty.takeIf { it.isNotEmpty() } ?: rhs.classpathOrEmpty
    override val buildScriptClasspath get() = lhs.buildScriptClasspath.takeIf { it.isNotEmpty() } ?: rhs.buildScriptClasspath
    override val buildScriptClasspathOrEmpty get() = lhs.buildScriptClasspathOrEmpty.takeIf { it.isNotEmpty() } ?: rhs.buildScriptClasspathOrEmpty
}

package org.javacs.kt.classpath

import org.javacs.kt.LOG
import java.nio.file.Path
import java.nio.file.PathMatcher
import java.nio.file.Files
import java.nio.file.FileSystems

fun defaultClassPathResolver(workspaceRoots: Collection<Path>): ClassPathResolver =
    WithStdlibResolver(
        ShellClassPathResolver.global(workspaceRoots.firstOrNull())
            .or(workspaceRoots.asSequence().flatMap(::workspaceResolvers).joined)
    ).or(BackupClassPathResolver)

/** Searches the workspace for all files that could provide classpath info. */
private fun workspaceResolvers(workspaceRoot: Path): Sequence<ClassPathResolver> {
    val ignored: List<PathMatcher> = ignoredPathPatterns(workspaceRoot.resolve(".gitignore"))
    return folderResolvers(workspaceRoot, workspaceRoot, ignored).asSequence()
}

/** Searches the folder for all build-files. */
private fun folderResolvers(workspaceRoot: Path, folder: Path, ignored: List<PathMatcher>): Collection<ClassPathResolver> {
    var resolvers = mutableListOf<ClassPathResolver>()

    for (file in Files.list(folder)) {
        // Only test whether non-ignored file is a build-file
        if (ignored.none { it.matches(workspaceRoot.relativize(file)) }) {
            val resolver = asClassPathProvider(file)
            if (resolver != null) {
                resolvers.add(resolver)
                break
            } else if (Files.isDirectory(file)) {
                resolvers.addAll(folderResolvers(workspaceRoot, file, ignored))
            }
        }
    }

    return resolvers
}

/** Tries to read glob patterns from a gitignore. */
private fun ignoredPathPatterns(path: Path): List<PathMatcher> =
    path.toFile()
        .takeIf { it.exists() }
        ?.readLines()
        ?.map { it.trim() }
        ?.filter { it.isNotEmpty() && !it.startsWith("#") }
        ?.let { it + listOf(
            // Patterns that are ignored by default
            ".git"
        ) }
        ?.mapNotNull { try {
            LOG.debug("Adding ignore pattern '{}' from {}", it, path)
            FileSystems.getDefault().getPathMatcher("glob:$it")
        } catch (e: Exception) {
            LOG.warn("Did not recognize gitignore pattern: '{}' ({})", it, e.message)
            null
        } }
        ?: emptyList<PathMatcher>()

/** Tries to create a classpath resolver from a file using as many sources as possible */
private fun asClassPathProvider(path: Path): ClassPathResolver? =
    MavenClassPathResolver.maybeCreate(path)
        ?: GradleClassPathResolver.maybeCreate(path)
        ?: ShellClassPathResolver.maybeCreate(path)

package org.javacs.kt.classpath

import org.javacs.kt.LOG
import org.javacs.kt.util.firstNonNull
import org.javacs.kt.util.tryResolving
import org.javacs.kt.util.execAndReadStdoutAndStderr
import org.javacs.kt.util.KotlinLSException
import org.javacs.kt.util.isOSWindows
import org.javacs.kt.util.findCommandOnPath
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

internal class GradleClassPathResolver(private val path: Path, private val includeKotlinDSL: Boolean): ClassPathResolver {
    override val resolverType: String = "Gradle"
    private val projectDirectory: Path get() = path.getParent()
    override val classpath: Set<Path> get() {
        val scripts = listOf("projectClassPathFinder.gradle")
        val tasks = listOf("kotlinLSPProjectDeps")

        return readDependenciesViaGradleCLI(projectDirectory, scripts, tasks)
            .apply { if (isNotEmpty()) LOG.info("Successfully resolved dependencies for '${projectDirectory.fileName}' using Gradle") }
    }
    override val buildScriptClasspath: Set<Path> get() {
        return if (includeKotlinDSL) {
            val scripts = listOf("kotlinDSLClassPathFinder.gradle")
            val tasks = listOf("kotlinLSPKotlinDSLDeps")

            return readDependenciesViaGradleCLI(projectDirectory, scripts, tasks)
                .apply { if (isNotEmpty()) LOG.info("Successfully resolved build script dependencies for '${projectDirectory.fileName}' using Gradle") }
        } else {
            emptySet()
        }
    }

    companion object {
        /** Create a Gradle resolver if a file is a pom. */
        fun maybeCreate(file: Path): GradleClassPathResolver? =
            file.takeIf { file.endsWith("build.gradle") || file.endsWith("build.gradle.kts") }
                ?.let { GradleClassPathResolver(it, includeKotlinDSL = file.toString().endsWith(".kts")) }
    }
}

private fun gradleScriptToTempFile(scriptName: String, deleteOnExit: Boolean = false): File {
    val config = File.createTempFile("classpath", ".gradle")
    if (deleteOnExit) {
        config.deleteOnExit()
    }

    LOG.debug("Creating temporary gradle file {}", config.absolutePath)

    config.bufferedWriter().use { configWriter ->
        ClassLoader.getSystemResourceAsStream(scriptName).bufferedReader().use { configReader ->
            configReader.copyTo(configWriter)
        }
    }

    return config
}

private fun getGradleCommand(workspace: Path): Path {
    val wrapperName = if (isOSWindows()) "gradlew.bat" else "gradlew"
    val wrapper = workspace.resolve(wrapperName).toAbsolutePath()
    if (Files.exists(wrapper)) {
        return wrapper
    } else {
        return workspace.parent?.let(::getGradleCommand)
            ?: findCommandOnPath("gradle")
            ?: throw KotlinLSException("Could not find 'gradle' on PATH")
    }
}

private fun readDependenciesViaGradleCLI(projectDirectory: Path, gradleScripts: List<String>, gradleTasks: List<String>): Set<Path> {
    LOG.info("Resolving dependencies for '{}' through Gradle's CLI using tasks {}...", projectDirectory.fileName, gradleTasks)

    val tmpScripts = gradleScripts.map { gradleScriptToTempFile(it, deleteOnExit = false).toPath().toAbsolutePath() }
    val gradle = getGradleCommand(projectDirectory)

    val command = "$gradle ${tmpScripts.map { "-I $it" }.joinToString(" ")} ${gradleTasks.joinToString(" ")} --console=plain"
    val dependencies = findGradleCLIDependencies(command, projectDirectory)
        ?.also { LOG.debug("Classpath for task {}", it) }
        .orEmpty()

    tmpScripts.forEach(Files::delete)
    return dependencies
}

private fun findGradleCLIDependencies(command: String, projectDirectory: Path): Set<Path>? {
    val (result, errors) = execAndReadStdoutAndStderr(command, projectDirectory)
    LOG.debug(result)
    if ("FAILURE: Build failed" in errors) {
        LOG.warn("Gradle task failed: {}", errors.lines().firstOrNull())
    }
    return parseGradleCLIDependencies(result)
}

private val artifactPattern by lazy { "kotlin-lsp-gradle (.+)(?:\r?\n)".toRegex() }
private val gradleErrorWherePattern by lazy { "\\*\\s+Where:[\r\n]+(\\S\\.*)".toRegex() }

private fun parseGradleCLIDependencies(output: String): Set<Path>? {
    LOG.debug(output)
    val artifacts = artifactPattern.findAll(output)
        .mapNotNull { Paths.get(it.groups[1]?.value) }
        .filterNotNull()
    return artifacts.toSet()
}

package org.javacs.kt.classpath

import org.javacs.kt.util.userHome

internal val gradleHome = userHome.resolve(".gradle")
internal val mavenHome = userHome.resolve(".m2")

package org.javacs.kt.classpath

import org.javacs.kt.LOG
import org.javacs.kt.util.findCommandOnPath
import org.javacs.kt.util.execAndReadStdoutAndStderr
import java.nio.file.Path
import java.nio.file.Files
import java.io.File

/** Resolver for reading maven dependencies */
internal class MavenClassPathResolver private constructor(private val pom: Path) : ClassPathResolver {
    override val resolverType: String = "Maven"
    override val classpath: Set<Path> get() {
        val mavenOutput = generateMavenDependencyList(pom)
        val artifacts = readMavenDependencyList(mavenOutput)

        when {
            artifacts.isEmpty() -> LOG.warn("No artifacts found in {}", pom)
            artifacts.size < 5 -> LOG.info("Found {} in {}", artifacts, pom)
            else -> LOG.info("Found {} artifacts in {}", artifacts.size, pom)
        }

        Files.deleteIfExists(mavenOutput)
        return artifacts.mapNotNull { findMavenArtifact(it, false) }.toSet()
    }

    companion object {
        /** Create a maven resolver if a file is a pom. */
        fun maybeCreate(file: Path): MavenClassPathResolver? =
            file.takeIf { it.endsWith("pom.xml") }?.let { MavenClassPathResolver(it) }
    }
}

private val artifactPattern = "^[^:]+:(?:[^:]+:)+[^:]+".toRegex()

private fun readMavenDependencyList(mavenOutput: Path): Set<Artifact> =
    mavenOutput.toFile()
        .readLines()
        .filter { it.matches(artifactPattern) }
        .map { parseMavenArtifact(it) }
        .toSet()

private fun findMavenArtifact(a: Artifact, source: Boolean): Path? {
    val result = mavenHome.resolve("repository")
        .resolve(a.group.replace('.', File.separatorChar))
        .resolve(a.artifact)
        .resolve(a.version)
        .resolve(mavenJarName(a, source))

    return if (Files.exists(result))
        result
    else {
        LOG.warn("Couldn't find {} in {}", a, result)
        null
    }
}

private fun mavenJarName(a: Artifact, source: Boolean) =
    if (source) "${a.artifact}-${a.version}-sources.jar"
    else "${a.artifact}-${a.version}.jar"

private fun generateMavenDependencyList(pom: Path): Path {
    val mavenOutput = Files.createTempFile("deps", ".txt")
    val command = "$mvnCommand dependency:list -DincludeScope=test -DoutputFile=$mavenOutput"
    val workingDirectory = pom.toAbsolutePath().parent
    LOG.info("Run {} in {}", command, workingDirectory)
    val (result, errors) = execAndReadStdoutAndStderr(command, workingDirectory)
    LOG.debug(result)
    if ("BUILD FAILURE" in errors) {
        LOG.warn("Maven task failed: {}", errors.lines().firstOrNull())
    }
    return mavenOutput
}

private val mvnCommand: Path by lazy {
    requireNotNull(findCommandOnPath("mvn")) { "Unable to find the 'mvn' command" }
}

fun parseMavenArtifact(rawArtifact: String, version: String? = null): Artifact {
    val parts = rawArtifact.trim().split(':')

    return when (parts.size) {
        3 -> Artifact(
            group = parts[0],
            artifact = parts[1],
            packaging = null,
            classifier = null,
            version = version ?: parts[2],
            scope = null
        )
        4 -> Artifact(
            group = parts[0],
            artifact = parts[1],
            packaging = parts[2],
            classifier = null,
            version = version ?: parts[3],
            scope = null
        )
        5 -> Artifact(
            group = parts[0],
            artifact = parts[1],
            packaging = parts[2],
            classifier = null,
            version = version ?: parts[3],
            scope = parts[4]
        )
        6 -> Artifact(
            group = parts[0],
            artifact = parts[1],
            packaging = parts[2],
            classifier = parts[3],
            version = version ?: parts[4],
            scope = parts[5]
        )
        else -> throw IllegalArgumentException("$rawArtifact is not a properly formed Maven/Gradle artifact")
    }
}

data class Artifact(
    val group: String,
    val artifact: String,
    val packaging: String?,
    val classifier: String?,
    val version: String,
    val scope: String?
) {
    override fun toString() = "$group:$artifact:$version"
}

package org.javacs.kt.classpath

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import org.javacs.kt.util.userHome
import org.javacs.kt.LOG

/** Executes a shell script to determine the classpath */
internal class ShellClassPathResolver(
    private val script: Path,
    private val workingDir: Path? = null
) : ClassPathResolver {
    override val resolverType: String = "Shell"
    override val classpath: Set<Path> get() {
        val workingDirectory = workingDir?.toFile() ?: script.toAbsolutePath().parent.toFile()
        val cmd = script.toString()
        LOG.info("Run {} in {}", cmd, workingDirectory)
        val process = Runtime.getRuntime().exec(cmd, null, workingDirectory)

        return process.inputStream.bufferedReader().readText()
            .split(':')
            .asSequence()
            .map { it.trim() }
            .filter { it.isNotEmpty() }
            .map { Paths.get(it) }
            .toSet()
    }

    companion object {
        /** Create a shell resolver if a file is a pom. */
        fun maybeCreate(file: Path): ShellClassPathResolver? =
            file.takeIf { it.endsWith("kotlinLspClasspath.sh") }?.let { ShellClassPathResolver(it) }

        /** The root directory for config files. */
        private val globalConfigRoot: Path =
            System.getenv("XDG_CONFIG_HOME")?.let { Paths.get(it) } ?: userHome.resolve(".config")

        /** Returns the ShellClassPathResolver for the global home directory shell script. */
        fun global(workingDir: Path?): ClassPathResolver =
            globalConfigRoot.resolve("KotlinLanguageServer").resolve("classpath.sh")
                .takeIf { Files.exists(it) }
                ?.let { ShellClassPathResolver(it, workingDir) }
                ?: ClassPathResolver.empty
    }
}

package org.javacs.kt.classpath

import java.nio.file.Path

/** A classpath resolver that ensures another resolver contains the stdlib */
internal class WithStdlibResolver(private val wrapped: ClassPathResolver) : ClassPathResolver {
    override val resolverType: String get() = "Stdlib + ${wrapped.resolverType}"
    override val classpath: Set<Path> get() = wrapWithStdlib(wrapped.classpath)
    override val classpathOrEmpty: Set<Path> get() = wrapWithStdlib(wrapped.classpathOrEmpty)
    override val buildScriptClasspath: Set<Path> get() = wrapWithStdlib(wrapped.buildScriptClasspath)
    override val buildScriptClasspathOrEmpty: Set<Path> get() = wrapWithStdlib(wrapped.buildScriptClasspathOrEmpty)
}

private fun wrapWithStdlib(paths: Set<Path>): Set<Path> {
    // Ensure that there is exactly one kotlin-stdlib present, and/or exactly one of kotlin-stdlib-common, -jdk8, etc.
    val isStdlib: ((Path) -> Boolean) = {
        val pathString = it.toString()
        pathString.contains("kotlin-stdlib") && !pathString.contains("kotlin-stdlib-common")
    }

    val linkedStdLibs = paths.filter(isStdlib)
        .mapNotNull { StdLibItem.from(it) }
        .groupBy { it.key }
        .map { candidates ->
            // For each "kotlin-stdlib-blah", use the newest.  This may not be correct behavior if the project has lots of
            // conflicting dependencies, but in general should get enough of the stdlib loaded that we can display errors

            candidates.value.sortedWith(
                compareByDescending<StdLibItem> { it.major } then
                    compareByDescending { it.minor } then
                    compareByDescending { it.patch }
            ).first().path
        }

    val stdlibs = if (linkedStdLibs.isNotEmpty()) {
        linkedStdLibs
    } else {
        findKotlinStdlib()?.let { listOf(it) } ?: listOf()
    }

    return paths.filterNot(isStdlib).union(stdlibs)
}

private data class StdLibItem(
    val key : String,
    val major : Int,
    val minor: Int,
    val patch : Int,
    val path: Path
) {
    companion object {
        // Matches names like: "kotlin-stdlib-jdk7-1.2.51.jar"
        val parser = Regex("""(kotlin-stdlib(-[^-]+)?)-(\d+)\.(\d+)\.(\d+)\.jar""")

        fun from(path: Path) : StdLibItem? {
            return parser.matchEntire(path.fileName.toString())?.let { match ->
                StdLibItem(
                    key = match.groups[1]?.value ?: match.groups[0]?.value!!,
                    major = match.groups[3]?.value?.toInt() ?: 0,
                    minor = match.groups[4]?.value?.toInt() ?: 0,
                    patch = match.groups[5]?.value?.toInt() ?: 0,
                    path = path
                )
            }
        }
    }
}

package org.javacs.kt.util

import org.javacs.kt.LOG
import java.time.Duration
import java.util.function.Supplier
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

private var threadCount = 0

class AsyncExecutor {
	private val workerThread = Executors.newSingleThreadExecutor { Thread(it, "async${threadCount++}") }

    fun execute(task: () -> Unit) =
            CompletableFuture.runAsync(Runnable(task), workerThread)

	fun <R> compute(task: () -> R) =
			CompletableFuture.supplyAsync(Supplier(task), workerThread)

	fun <R> computeOr(defaultValue: R, task: () -> R?) =
			CompletableFuture.supplyAsync(Supplier {
				try {
					task() ?: defaultValue
				} catch (e: Exception) {
					defaultValue
				}
			}, workerThread)

	fun shutdown(awaitTermination: Boolean) {
		workerThread.shutdown()
		if (awaitTermination) {
			LOG.info("Awaiting async termination...")
			workerThread.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS)
		}
	}
}

package org.javacs.kt.util

import org.javacs.kt.LOG
import java.time.Duration
import java.util.function.Supplier
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future

private var threadCount = 0

class Debouncer(
    private val delay: Duration,
    private val executor: ScheduledExecutorService = Executors.newScheduledThreadPool(1) {
        Thread(it, "debounce${threadCount++}")
    }
) {
    private val delayMs = delay.toMillis()
    private var pendingTask: Future<*>? = null

    fun submitImmediately(task: (cancelCallback: () -> Boolean) -> Unit) {
        pendingTask?.cancel(false)
        val currentTaskRef = AtomicReference<Future<*>>()
        val currentTask = executor.submit { task { currentTaskRef.get()?.isCancelled() ?: false } }
        currentTaskRef.set(currentTask)
        pendingTask = currentTask
    }

    fun schedule(task: (cancelCallback: () -> Boolean) -> Unit) {
        pendingTask?.cancel(false)
        val currentTaskRef = AtomicReference<Future<*>>()
        val currentTask = executor.schedule({ task { currentTaskRef.get()?.isCancelled() ?: false } }, delayMs, TimeUnit.MILLISECONDS)
        currentTaskRef.set(currentTask)
        pendingTask = currentTask
    }

    fun waitForPendingTask() {
        pendingTask?.get()
    }

    fun shutdown(awaitTermination: Boolean) {
        executor.shutdown()
        if (awaitTermination) {
			LOG.info("Awaiting debouncer termination...")
			executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS)
		}
    }
}

package org.javacs.kt.util

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.util.function.Consumer

class DelegatePrintStream(private val delegate: (String) -> Unit): PrintStream(ByteArrayOutputStream(0)) {
	private val newLine = System.lineSeparator()

	override fun write(c: Int) = delegate((c.toChar()).toString())

	override fun write(buf: ByteArray, off: Int, len: Int) {
		if (len > 0 && buf.size > 0) {
			delegate(String(buf, off, len))
		}
	}

	override fun append(csq: CharSequence): PrintStream {
		delegate(csq.toString())
		return this
	}

	override fun append(csq: CharSequence, start: Int, end: Int): PrintStream {
		delegate(csq.subSequence(start, end).toString())
		return this
	}

	override fun append(c:Char): PrintStream {
		delegate((c).toString())
		return this
	}

	override fun print(x: Boolean) = delegate(x.toString())

	override fun print(x: Char) = delegate(x.toString())

	override fun print(x: Int) = delegate(x.toString())

	override fun print(x: Long) = delegate(x.toString())

	override fun print(x: Float) = delegate(x.toString())

	override fun print(x: Double) = delegate(x.toString())

	override fun print(s: CharArray) = delegate(String(s))

	override fun print(s: String) = delegate(s)

	override fun print(obj: Any) = delegate(obj.toString())

	override fun println() = delegate(newLine)

	override fun println(x: Boolean) = delegate(x.toString() + newLine)

	override fun println(x: Char) = delegate(x.toString() + newLine)

	override fun println(x: Int) = delegate(x.toString() + newLine)

	override fun println(x: Long) = delegate(x.toString() + newLine)

	override fun println(x: Float) = delegate(x.toString() + newLine)

	override fun println(x: Double) = delegate(x.toString() + newLine)

	override fun println(x: CharArray) = delegate(String(x) + newLine)

	override fun println(x: String) = delegate(x + newLine)

	override fun println(x: Any) = delegate(x.toString() + newLine)
}

package org.javacs.kt.util

import java.io.InputStream
import org.javacs.kt.LOG

class ExitingInputStream(private val delegate: InputStream): InputStream() {
    override fun read(): Int = exitIfNegative { delegate.read() }

    override fun read(b: ByteArray): Int = exitIfNegative { delegate.read(b) }

    override fun read(b: ByteArray, off: Int, len: Int): Int = exitIfNegative { delegate.read(b, off, len) }

    private fun exitIfNegative(call: () -> Int): Int {
        val result = call()

        if (result < 0) {
            LOG.info("System.in has closed, exiting")
            System.exit(0)
        }

        return result
    }
}

package org.javacs.kt.util

class KotlinLSException : RuntimeException {
	constructor(msg: String) : super(msg) {}

	constructor(msg: String, cause: Throwable) : super(msg, cause) {}
}

package org.javacs.kt.util

import org.javacs.kt.LOG
import java.nio.file.Paths
import java.nio.file.Path
import java.io.File

internal val userHome = Paths.get(System.getProperty("user.home"))

internal fun isOSWindows() = (File.separatorChar == '\\')

fun findCommandOnPath(name: String): Path? =
        if (isOSWindows()) windowsCommand(name)
        else unixCommand(name)

private fun windowsCommand(name: String) =
        findExecutableOnPath("$name.cmd")
        ?: findExecutableOnPath("$name.bat")
        ?: findExecutableOnPath("$name.exe")

private fun unixCommand(name: String) = findExecutableOnPath(name)

private fun findExecutableOnPath(fileName: String): Path? {
    for (dir in System.getenv("PATH").split(File.pathSeparator)) {
        val file = File(dir, fileName)

        if (file.isFile && file.canExecute()) {
            LOG.info("Found {} at {}", fileName, file.absolutePath)

            return Paths.get(file.absolutePath)
        }
    }

    return null
}

package org.javacs.kt.util

import java.io.Closeable
import java.nio.file.Path
import java.nio.file.Files

/**
 * A directory in which temporary files may be created.
 * The advantage of using this class over a standard
 * function such as Files.createTempFile is that all
 * temp files in the directory can easily be disposed
 * of once no longer needed.
 */
class TemporaryDirectory(prefix: String = "kotlinlangserver") : Closeable {
    private val dirPath: Path = Files.createTempDirectory(prefix)

    fun createTempFile(prefix: String = "tmp", suffix: String = ""): Path = Files.createTempFile(dirPath, prefix, suffix)

    override fun close() {
        if (Files.exists(dirPath)) {
            dirPath.toFile().deleteRecursively()
        }
    }
}

package org.javacs.kt.util

import java.nio.charset.StandardCharsets
import java.net.URI
import java.net.URLDecoder
import java.nio.file.Path
import java.nio.file.Paths

/**
 * Parse a possibly-percent-encoded URI string.
 * Decoding is necessary since some language clients
 * (including VSCode) invalidly percent-encode colons.
 */
fun parseURI(uri: String): URI = URI.create(runCatching { URLDecoder.decode(uri, StandardCharsets.UTF_8.toString()) }.getOrDefault(uri))

val URI.filePath: Path? get() = runCatching { Paths.get(this) }.getOrNull()

/** Fetches the file extension WITHOUT the dot. */
val URI.fileExtension: String?
    get() {
        val str = toString()
        val dotOffset = str.lastIndexOf(".")
        return if (dotOffset < 0) null else str.substring(dotOffset + 1)
    }

fun describeURIs(uris: Collection<URI>): String =
    if (uris.isEmpty()) "0 files"
    else if (uris.size > 5) "${uris.size} files"
    else uris.map(::describeURI).joinToString(", ")

fun describeURI(uri: String): String = describeURI(parseURI(uri))

fun describeURI(uri: URI): String =
    uri.path?.let {
        val (parent, fileName) = it.partitionAroundLast("/")
        ".../" + parent.substringAfterLast("/") + fileName
    } ?: uri.toString()

package org.javacs.kt.util

import org.javacs.kt.LOG
import java.io.PrintStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.net.URI
import java.util.concurrent.CompletableFuture
import java.util.stream.Stream

fun execAndReadStdout(shellCommand: String, directory: Path): String {
    val process = Runtime.getRuntime().exec(shellCommand, null, directory.toFile())
    val stdout = process.inputStream
    return stdout.bufferedReader().use { it.readText() }
}

fun execAndReadStdoutAndStderr(shellCommand: String, directory: Path): Pair<String, String> {
    val process = Runtime.getRuntime().exec(shellCommand, null, directory.toFile())
    val stdout = process.inputStream
    val stderr = process.errorStream
    var output = ""
    var errors = ""
    val outputThread = Thread { stdout.bufferedReader().use { output += it.readText() } }
    val errorsThread = Thread { stderr.bufferedReader().use { errors += it.readText() } }
    outputThread.start()
    errorsThread.start()
    outputThread.join()
    errorsThread.join()
    return Pair(output, errors)
}

inline fun withCustomStdout(delegateOut: PrintStream, task: () -> Unit) {
    val actualOut = System.out
    System.setOut(delegateOut)
    task()
    System.setOut(actualOut)
}

fun winCompatiblePathOf(path: String): Path {
    if (path.get(2) == ':' && path.get(0) == '/') {
        // Strip leading '/' when dealing with paths on Windows
        return Paths.get(path.substring(1))
    } else {
        return Paths.get(path)
    }
}

fun String.partitionAroundLast(separator: String): Pair<String, String> = lastIndexOf(separator)
    .let { Pair(substring(0, it), substring(it, length)) }

fun Path.replaceExtensionWith(newExtension: String): Path {
	val oldName = fileName.toString()
	val newName = oldName.substring(0, oldName.lastIndexOf(".")) + newExtension
	return resolveSibling(newName)
}

inline fun <T, C : Iterable<T>> C.onEachIndexed(transform: (index: Int, T) -> Unit): C = apply {
    var i = 0
    for (element in this) {
        transform(i, element)
        i++
    }
}

fun <T> noResult(message: String, result: T): T {
    LOG.info(message)
    return result
}

fun <T> noFuture(message: String, contents: T): CompletableFuture<T> = noResult(message, CompletableFuture.completedFuture(contents))

fun <T> emptyResult(message: String): List<T> = noResult(message, emptyList())

fun <T> nullResult(message: String): T? = noResult(message, null)

fun <T> firstNonNull(vararg optionals: () -> T?): T? {
    for (optional in optionals) {
        val result = optional()
        if (result != null) {
            return result
        }
    }
    return null
}

fun <T> nonNull(item: T?, errorMsgIfNull: String): T =
    if (item == null) {
        throw NullPointerException(errorMsgIfNull)
    } else item

inline fun <T> tryResolving(what: String, resolver: () -> T?): T? {
    try {
        val resolved = resolver()
        if (resolved != null) {
            LOG.info("Successfully resolved {}", what)
            return resolved
        } else {
            LOG.info("Could not resolve {} as it is null", what)
        }
    } catch (e: Exception) {
        LOG.info("Could not resolve {}: {}", what, e.message)
    }
    return null
}