Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,32 @@
"default": true,
"description": "Specifies whether the language server should be used. When enabled the extension will provide code completions and linting, otherwise just syntax highlighting. Might require a reload to apply."
},
"kotlin.debounceTime": {
"kotlin.compiler.jvm.target": {
"type": "string",
"default": "default",
"description": "Specifies the JVM target, e.g. \"1.6\" or \"1.8\""
},
"kotlin.linting.debounceTime": {
"type": "integer",
"default": 250,
"description": "[DEBUG] Specifies the debounce time limit. Lower to increase responsiveness at the cost of possibile stability issues"
},
"kotlin.snippetsEnabled": {
"kotlin.completion.snippets.enabled": {
"type": "boolean",
"default": true,
"description": "Specifies whether code completion should provide snippets (true) or plain-text items (false)"
},
"kotlin.debounceTime": {
"type": "integer",
"default": 250,
"description": "[DEPRECATED] Specifies the debounce time limit. Lower to increase responsiveness at the cost of possibile stability issues",
"deprecationMessage": "Use 'kotlin.linting.debounceTime' instead"
},
"kotlin.snippetsEnabled": {
"type": "boolean",
"default": true,
"description": "[DEPRECATED] Specifies whether code completion should provide snippets (true) or plain-text items (false)",
"deprecationMessage": "Use 'kotlin.completion.snippets.enabled'"
}
}
}
Expand Down
59 changes: 45 additions & 14 deletions server/src/main/kotlin/org/javacs/kt/Compiler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intellij.codeInsight.NullableNotNullManager
import com.intellij.openapi.Disposable
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.VirtualFileSystem
import com.intellij.psi.PsiFileFactory
import com.intellij.mock.MockProject
import org.jetbrains.kotlin.cli.common.script.CliScriptDefinitionProvider
Expand All @@ -14,8 +15,9 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfiguration as KotlinCompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.idea.KotlinLanguage
Expand Down Expand Up @@ -46,32 +48,61 @@ import org.javacs.kt.util.LoggingMessageCollector
* The basic strategy for compiling one file at-a-time is outlined in OneFilePerformance.
*/
class Compiler(classPath: Set<Path>) {
private val config = CompilerConfiguration().apply {
put(CommonConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME)
put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, LoggingMessageCollector)
addJvmClasspathRoots(classPath.map { it.toFile() })
}
val environment: KotlinCoreEnvironment

private var parser: KtPsiFactory
private var scripts: CliScriptDefinitionProvider
private val localFileSystem: VirtualFileSystem

companion object {
init {
System.setProperty("idea.io.use.fallback", "true")
}
}

init {
System.setProperty("idea.io.use.fallback", "true")
environment = KotlinCoreEnvironment.createForProduction(
parentDisposable = Disposable { },
configuration = config,
parentDisposable = Disposable {},
// Not to be confused with the CompilerConfiguration in the language server Configuration
configuration = KotlinCompilerConfiguration().apply {
put(CommonConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME)
put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, LoggingMessageCollector)
addJvmClasspathRoots(classPath.map { it.toFile() })
},
configFiles = EnvironmentConfigFiles.JVM_CONFIG_FILES
)

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

parser = KtPsiFactory(environment.project)
scripts = ScriptDefinitionProvider.getInstance(environment.project) as CliScriptDefinitionProvider
scripts.setScriptDefinitions(listOf(KotlinScriptDefinition(Any::class)))
localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)
}

private val parser = KtPsiFactory(environment.project)
private val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)
private val scripts = ScriptDefinitionProvider.getInstance(environment.project) as CliScriptDefinitionProvider
/**
* Updates the compiler environment using the given
* configuration (which is a class from this project).
*/
fun updateConfiguration(config: CompilerConfiguration) {
jvmTargetFrom(config.jvm.target)
?.let { environment.configuration.put(JVMConfigurationKeys.JVM_TARGET, it) }
}

init {
scripts.setScriptDefinitions(listOf(KotlinScriptDefinition(Any::class)))
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
// TODO: Add once Java 9+ is supported
// "9" -> JvmTarget.JVM_9
// "10" -> JvmTarget.JVM_10
// "11" -> JvmTarget.JVM_11
// "12" -> JvmTarget.JVM_12
else -> null
}

fun createFile(content: String, file: Path = Paths.get("dummy.kt")): KtFile {
Expand Down
11 changes: 10 additions & 1 deletion server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ package org.javacs.kt
import org.javacs.kt.classpath.findClassPath
import java.nio.file.Path

class CompilerClassPath {
class CompilerClassPath(private val config: CompilerConfiguration) {
private val workspaceRoots = mutableSetOf<Path>()
private val classPath = mutableSetOf<Path>()
var compiler = Compiler(classPath)
private set

init {
compiler.updateConfiguration(config)
}

private fun refresh() {
val newClassPath = findClassPath(workspaceRoots)

Expand All @@ -22,9 +26,14 @@ class CompilerClassPath {
classPath.removeAll(removed)
classPath.addAll(added)
compiler = Compiler(classPath)
updateCompilerConfiguration()
}
}

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

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

Expand Down
27 changes: 24 additions & 3 deletions server/src/main/kotlin/org/javacs/kt/Configuration.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
package org.javacs.kt

public class Configuration(
var debounceTime: Long = 250L,
var snippetsEnabled: Boolean = true
public data class SnippetsConfiguration(
var enabled: Boolean = true
)

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

public data class LintingConfiguration(
var debounceTime: Long = 250L
)

public data class JVMConfiguration(
var target: String = "default" // See Compiler.jvmTargetFrom for possible values
)

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

public data class Configuration(
val compiler: CompilerConfiguration = CompilerConfiguration(),
val completion: CompletionConfiguration = CompletionConfiguration(),
val linting: LintingConfiguration = LintingConfiguration()
)
6 changes: 3 additions & 3 deletions server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletableFuture.completedFuture

class KotlinLanguageServer : LanguageServer, LanguageClientAware {
val classPath = CompilerClassPath()
private val config = Configuration()
val classPath = CompilerClassPath(config.compiler)
val sourcePath = SourcePath(classPath)
val sourceFiles = SourceFiles(sourcePath)
private val config = Configuration()
private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath, config)
private val workspaces = KotlinWorkspaceService(sourceFiles, sourcePath, classPath, textDocuments, config)

Expand Down Expand Up @@ -57,7 +57,7 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware {
serverCapabilities.executeCommandProvider = ExecuteCommandOptions(ALL_COMMANDS)

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

if (params.rootUri != null) {
LOG.info("Adding workspace {} to source path", params.rootUri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class KotlinTextDocumentService(
private val async = AsyncExecutor()
private var linting = false

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

Expand Down Expand Up @@ -115,7 +115,7 @@ class KotlinTextDocumentService(
LOG.info("Completing at {}", describePosition(position))

val (file, cursor) = recover(position, false)
val completions = completions(file, cursor, config.snippetsEnabled)
val completions = completions(file, cursor, config.completion)

LOG.info("Found {} items", completions.items.size)

Expand Down Expand Up @@ -190,7 +190,7 @@ class KotlinTextDocumentService(
}

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

private fun clearLint(): List<Path> {
Expand Down
43 changes: 38 additions & 5 deletions server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,45 @@ class KotlinWorkspaceService(
override fun didChangeConfiguration(params: DidChangeConfigurationParams) {
val settings = params.settings as? JsonObject
settings?.get("kotlin")?.asJsonObject?.apply {
get("debounceTime")?.asLong?.let { config.debounceTime = it }
get("snippetsEnabled")?.asBoolean?.let { config.snippetsEnabled = it }
// 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 }
}
}
}

docService.updateDebouncer()
LOG.info("configurations updated {}", settings)

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

override fun symbol(params: WorkspaceSymbolParams): CompletableFuture<List<SymbolInformation>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.eclipse.lsp4j.CompletionItem
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
Expand Down Expand Up @@ -34,24 +35,24 @@ import java.util.concurrent.TimeUnit

private const val MAX_COMPLETION_ITEMS = 50

fun completions(file: CompiledFile, cursor: Int, snippetsEnabled: Boolean = true): CompletionList {
fun completions(file: CompiledFile, cursor: Int, config: CompletionConfiguration): CompletionList {
val surroundingElement = completableElement(file, cursor) ?: return CompletionList(true, emptyList())
val completions = doCompletions(file, cursor, surroundingElement)
val partial = findPartialIdentifier(file, cursor)
LOG.debug("Looking for names that match '{}'", partial)
val nameFilter = matchesPartialIdentifier(partial)
val matchesName = completions.filter(nameFilter)
val visible = matchesName.filter(isVisible(file, cursor))
val list = visible.map { completionItem(it, surroundingElement, file, snippetsEnabled) }.take(MAX_COMPLETION_ITEMS).toList()
val list = visible.map { completionItem(it, surroundingElement, file, config) }.take(MAX_COMPLETION_ITEMS).toList()
val isIncomplete = (list.size == MAX_COMPLETION_ITEMS)
return CompletionList(isIncomplete, list)
}

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

private fun completionItem(d: DeclarationDescriptor, surroundingElement: KtElement, file: CompiledFile, snippetsEnabled: Boolean): CompletionItem {
val result = d.accept(RenderCompletionItem(snippetsEnabled), null)
private fun completionItem(d: DeclarationDescriptor, surroundingElement: KtElement, file: CompiledFile, config: CompletionConfiguration): CompletionItem {
val result = d.accept(RenderCompletionItem(config.snippets.enabled), null)

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

Expand Down
4 changes: 2 additions & 2 deletions server/src/test/kotlin/org/javacs/kt/CompiledFileTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CompiledFileTest {
val file = testResourcesRoot().resolve("compiledFile/CompiledFileExample.kt")
val content = Files.readAllLines(file).joinToString("\n")
val parse = compiler.createFile(content, file)
val classPath = CompilerClassPath()
val classPath = CompilerClassPath(CompilerConfiguration())
val sourcePath = listOf(parse)
val (context, container) = compiler.compileFiles(sourcePath, sourcePath)
return CompiledFile(content, parse, context, container, sourcePath, classPath)
Expand All @@ -24,4 +24,4 @@ class CompiledFileTest {

assertThat(type.toString(), equalTo("Int"))
}
}
}
7 changes: 4 additions & 3 deletions server/src/test/kotlin/org/javacs/kt/SimpleScriptTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ package org.javacs.kt

import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.repl.*
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration as KotlinCompilerConfiguration
import org.jetbrains.kotlin.script.*
import com.intellij.openapi.util.*
import org.jetbrains.kotlin.cli.common.messages.*
Expand All @@ -19,7 +20,7 @@ class SimpleScriptTest {
fun basicScript() {
val scriptDef = KotlinScriptDefinition(Any::class)
val repl = GenericReplEvaluator(listOf())
val config = CompilerConfiguration()
val config = KotlinCompilerConfiguration()
config.put(CommonConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME)
val compiler = GenericReplCompiler(scriptDef, config, MessageCollector.NONE)
val compilerState = compiler.createState()
Expand All @@ -38,7 +39,7 @@ class SimpleScriptTest {
is ReplEvalResult.UnitResult -> println('_')
}
}

private fun assertNotError(result: ReplCompileResult) {
if (result is ReplCompileResult.Error) {
fail(result.message)
Expand Down
4 changes: 2 additions & 2 deletions vscode-extension-src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
export function deactivate(): void {}

async function activateLanguageServer(context: vscode.ExtensionContext) {
LOG.info('Activating Kotlin language server...');
LOG.info('Activating Kotlin Language Server...');
let barItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
context.subscriptions.push(barItem);
barItem.text = '$(sync) Activating Kotlin language server...';
barItem.text = '$(sync~spin) Activating Kotlin Language Server...';
barItem.show();

let javaExecutablePath = findJavaExecutable('java');
Expand Down