├── src ├── test │ ├── resources │ │ ├── script │ │ │ ├── ExampleScript.kts │ │ │ └── FunctionScript.kts │ │ ├── completions │ │ │ ├── EditCall.kt │ │ │ ├── DoubleDot.kt │ │ │ ├── QuestionDot.kt │ │ │ ├── Imports.kt │ │ │ ├── Constructor.kt │ │ │ ├── InstanceMembersJava.kt │ │ │ ├── BackquotedFunction.kt │ │ │ ├── MiddleOfFunction.kt │ │ │ ├── FillEmptyBody.kt │ │ │ ├── Types.kt │ │ │ ├── OuterDotInner.kt │ │ │ ├── FunctionScope.kt │ │ │ ├── Statics.kt │ │ │ ├── InstanceMember.kt │ │ │ └── Visibility.kt │ │ ├── compiler │ │ │ └── FileToEdit.kt │ │ ├── hover │ │ │ ├── ResolveToFile.kt │ │ │ ├── Literals.kt │ │ │ ├── ResolveFromFile.kt │ │ │ ├── FunctionReference.kt │ │ │ ├── Recover.kt │ │ │ └── ObjectReference.kt │ │ ├── definition │ │ │ ├── GoTo.kt │ │ │ └── GoFrom.kt │ │ ├── Anchor.txt │ │ ├── lint │ │ │ └── LintErrors.kt │ │ ├── references │ │ │ ├── ReferenceFrom.kt │ │ │ ├── ReferenceInvoke.kt │ │ │ ├── ReferenceComponents.kt │ │ │ ├── ReferenceTo.kt │ │ │ ├── ReferenceGetterSetter.kt │ │ │ ├── ReferenceConstructor.kt │ │ │ ├── ReferenceGetSetValue.kt │ │ │ ├── ReferenceCollectionish.kt │ │ │ ├── ReferenceOperatorUsingName.kt │ │ │ └── ReferenceOperator.kt │ │ ├── compiledFile │ │ │ └── CompiledFileExample.kt │ │ ├── mainWorkspace │ │ │ └── MainWorkspaceFile.kt │ │ ├── kotlinCompilerPerformance │ │ │ └── ReferencesBigFile.kt │ │ ├── symbols │ │ │ ├── DocumentSymbols.kt │ │ │ └── OtherFileSymbols.kt │ │ ├── signatureHelp │ │ │ └── SignatureHelp.kt │ │ └── additionalWorkspace │ │ │ └── pom.xml │ ├── kotlin │ │ └── org │ │ │ └── javacs │ │ │ └── kt │ │ │ ├── DebounceDelayTest.kt │ │ │ ├── ScriptTest.kt │ │ │ ├── ClassPathTest.kt │ │ │ ├── LintTest.kt │ │ │ ├── DefinitionTest.kt │ │ │ ├── CompiledFileTest.kt │ │ │ ├── WorkspaceSymbolsTest.kt │ │ │ ├── CompilerFixtures.kt │ │ │ ├── DocumentSymbolsTest.kt │ │ │ ├── SimpleScriptTest.kt │ │ │ ├── AdditionalWorkspaceTest.kt │ │ │ ├── SignatureHelpTest.kt │ │ │ ├── CompilerTest.kt │ │ │ ├── HoverTest.kt │ │ │ ├── LanguageServerTestFixture.kt │ │ │ ├── ReferencesTest.kt │ │ │ └── CompletionsTest.kt │ └── java │ │ └── org │ │ └── javacs │ │ └── kt │ │ └── OneFilePerformance.java └── main │ └── kotlin │ └── org │ └── javacs │ └── kt │ ├── definition │ └── goToDefinition.kt │ ├── DebounceDelay.kt │ ├── hover │ └── hovers.kt │ ├── Main.kt │ ├── util │ └── utils.kt │ ├── diagnostic │ └── convertDiagnostic.kt │ ├── docs │ └── findDoc.kt │ ├── LogFormat.kt │ ├── CompilerClassPath.kt │ ├── KotlinWorkspaceService.kt │ ├── KotlinLanguageServer.kt │ ├── position │ └── position.kt │ ├── symbols │ └── symbols.kt │ ├── signatureHelp │ └── signatureHelp.kt │ ├── SourcePath.kt │ ├── SourceFiles.kt │ ├── completion │ └── RenderCompletionItem.kt │ ├── classpath │ └── findClassPath.kt │ ├── Compiler.kt │ ├── CompiledFile.kt │ ├── KotlinTextDocumentService.kt │ └── references │ └── findReferences.kt ├── Icon128.png ├── README.md ├── images ├── Hover.png ├── Autocomplete.png ├── GlobalSymbols.png ├── SignatureHelp.png ├── DocumentSymbols.png ├── FindAllReferences.png └── Example.kt ├── .gitignore ├── TODO.md ├── CHANGELOG.md ├── .vscodeignore ├── grammar ├── README.md ├── enum.grm ├── when.grm ├── annotations.grm ├── types.grm ├── control.grm ├── toplevel.grm ├── notation.grm ├── modifiers.grm ├── class.grm ├── class_members.grm ├── lexical.grm └── expressions.grm ├── scripts └── install.sh ├── syntaxes ├── codeblock.json ├── kotlin.configuration.json └── Kotlin.tmLanguage.json ├── .vscode ├── tasks.json ├── settings.json └── launch.json ├── lib ├── test │ ├── extension.test.ts │ └── index.ts └── extension.ts ├── tsconfig.json ├── LICENSE.txt ├── package.json └── pom.xml /src/test/resources/script/ExampleScript.kts: -------------------------------------------------------------------------------- 1 | val foo = 1 -------------------------------------------------------------------------------- /src/test/resources/completions/EditCall.kt: -------------------------------------------------------------------------------- 1 | private fun test() { 2 | printl() 3 | } -------------------------------------------------------------------------------- /Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgewfraser/kotlin-language-server/HEAD/Icon128.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository has been moved to https://github.com/fwcd/KotlinLanguageServer 2 | -------------------------------------------------------------------------------- /src/test/resources/compiler/FileToEdit.kt: -------------------------------------------------------------------------------- 1 | private class FileToEdit { 2 | val someVal = "Foo" 3 | } -------------------------------------------------------------------------------- /src/test/resources/completions/DoubleDot.kt: -------------------------------------------------------------------------------- 1 | private fun doubleDot(p: String) { 2 | p.chars(). 3 | } -------------------------------------------------------------------------------- /images/Hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgewfraser/kotlin-language-server/HEAD/images/Hover.png -------------------------------------------------------------------------------- /src/test/resources/completions/QuestionDot.kt: -------------------------------------------------------------------------------- 1 | private fun completeQuestionDot(s: String?) { 2 | s?. 3 | } -------------------------------------------------------------------------------- /src/test/resources/hover/ResolveToFile.kt: -------------------------------------------------------------------------------- 1 | object ResolveToFile { 2 | fun target() { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /src/test/resources/script/FunctionScript.kts: -------------------------------------------------------------------------------- 1 | fun foo() { 2 | val first = 1 3 | val second = 2 4 | } -------------------------------------------------------------------------------- /src/test/resources/definition/GoTo.kt: -------------------------------------------------------------------------------- 1 | object GoTo { 2 | fun methodInDifferentFile() { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /images/Autocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgewfraser/kotlin-language-server/HEAD/images/Autocomplete.png -------------------------------------------------------------------------------- /src/test/resources/Anchor.txt: -------------------------------------------------------------------------------- 1 | The purpose of this file is to help us figure out where the test/resources directory is -------------------------------------------------------------------------------- /images/GlobalSymbols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgewfraser/kotlin-language-server/HEAD/images/GlobalSymbols.png -------------------------------------------------------------------------------- /images/SignatureHelp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgewfraser/kotlin-language-server/HEAD/images/SignatureHelp.png -------------------------------------------------------------------------------- /src/test/resources/completions/Imports.kt: -------------------------------------------------------------------------------- 1 | import java.nio.file.P 2 | // Import something else 3 | import java.lang.invoke. -------------------------------------------------------------------------------- /src/test/resources/lint/LintErrors.kt: -------------------------------------------------------------------------------- 1 | private class LintErrors { 2 | fun foo() { 3 | return 1 4 | } 5 | } -------------------------------------------------------------------------------- /images/DocumentSymbols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgewfraser/kotlin-language-server/HEAD/images/DocumentSymbols.png -------------------------------------------------------------------------------- /images/FindAllReferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/georgewfraser/kotlin-language-server/HEAD/images/FindAllReferences.png -------------------------------------------------------------------------------- /src/test/resources/hover/Literals.kt: -------------------------------------------------------------------------------- 1 | private fun foo() { 2 | val stringLiteral = "Foo" 3 | println(stringLiteral) 4 | } -------------------------------------------------------------------------------- /images/Example.kt: -------------------------------------------------------------------------------- 1 | 2 | fun example() = "Hello world!" 3 | 4 | fun otherFun() = example() 5 | 6 | fun yetAnotherFun() = example() -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | 4 | target/ 5 | out/ 6 | *.vsix 7 | 8 | node_modules/ 9 | .vscode-test/ 10 | 11 | .DS_STORE -------------------------------------------------------------------------------- /src/test/resources/hover/ResolveFromFile.kt: -------------------------------------------------------------------------------- 1 | object ResolveFromFile { 2 | fun main() { 3 | ResolveToFile.target() 4 | } 5 | } -------------------------------------------------------------------------------- /src/test/resources/completions/Constructor.kt: -------------------------------------------------------------------------------- 1 | private fun main() { 2 | SomeC 3 | } 4 | 5 | private class SomeConstructor(x: Int) { 6 | 7 | } -------------------------------------------------------------------------------- /src/test/resources/completions/InstanceMembersJava.kt: -------------------------------------------------------------------------------- 1 | import java.nio.file.Path 2 | 3 | private fun findJavaInstanceMembers(p: Path) { 4 | p.fileNam 5 | } -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceFrom.kt: -------------------------------------------------------------------------------- 1 | object ReferenceFrom { 2 | fun main() { 3 | ReferenceTo.foo() 4 | ReferenceTo + 1 5 | } 6 | } -------------------------------------------------------------------------------- /src/test/resources/compiledFile/CompiledFileExample.kt: -------------------------------------------------------------------------------- 1 | private class CompiledFileExample { 2 | fun main() { 3 | val x = 1 4 | println(x) 5 | } 6 | } -------------------------------------------------------------------------------- /src/test/resources/completions/BackquotedFunction.kt: -------------------------------------------------------------------------------- 1 | private fun completeBackquotedFunction() { 2 | fu 3 | } 4 | 5 | private fun `fun that needs backquotes`() { 6 | } -------------------------------------------------------------------------------- /src/test/resources/completions/MiddleOfFunction.kt: -------------------------------------------------------------------------------- 1 | private class MiddleOfFunction { 2 | fun example(p: String): String { 3 | p. 4 | return p 5 | } 6 | } -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## Bugs 2 | 3 | ## Features 4 | - Fix-imports-on-save, default to 'on' 5 | - See https://godoc.org/golang.org/x/tools/cmd/goimports 6 | - Rename 7 | - .kts support with eval -------------------------------------------------------------------------------- /src/test/resources/hover/FunctionReference.kt: -------------------------------------------------------------------------------- 1 | private fun foo(): List { 2 | return listOf("Foo", "Bar").filter(::isFoo) 3 | } 4 | 5 | private fun isFoo(s: String) = 6 | s == "Foo" -------------------------------------------------------------------------------- /src/test/resources/completions/FillEmptyBody.kt: -------------------------------------------------------------------------------- 1 | private object Caller { 2 | fun foo() { 3 | } 4 | } 5 | 6 | private object Callee { 7 | fun bar() { 8 | println("Bar!") 9 | } 10 | } -------------------------------------------------------------------------------- /src/test/resources/hover/Recover.kt: -------------------------------------------------------------------------------- 1 | private fun singleExpressionFunction() = 2 | "Foo" 3 | 4 | private fun blockFunction() { 5 | println("Foo") 6 | } 7 | 8 | private fun intFunction() = 9 | 1 -------------------------------------------------------------------------------- /src/test/resources/mainWorkspace/MainWorkspaceFile.kt: -------------------------------------------------------------------------------- 1 | import junit.framework.Assert.assertTrue 2 | 3 | private class MainWorkspaceFile { 4 | fun testFunction() { 5 | assertTrue(true) 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceInvoke.kt: -------------------------------------------------------------------------------- 1 | private class ReferenceInvoke { 2 | operator fun invoke(): Int = TODO() 3 | } 4 | 5 | private fun main() { 6 | val example = ReferenceInvoke() 7 | example() 8 | } -------------------------------------------------------------------------------- /src/test/resources/kotlinCompilerPerformance/ReferencesBigFile.kt: -------------------------------------------------------------------------------- 1 | private object ReferencesBigFile { 2 | fun main() { 3 | val one = BigFile.max(1, 0) 4 | println(one) 5 | println("Foo") 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/resources/completions/Types.kt: -------------------------------------------------------------------------------- 1 | private class Types { 2 | fun returnsType(): S 3 | 4 | class SomeInnerClass() 5 | object SomeInnerObject { 6 | } 7 | } 8 | 9 | private typealias SomeAlias = Types.SomeInnerClass -------------------------------------------------------------------------------- /src/test/resources/symbols/DocumentSymbols.kt: -------------------------------------------------------------------------------- 1 | private class DocumentSymbols() { 2 | val aProperty = 1 3 | 4 | fun aFunction(aFunctionArg: Int) { 5 | } 6 | 7 | constructor(aConstructorArg: Int): this() { 8 | } 9 | } -------------------------------------------------------------------------------- /src/test/resources/definition/GoFrom.kt: -------------------------------------------------------------------------------- 1 | private object GoFrom { 2 | fun main() { 3 | GoFrom.methodInSameFile() 4 | GoTo.methodInDifferentFile() 5 | } 6 | 7 | fun methodInSameFile() { 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "kotlin" extension will be documented in this file. 3 | 4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 5 | 6 | ## [0.1.0] 7 | - Initial release -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .idea/** 3 | .vscode-test/** 4 | tsconfig.json 5 | tslint.json 6 | *.iml 7 | pom.xml 8 | 9 | out/test/** 10 | out/**/*.map 11 | target/** 12 | !target/KotlinLanguageServer.jar 13 | 14 | src/** 15 | lib/** 16 | 17 | .gitignore -------------------------------------------------------------------------------- /src/test/resources/completions/OuterDotInner.kt: -------------------------------------------------------------------------------- 1 | private fun test(p: Any) { 2 | p as MyOuterClass.I 3 | } 4 | 5 | private fun staticDot() { 6 | MyOuterClass.I 7 | } 8 | 9 | private class MyOuterClass { 10 | class InnerClass { 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/test/resources/symbols/OtherFileSymbols.kt: -------------------------------------------------------------------------------- 1 | class OtherFileSymbols() { 2 | val otherFileProperty = 1 3 | 4 | fun otherFileFunction() { 5 | val otherFileLocalVariable = 1 6 | } 7 | 8 | constructor(aConstructorArg: Int): this() { 9 | } 10 | } -------------------------------------------------------------------------------- /src/test/resources/hover/ObjectReference.kt: -------------------------------------------------------------------------------- 1 | private fun foo() { 2 | AnObject 3 | } 4 | 5 | private fun bar() { 6 | AnObject.d 7 | } 8 | 9 | private fun dang() { 10 | AnObject.doh() 11 | } 12 | 13 | private object AnObject { 14 | fun doh() { 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /grammar/README.md: -------------------------------------------------------------------------------- 1 | Copied for easy reference from 2 | - https://github.com/JetBrains/kotlin/blob/master/grammar/src 3 | - https://github.com/Microsoft/vscode/blob/master/extensions/csharp/syntaxes/csharp.tmLanguage.json 4 | - https://github.com/Microsoft/vscode/blob/master/extensions/java/syntaxes/java.tmLanguage.json -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceComponents.kt: -------------------------------------------------------------------------------- 1 | private class ReferenceComponents { 2 | operator fun component1(): Int = TODO() 3 | operator fun component2(): Int = TODO() 4 | } 5 | 6 | private fun main() { 7 | val (a, b) = ReferenceComponents() 8 | val c = ReferenceComponents().component1() 9 | } -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceTo.kt: -------------------------------------------------------------------------------- 1 | object ReferenceTo { 2 | fun foo() { 3 | 4 | } 5 | 6 | private class MainConstructor(foo: Int) 7 | 8 | fun main() { 9 | ReferenceTo.foo() 10 | MainConstructor(1) 11 | // TODO getValue, setValue, contains, invoke 12 | } 13 | } -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceGetterSetter.kt: -------------------------------------------------------------------------------- 1 | private class ReferenceGetterSetter { 2 | operator fun get(index: Int): Int = TODO() 3 | operator fun set(index: Int, value: Int) { } 4 | } 5 | 6 | private fun main() { 7 | val example = ReferenceGetterSetter() 8 | example[1] 9 | example[1] = 2 10 | } -------------------------------------------------------------------------------- /grammar/enum.grm: -------------------------------------------------------------------------------- 1 | /** 2 | ### Enum classes 3 | 4 | See [Enum classes](enum-classes.html) 5 | */ 6 | 7 | enumClassBody 8 | : "{" enumEntries (";" members)? "}" 9 | ; 10 | 11 | enumEntries 12 | : (enumEntry{","} ","? ";"?)? 13 | ; 14 | 15 | enumEntry 16 | : modifiers SimpleName valueArguments? classBody? 17 | ; 18 | -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceConstructor.kt: -------------------------------------------------------------------------------- 1 | private class ReferenceConstructor(mainConstructor: String) { 2 | constructor(secondaryConstructor: String, partTwo: String): this(secondaryConstructor + partTwo) { 3 | } 4 | } 5 | 6 | private fun main() { 7 | ReferenceConstructor("foo") 8 | ReferenceConstructor("foo", "bar") 9 | } 10 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Installs locally 4 | # You will need java, maven, vsce, and visual studio code to run this script 5 | set -e 6 | 7 | # Needed once 8 | npm install 9 | 10 | # Build fat jar 11 | mvn clean package 12 | 13 | # Build vsix 14 | vsce package -o build.vsix 15 | 16 | # Install to Code 17 | echo 'Install build.vsix via the extensions menu' -------------------------------------------------------------------------------- /src/test/resources/completions/FunctionScope.kt: -------------------------------------------------------------------------------- 1 | private class FunctionScope { 2 | fun foo(anArgument: Int) { 3 | val aLocal = 1 4 | a 5 | } 6 | 7 | private val aClassVal = 1 8 | private fun aClassFun() = 1 9 | 10 | companion object { 11 | private val aCompanionVal = 1 12 | private fun aCompanionFun() = 1 13 | } 14 | } -------------------------------------------------------------------------------- /src/test/resources/completions/Statics.kt: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | 3 | private fun completeStatics() { 4 | Objects.isN 5 | MyObject.obj 6 | MyClass.com 7 | Objects::isN 8 | } 9 | 10 | private object MyObject { 11 | fun objectFun() { 12 | } 13 | } 14 | 15 | private class MyClass { 16 | companion object { 17 | fun companionFun() { 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceGetSetValue.kt: -------------------------------------------------------------------------------- 1 | import kotlin.reflect.KProperty 2 | 3 | private class ReferenceGetSetValue { 4 | operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = TODO() 5 | operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int): Unit = TODO() 6 | } 7 | 8 | private class Main { 9 | var x: Int by ReferenceGetSetValue() 10 | 11 | private fun main() { 12 | x = 1 13 | } 14 | } -------------------------------------------------------------------------------- /grammar/when.grm: -------------------------------------------------------------------------------- 1 | /** 2 | #### When-expression 3 | 4 | See [When-expression](control-flow.html#when-expression) 5 | */ 6 | 7 | when 8 | : "when" ("(" expression ")")? "{" 9 | whenEntry* 10 | "}" 11 | ; 12 | 13 | whenEntry 14 | : whenCondition{","} "->" controlStructureBody SEMI 15 | : "else" "->" controlStructureBody SEMI 16 | ; 17 | 18 | whenCondition 19 | : expression 20 | : ("in" | "!in") expression 21 | : ("is" | "!is") type 22 | ; 23 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/definition/goToDefinition.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.definition 2 | 3 | import org.eclipse.lsp4j.Location 4 | import org.javacs.kt.CompiledFile 5 | import org.javacs.kt.position.location 6 | 7 | fun goToDefinition(file: CompiledFile, cursor: Int): Location? { 8 | val (_, target) = file.referenceAtPoint(cursor) ?: return null 9 | // TODO go to declaration name rather than beginning of javadoc comment 10 | return location(target) 11 | } -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceCollectionish.kt: -------------------------------------------------------------------------------- 1 | private class ReferenceCollectionish { 2 | operator fun iterator(): Iterator = TODO() 3 | operator fun contains(other: Int): Boolean = TODO() 4 | operator fun rangeTo(i: Int): Sequence = TODO() 5 | } 6 | 7 | private fun main() { 8 | val example = ReferenceCollectionish() 9 | for (i in example) { } 10 | example.iterator() 11 | 1 in example 12 | example.contains(1) 13 | for (i in example..10) { } 14 | } -------------------------------------------------------------------------------- /syntaxes/codeblock.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:markup.fenced_code.block.markdown", 4 | "patterns": [ 5 | { 6 | "include": "#kotlin-code-block" 7 | } 8 | ], 9 | "repository": { 10 | "kotlin-code-block": { 11 | "begin": "kotlin", 12 | "end": "(^|\\G)(?=\\s*[`~]{3,}\\s*$)", 13 | "contentName": "meta.embedded.block.kotlin", 14 | "patterns": [ 15 | { 16 | "include": "source.Kotlin" 17 | } 18 | ] 19 | } 20 | }, 21 | "scopeName": "markdown.Kotlin.codeblock" 22 | } -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceOperatorUsingName.kt: -------------------------------------------------------------------------------- 1 | private class ReferenceOperatorUsingName { 2 | override operator fun equals(other: Any?): Boolean = TODO() 3 | operator fun compareTo(other: ReferenceOperatorUsingName): Int = TODO() 4 | operator fun inc(): ReferenceOperatorUsingName = TODO() 5 | operator fun not(): Int = TODO() 6 | } 7 | 8 | private fun main() { 9 | var example = ReferenceOperatorUsingName() 10 | 11 | example.equals(example) 12 | example.compareTo(example) 13 | example.inc() 14 | example.not() 15 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "*.iml": true, 5 | ".idea": true, 6 | "node_modules": true, 7 | "target": true, 8 | "out": true // set this to true to hide the "out" folder with the compiled JS files 9 | }, 10 | "search.exclude": { 11 | "*.iml": true, 12 | ".idea": true, 13 | "node_modules": true, 14 | "target": true, 15 | "out": true // set this to false to include "out" folder in search results 16 | } 17 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/DebounceDelayTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.hamcrest.Matchers.equalTo 4 | import org.junit.Assert.assertThat 5 | import org.junit.Test 6 | import java.time.Duration 7 | 8 | class DebounceDelayTest { 9 | val debounce = DebounceDelay(Duration.ofSeconds(1)) 10 | var counter = 0 11 | 12 | @Test fun callQuickly() { 13 | for (i in 1..10) { 14 | debounce.submit { 15 | counter++ 16 | } 17 | } 18 | 19 | debounce.waitForPendingTask() 20 | 21 | assertThat(counter, equalTo(1)) 22 | } 23 | } -------------------------------------------------------------------------------- /grammar/annotations.grm: -------------------------------------------------------------------------------- 1 | /** 2 | ## Annotations 3 | */ 4 | 5 | annotations 6 | : (annotation | annotationList)* 7 | ; 8 | 9 | annotation 10 | : "@" (annotationUseSiteTarget ":")? unescapedAnnotation 11 | ; 12 | 13 | annotationList 14 | : "@" (annotationUseSiteTarget ":")? "[" unescapedAnnotation+ "]" 15 | ; 16 | 17 | annotationUseSiteTarget 18 | : "field" 19 | : "file" 20 | : "property" 21 | : "get" 22 | : "set" 23 | : "receiver" 24 | : "param" 25 | : "setparam" 26 | : "delegate" 27 | ; 28 | 29 | unescapedAnnotation 30 | : SimpleName{"."} typeArguments? valueArguments? 31 | ; 32 | -------------------------------------------------------------------------------- /src/test/resources/completions/InstanceMember.kt: -------------------------------------------------------------------------------- 1 | private fun foo() { 2 | val instance = SomeClass() 3 | instance.f 4 | } 5 | 6 | private fun findListExtensionFunctions() { 7 | val list = listOf(1, 2, 3) 8 | list.coun 9 | } 10 | 11 | private fun findFunctionReference() { 12 | val instance = SomeClass() 13 | instance::f 14 | } 15 | 16 | private fun findUnqualifiedFunctionReference() { 17 | ::f 18 | } 19 | 20 | private fun completeIdentifierInsideCall() { 21 | val instance = SomeClass() 22 | instance.instanceFoo(f) 23 | } 24 | 25 | private class SomeClass { 26 | fun instanceFoo() = "Foo" 27 | private fun privateInstanceFoo() = "Foo" 28 | var fooVar = 1 29 | } 30 | 31 | private fun SomeClass.extensionFoo() = "Bar" -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/ScriptTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.hamcrest.Matchers.hasItem 4 | import org.junit.Assert.assertThat 5 | import org.junit.Test 6 | 7 | class ScriptTest: LanguageServerTestFixture("script") { 8 | @Test fun `open script`() { 9 | open("ExampleScript.kts") 10 | } 11 | } 12 | 13 | class EditFunctionTest: SingleFileTestFixture("script", "FunctionScript.kts") { 14 | @Test fun `edit a function in a script`() { 15 | replace("FunctionScript.kts", 3, 18, "2", "f") 16 | 17 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 3, 19)).get().right!! 18 | val labels = completions.items.map { it.label } 19 | 20 | assertThat(labels, hasItem("first")) 21 | } 22 | } -------------------------------------------------------------------------------- /lib/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | // import * as vscode from 'vscode'; 12 | // import * as myExtension from '../extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", function () { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", function() { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/ClassPathTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.hamcrest.Matchers.* 4 | import org.javacs.kt.classpath.* 5 | import org.junit.Assert.assertThat 6 | import org.junit.Assert.assertTrue 7 | import org.junit.Test 8 | import java.nio.file.Files 9 | 10 | class ClassPathTest { 11 | @Test fun `find maven classpath`() { 12 | val workspaceRoot = testResourcesRoot().resolve("additionalWorkspace") 13 | val pom = workspaceRoot.resolve("pom.xml") 14 | 15 | assertTrue(Files.exists(pom)) 16 | 17 | val classPath = findClassPath(listOf(workspaceRoot)) 18 | 19 | assertThat(classPath, hasItem(hasToString(containsString("junit")))) 20 | } 21 | 22 | @Test fun `find kotlin stdlib in maven repo`() { 23 | assertThat(findKotlinStdlib(), notNullValue()) 24 | } 25 | } -------------------------------------------------------------------------------- /grammar/types.grm: -------------------------------------------------------------------------------- 1 | /** 2 | ## Types 3 | 4 | See [Types](basic-types.html) 5 | */ 6 | 7 | /* 8 | 9 | Foo, T, Object> // user type 10 | (A, Object) -> Foo // function type 11 | () -> Foo // function with no arguments 12 | 13 | */ 14 | 15 | type 16 | : typeModifiers typeReference 17 | ; 18 | 19 | // If you change this, consider updating TYPE_REF_FIRST in KotlinParsing 20 | typeReference 21 | : "(" typeReference ")" 22 | : functionType 23 | : userType 24 | : nullableType 25 | : "dynamic" 26 | ; 27 | 28 | nullableType 29 | : typeReference "?" 30 | ; 31 | 32 | userType 33 | : simpleUserType{"."} 34 | ; 35 | 36 | simpleUserType 37 | : SimpleName ("<" (projection? type | "*"){","} ">")? 38 | ; 39 | 40 | projection 41 | : varianceAnnotation 42 | ; 43 | 44 | functionType 45 | : (type ".")? "(" parameter{","}? ")" "->" type 46 | ; 47 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/DebounceDelay.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import java.time.Duration 4 | import java.util.concurrent.Executors 5 | import java.util.concurrent.TimeUnit 6 | 7 | private var threadCount = 0 8 | 9 | class DebounceDelay(private val delay: Duration) { 10 | private val workerThread = Executors.newScheduledThreadPool(1, { Thread(it, "debounce-${threadCount++}")}) 11 | private var pendingTask = workerThread.submit({}) 12 | 13 | fun submitImmediately(task: () -> Unit) { 14 | pendingTask.cancel(false) 15 | pendingTask = workerThread.submit(task) 16 | } 17 | 18 | fun submit(task: () -> Unit) { 19 | pendingTask.cancel(false) 20 | pendingTask = workerThread.schedule(task, delay.toMillis(), TimeUnit.MILLISECONDS) 21 | } 22 | 23 | fun waitForPendingTask() { 24 | pendingTask.get() 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/hover/hovers.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.hover 2 | 3 | import org.eclipse.lsp4j.Hover 4 | import org.eclipse.lsp4j.MarkedString 5 | import org.eclipse.lsp4j.Range 6 | import org.eclipse.lsp4j.jsonrpc.messages.Either 7 | import org.javacs.kt.CompiledFile 8 | import org.javacs.kt.completion.DECL_RENDERER 9 | import org.javacs.kt.position.position 10 | 11 | fun hoverAt(file: CompiledFile, cursor: Int): Hover? { 12 | val (ref, target) = file.referenceAtPoint(cursor) ?: return null 13 | val location = ref.textRange 14 | val hoverText = DECL_RENDERER.render(target) 15 | val hover = Either.forRight(MarkedString("kotlin", hoverText)) 16 | val range = Range( 17 | position(file.content, location.startOffset), 18 | position(file.content, location.endOffset)) 19 | return Hover(listOf(hover), range) 20 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "lib", 11 | /* Strict Type-Checking Option */ 12 | // "strict": true, /* enable all strict type-checking options */ 13 | /* Additional Checks */ 14 | "noUnusedLocals": true /* Report errors on unused locals. */ 15 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 16 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 17 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | ".vscode-test" 22 | ] 23 | } -------------------------------------------------------------------------------- /grammar/control.grm: -------------------------------------------------------------------------------- 1 | /** 2 | ## Control structures 3 | 4 | See [Control structures](control-flow.html) 5 | */ 6 | 7 | controlStructureBody 8 | : block 9 | : blockLevelExpression 10 | ; 11 | 12 | if 13 | : "if" "(" expression ")" controlStructureBody SEMI? ("else" controlStructureBody)? 14 | ; 15 | 16 | try 17 | : "try" block catchBlock* finallyBlock? 18 | ; 19 | 20 | catchBlock 21 | : "catch" "(" annotations SimpleName ":" userType ")" block 22 | ; 23 | 24 | finallyBlock 25 | : "finally" block 26 | ; 27 | 28 | loop 29 | : for 30 | : while 31 | : doWhile 32 | ; 33 | 34 | for 35 | : "for" "(" annotations (multipleVariableDeclarations | variableDeclarationEntry) "in" expression ")" controlStructureBody 36 | ; 37 | 38 | while 39 | : "while" "(" expression ")" controlStructureBody 40 | ; 41 | 42 | doWhile 43 | : "do" controlStructureBody "while" "(" expression ")" 44 | ; 45 | -------------------------------------------------------------------------------- /src/test/resources/references/ReferenceOperator.kt: -------------------------------------------------------------------------------- 1 | private class ReferenceOperator { 2 | override operator fun equals(other: Any?): Boolean = TODO() 3 | operator fun compareTo(other: ReferenceOperator): Int = TODO() 4 | operator fun inc(): ReferenceOperator = TODO() 5 | operator fun dec(): ReferenceOperator = TODO() 6 | operator fun plus(value: Int): Int = TODO() 7 | operator fun minus(value: Int): Int = TODO() 8 | operator fun not(): Int = TODO() 9 | } 10 | 11 | private class ReferenceEquals { 12 | override fun equals(other: Any?): Boolean = TODO() 13 | } 14 | 15 | private fun main() { 16 | var example = ReferenceOperator() 17 | 18 | assert(example == example) 19 | example > example 20 | example++ 21 | example-- 22 | example + 1 23 | example - 1 24 | !example 25 | 26 | val example2 = ReferenceEquals() 27 | 28 | assert(example2 == example2) 29 | } -------------------------------------------------------------------------------- /src/test/resources/completions/Visibility.kt: -------------------------------------------------------------------------------- 1 | private open class Visibility: VisibilitySuper { 2 | fun test() { 3 | p 4 | } 5 | 6 | private fun privateThisFun() { } 7 | protected fun protectedThisFun() { } 8 | fun publicThisFun() { } 9 | 10 | companion object { 11 | private fun privateThisCompanionFun() { } 12 | protected fun protectedThisCompanionFun() { } 13 | fun publicThisCompanionFun() { } 14 | } 15 | } 16 | 17 | private open class VisibilitySuper { 18 | private fun privateSuperFun() { } 19 | protected fun protectedSuperFun() { } 20 | fun publicSuperFun() { } 21 | 22 | companion object { 23 | private fun privateSuperCompanionFun() { } 24 | protected fun protectedSuperCompanionFun() { } 25 | fun publicSuperCompanionFun() { } 26 | } 27 | } 28 | 29 | private fun privateTopLevelFun() { } 30 | 31 | fun String.publicExtensionFun() { } -------------------------------------------------------------------------------- /grammar/toplevel.grm: -------------------------------------------------------------------------------- 1 | /** 2 | # Syntax 3 | 4 | Relevant pages: [Packages](packages.html) 5 | */ 6 | 7 | [start] 8 | kotlinFile 9 | : preamble topLevelObject* 10 | ; 11 | 12 | [start] 13 | script 14 | : preamble expression* 15 | ; 16 | 17 | preamble 18 | : fileAnnotations? packageHeader? import* 19 | ; 20 | 21 | fileAnnotations 22 | : fileAnnotation* 23 | ; 24 | 25 | fileAnnotation 26 | : "@" "file" ":" ("[" unescapedAnnotation+ "]" | unescapedAnnotation) 27 | ; 28 | 29 | packageHeader 30 | : modifiers "package" SimpleName{"."} SEMI? 31 | ; 32 | 33 | /** 34 | See [Packages](packages.html) 35 | */ 36 | 37 | import 38 | : "import" SimpleName{"."} ("." "*" | "as" SimpleName)? SEMI? 39 | ; 40 | 41 | /** 42 | See [Imports](packages.html#imports) 43 | */ 44 | 45 | topLevelObject 46 | : class 47 | : object 48 | : function 49 | : property 50 | : typeAlias 51 | ; 52 | 53 | typeAlias 54 | : modifiers "typealias" SimpleName typeParameters? "=" type 55 | ; 56 | -------------------------------------------------------------------------------- /syntaxes/kotlin.configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": [ "/*", "*/" ] 5 | }, 6 | "brackets": [ 7 | ["{", "}"], 8 | ["[", "]"], 9 | ["(", ")"], 10 | ["<", ">"] 11 | ], 12 | "autoClosingPairs": [ 13 | { "open": "{", "close": "}" }, 14 | { "open": "[", "close": "]" }, 15 | { "open": "(", "close": ")" }, 16 | { "open": "'", "close": "'", "notIn": ["string", "comment"] }, 17 | { "open": "\"", "close": "\"", "notIn": ["string"] }, 18 | { "open": "/*", "close": " */", "notIn": ["string"] } 19 | ], 20 | "surroundingPairs": [ 21 | ["{", "}"], 22 | ["[", "]"], 23 | ["(", ")"], 24 | ["<", ">"], 25 | ["'", "'"], 26 | ["\"", "\""] 27 | ], 28 | "folding": { 29 | "offSide": true, 30 | "markers": { 31 | "start": "^\\s*//\\s*#region", 32 | "end": "^\\s*//\\s*#endregion" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/LintTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.Diagnostic 4 | import org.hamcrest.Matchers.* 5 | import org.junit.Assert.assertThat 6 | import org.junit.Test 7 | 8 | class LintTest: SingleFileTestFixture("lint", "LintErrors.kt") { 9 | @Test fun `report error on open`() { 10 | languageServer.textDocumentService.debounceLint.waitForPendingTask() 11 | 12 | assertThat(diagnostics, not(empty())) 13 | } 14 | 15 | @Test fun `only lint once for many edits in a short period`() { 16 | var text = "1" 17 | for (i in 1..10) { 18 | val newText = text + "1" 19 | 20 | replace(file, 3, 16, text, newText) 21 | text = newText 22 | } 23 | 24 | languageServer.textDocumentService.debounceLint.waitForPendingTask() 25 | 26 | assertThat(diagnostics, not(empty())) 27 | assertThat(languageServer.textDocumentService.lintCount, lessThan(5)) 28 | } 29 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/DefinitionTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.hamcrest.Matchers.* 4 | import org.junit.Assert.assertThat 5 | import org.junit.Before 6 | import org.junit.Test 7 | 8 | class DefinitionTest: LanguageServerTestFixture("definition") { 9 | val file = "GoFrom.kt" 10 | 11 | @Before fun `open GoFrom`() { 12 | open(file) 13 | } 14 | 15 | @Test fun `go to a definition in the same file`() { 16 | val definitions = languageServer.textDocumentService.definition(textDocumentPosition(file, 3, 24)).get() 17 | 18 | assertThat(definitions, hasSize(1)) 19 | assertThat(definitions, hasItem(hasProperty("uri", containsString("GoFrom.kt")))) 20 | } 21 | 22 | @Test fun `go to a definition in a different file`() { 23 | val definitions = languageServer.textDocumentService.definition(textDocumentPosition(file, 4, 24)).get() 24 | 25 | assertThat(definitions, hasSize(1)) 26 | assertThat(definitions, hasItem(hasProperty("uri", containsString("GoTo.kt")))) 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/CompiledFileTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.hamcrest.Matchers.equalTo 4 | import org.junit.Assert.assertThat 5 | import org.junit.Test 6 | import java.nio.file.Files 7 | 8 | class CompiledFileTest { 9 | val compiledFile = compileFile() 10 | 11 | fun compileFile(): CompiledFile { 12 | val compiler = Compiler(setOf()) 13 | val file = testResourcesRoot().resolve("compiledFile/CompiledFileExample.kt") 14 | val content = Files.readAllLines(file).joinToString("\n") 15 | val parse = compiler.createFile(content, file) 16 | val classPath = CompilerClassPath() 17 | val sourcePath = listOf(parse) 18 | val (context, container) = compiler.compileFiles(sourcePath, sourcePath) 19 | return CompiledFile(content, parse, context, container, sourcePath, classPath) 20 | } 21 | 22 | @Test fun `typeAtPoint should return type for x`() { 23 | val type = compiledFile.typeAtPoint(87)!! 24 | 25 | assertThat(type.toString(), equalTo("Int")) 26 | } 27 | } -------------------------------------------------------------------------------- /lib/test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | import * as testRunner from 'vscode/lib/testrunner'; 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 George Fraser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/test/resources/signatureHelp/SignatureHelp.kt: -------------------------------------------------------------------------------- 1 | private object SignatureHelp { 2 | fun main(param: String) { 3 | Target.foo() 4 | Constructor() 5 | Target.multiParam("1", ) 6 | Target.oneOrTwoArgs("1", ) 7 | } 8 | } 9 | 10 | private object Target { 11 | /** 12 | * Call foo with a String 13 | * @param bar String param 14 | */ 15 | fun foo(bar: String) { 16 | } 17 | 18 | /** 19 | * Call foo with an Int 20 | * @param bar Int param 21 | */ 22 | fun foo(bar: Int) { 23 | } 24 | 25 | fun multiParam(first: Int, second: Int) { 26 | } 27 | 28 | fun multiParam(first: String, second: String) { 29 | } 30 | 31 | fun oneOrTwoArgs(first: String) { 32 | } 33 | 34 | fun oneOrTwoArgs(first: String, second: String) { 35 | } 36 | } 37 | 38 | /** 39 | * Construct with a String 40 | * @param bar String param 41 | */ 42 | private class Constructor(bar: String) { 43 | /** 44 | * Construct with an Int 45 | * @param bar Int param 46 | */ 47 | constructor(bar: Int): this(bar.toString()) 48 | } -------------------------------------------------------------------------------- /src/test/resources/additionalWorkspace/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.javacs 5 | kotlin-language-server 6 | jar 7 | 1.0-SNAPSHOT 8 | test-project 9 | http://maven.apache.org 10 | 11 | 1.2.31 12 | 4.10 13 | 14 | 15 | 16 | ${project.basedir} 17 | 18 | 19 | 20 | 21 | org.jetbrains.kotlin 22 | kotlin-stdlib 23 | ${kotlin.version} 24 | 25 | 26 | 27 | junit 28 | junit 29 | ${junit.version} 30 | test 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/Main.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import java.io.InputStream 4 | import java.util.concurrent.Executors 5 | import org.eclipse.lsp4j.launch.LSPLauncher 6 | 7 | fun main(args: Array) { 8 | val server = KotlinLanguageServer() 9 | val input = ExitOnClose(System.`in`) 10 | val threads = Executors.newSingleThreadExecutor({ Thread(it, "client")}) 11 | val launcher = LSPLauncher.createServerLauncher(server, input, System.out, threads, { it }) 12 | server.connect(launcher.remoteProxy) 13 | launcher.startListening() 14 | } 15 | 16 | private class ExitOnClose(private val delegate: InputStream): InputStream() { 17 | override fun read(): Int = exitIfNegative { delegate.read() } 18 | 19 | override fun read(b: ByteArray): Int = exitIfNegative { delegate.read(b) } 20 | 21 | override fun read(b: ByteArray, off: Int, len: Int): Int = exitIfNegative { delegate.read(b, off, len) } 22 | 23 | private fun exitIfNegative(call: () -> Int): Int { 24 | val result = call() 25 | 26 | if (result < 0) { 27 | LOG.info("System.in has closed, exiting") 28 | 29 | System.exit(0) 30 | } 31 | 32 | return result 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/WorkspaceSymbolsTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.SymbolKind 4 | import org.eclipse.lsp4j.WorkspaceSymbolParams 5 | import org.hamcrest.Matchers.hasItem 6 | import org.hamcrest.Matchers.not 7 | import org.junit.Assert.assertThat 8 | import org.junit.Before 9 | import org.junit.Test 10 | 11 | class WorkspaceSymbolsTest: SingleFileTestFixture("symbols", "DocumentSymbols.kt") { 12 | @Test fun `find symbols in OtherFileSymbols`() { 13 | val found = languageServer.workspaceService.symbol(WorkspaceSymbolParams("")).get() 14 | val byKind = found.groupBy({ it.kind }, { it.name }) 15 | val all = found.map { it.name }.toList() 16 | 17 | assertThat(byKind[SymbolKind.Class], hasItem("OtherFileSymbols")) 18 | assertThat(byKind[SymbolKind.Constructor], hasItem("OtherFileSymbols")) 19 | assertThat(byKind[SymbolKind.Property], hasItem("otherFileProperty")) 20 | assertThat(byKind[SymbolKind.Function], hasItem("otherFileFunction")) 21 | assertThat(all, not(hasItem("aFunctionArg"))) 22 | assertThat(all, not(hasItem("aConstructorArg"))) 23 | assertThat(all, not(hasItem("otherFileLocalVariable"))) 24 | } 25 | } -------------------------------------------------------------------------------- /grammar/notation.grm: -------------------------------------------------------------------------------- 1 | /** 2 | # Notation 3 | 4 | This section informally explains the grammar notation used below. 5 | 6 | ## Symbols and naming 7 | 8 | _Terminal symbol_ names start with an uppercase letter, e.g. **SimpleName**.
9 | _Nonterminal symbol_ names start with a lowercase letter, e.g. **kotlinFile**.
10 | Each _production_ starts with a colon (**:**).
11 | _Symbol definitions_ may have many productions and are terminated by a semicolon (**;**).
12 | Symbol definitions may be prepended with _attributes_, e.g. `start` attribute denotes a start symbol. 13 | 14 | ## EBNF expressions 15 | 16 | Operator `|` denotes _alternative_.
17 | Operator `*` denotes _iteration_ (zero or more).
18 | Operator `+` denotes _iteration_ (one or more).
19 | Operator `?` denotes _option_ (zero or one).
20 | alpha`{`beta`}` denotes a nonempty _beta_-separated list of _alpha_'s.
21 | Operator `++` means that no space or comment is allowed between operands. 22 | 23 | # Semicolons 24 | 25 | Kotlin provides "semicolon inference": syntactically, subsentences (e.g., statements, declarations etc) are separated by 26 | the pseudo-token [SEMI](#SEMI), which stands for "semicolon or newline". In most cases, there's no need for semicolons in 27 | Kotlin code. 28 | 29 | */ 30 | -------------------------------------------------------------------------------- /grammar/modifiers.grm: -------------------------------------------------------------------------------- 1 | /** 2 | ## Modifiers 3 | */ 4 | 5 | modifiers 6 | : (modifier | annotations)* 7 | ; 8 | 9 | typeModifiers 10 | : (suspendModifier | annotations)* 11 | ; 12 | 13 | modifier 14 | : classModifier 15 | : accessModifier 16 | : varianceAnnotation 17 | : memberModifier 18 | : parameterModifier 19 | : typeParameterModifier 20 | : functionModifier 21 | : propertyModifier 22 | ; 23 | 24 | classModifier 25 | : "abstract" 26 | : "final" 27 | : "enum" 28 | : "open" 29 | : "annotation" 30 | : "sealed" 31 | : "data" 32 | ; 33 | 34 | memberModifier 35 | : "override" 36 | : "open" 37 | : "final" 38 | : "abstract" 39 | : "lateinit" 40 | ; 41 | 42 | accessModifier 43 | : "private" 44 | : "protected" 45 | : "public" 46 | : "internal" 47 | ; 48 | 49 | varianceAnnotation 50 | : "in" 51 | : "out" 52 | ; 53 | 54 | parameterModifier 55 | : "noinline" 56 | : "crossinline" 57 | : "vararg" 58 | ; 59 | 60 | typeParameterModifier 61 | : "reified" 62 | ; 63 | 64 | functionModifier 65 | : "tailrec" 66 | : "operator" 67 | : "infix" 68 | : "inline" 69 | : "external" 70 | : suspendModifier 71 | ; 72 | 73 | propertyModifier 74 | : "const" 75 | ; 76 | 77 | suspendModifier 78 | : "suspend" 79 | ; 80 | -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/CompilerFixtures.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.search.GlobalSearchScope 5 | import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM 6 | import org.jetbrains.kotlin.config.CompilerConfiguration 7 | import org.jetbrains.kotlin.container.ComponentProvider 8 | import org.jetbrains.kotlin.descriptors.PackagePartProvider 9 | import org.jetbrains.kotlin.psi.KtFile 10 | import org.jetbrains.kotlin.resolve.BindingTrace 11 | import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory 12 | 13 | object CompilerFixtures { 14 | @JvmStatic fun createContainer( 15 | project: Project, 16 | files: Collection, 17 | trace: BindingTrace, 18 | configuration: CompilerConfiguration, 19 | packagePartProvider: (GlobalSearchScope) -> PackagePartProvider): ComponentProvider { 20 | return TopDownAnalyzerFacadeForJVM.createContainer( 21 | project, 22 | emptyList(), 23 | trace, 24 | configuration, 25 | packagePartProvider, 26 | { storageManager, _ -> FileBasedDeclarationProviderFactory(storageManager, files) }) 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/util/utils.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.util 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.psi.PsiFile 5 | import org.javacs.kt.LOG 6 | import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf 7 | import java.nio.file.Paths 8 | import java.util.concurrent.CompletableFuture 9 | import kotlin.coroutines.experimental.buildSequence 10 | 11 | inline fun PsiElement.findParent() = 12 | this.parentsWithSelf.filterIsInstance().firstOrNull() 13 | 14 | fun PsiElement.preOrderTraversal(): Sequence { 15 | val root = this 16 | 17 | return buildSequence { 18 | yield(root) 19 | 20 | for (child in root.children) { 21 | yieldAll(child.preOrderTraversal()) 22 | } 23 | } 24 | } 25 | 26 | fun PsiFile.toPath() = 27 | Paths.get(this.originalFile.viewProvider.virtualFile.path) 28 | 29 | fun noResult(message: String, result: T): T { 30 | LOG.info(message) 31 | 32 | return result 33 | } 34 | 35 | fun noFuture(message: String, contents: T): CompletableFuture = noResult(message, CompletableFuture.completedFuture(contents)) 36 | 37 | fun emptyResult(message: String): List = noResult(message, emptyList()) 38 | 39 | fun nullResult(message: String): T? = noResult(message, null) -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/DocumentSymbolsTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.DocumentSymbolParams 4 | import org.eclipse.lsp4j.SymbolKind 5 | import org.eclipse.lsp4j.TextDocumentIdentifier 6 | import org.hamcrest.Matchers.hasItem 7 | import org.hamcrest.Matchers.not 8 | import org.junit.Assert.assertThat 9 | import org.junit.Before 10 | import org.junit.Test 11 | 12 | class DocumentSymbolsTest: LanguageServerTestFixture("symbols") { 13 | val file = "DocumentSymbols.kt" 14 | 15 | @Before fun `open DocumentSymbols`() { 16 | open(file) 17 | } 18 | 19 | @Test fun `find document symbols`() { 20 | val fileId = TextDocumentIdentifier(uri(file).toString()) 21 | val found = languageServer.textDocumentService.documentSymbol(DocumentSymbolParams(fileId)).get() 22 | val byKind = found.groupBy({ it.kind }, { it.name }) 23 | val all = found.map { it.name }.toList() 24 | 25 | assertThat(byKind[SymbolKind.Class], hasItem("DocumentSymbols")) 26 | assertThat(byKind[SymbolKind.Constructor], hasItem("DocumentSymbols")) 27 | assertThat(byKind[SymbolKind.Property], hasItem("aProperty")) 28 | assertThat(byKind[SymbolKind.Function], hasItem("aFunction")) 29 | assertThat(all, not(hasItem("aFunctionArg"))) 30 | assertThat(all, not(hasItem("aConstructorArg"))) 31 | } 32 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: watch" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "npm: watch" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /grammar/class.grm: -------------------------------------------------------------------------------- 1 | /** 2 | ## Classes 3 | 4 | See [Classes and Inheritance](classes.html) 5 | */ 6 | 7 | /* 8 | internal class Example>(protected val x : Foo, y : Some) 9 | : Bar(x), Foo by x, IAbstractSome by y.asAbstract() 10 | where 11 | T : Function 12 | { 13 | // members 14 | } 15 | */ 16 | 17 | class 18 | : modifiers ("class" | "interface") SimpleName 19 | typeParameters? 20 | primaryConstructor? 21 | (":" annotations delegationSpecifier{","})? 22 | typeConstraints 23 | (classBody? | enumClassBody) 24 | ; 25 | 26 | primaryConstructor 27 | : (modifiers "constructor")? ("(" functionParameter{","} ")") 28 | ; 29 | 30 | classBody 31 | : ("{" members "}")? 32 | ; 33 | 34 | members 35 | : memberDeclaration* 36 | ; 37 | 38 | delegationSpecifier 39 | : constructorInvocation // type and constructor arguments 40 | : userType 41 | : explicitDelegation 42 | ; 43 | 44 | explicitDelegation 45 | : userType "by" expression // internal this expression no foo {bar} is allowed 46 | ; 47 | 48 | typeParameters 49 | : "<" typeParameter{","} ">" 50 | ; 51 | 52 | typeParameter 53 | : modifiers SimpleName (":" userType)? 54 | ; 55 | 56 | /** 57 | See [Generic classes](generics.html) 58 | */ 59 | 60 | typeConstraints 61 | : ("where" typeConstraint{","})? 62 | ; 63 | 64 | typeConstraint 65 | : annotations SimpleName ":" type 66 | ; 67 | /** 68 | See [Generic constraints](generics.html#generic-constraints) 69 | */ -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/SimpleScriptTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.jetbrains.kotlin.cli.common.repl.* 4 | import org.jetbrains.kotlin.cli.jvm.repl.* 5 | import org.jetbrains.kotlin.config.* 6 | import org.jetbrains.kotlin.script.* 7 | import com.intellij.openapi.util.* 8 | import org.jetbrains.kotlin.cli.common.messages.* 9 | import org.junit.* 10 | import org.junit.Assert.* 11 | import org.hamcrest.Matchers.* 12 | import org.jetbrains.kotlin.load.java.JvmAbi 13 | 14 | class SimpleScriptTest { 15 | @Test fun basicScript() { 16 | val scriptDef = KotlinScriptDefinition(Any::class) 17 | val repl = GenericReplEvaluator(listOf()) 18 | val config = CompilerConfiguration() 19 | config.put(CommonConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME) 20 | val compiler = GenericReplCompiler(scriptDef, config, MessageCollector.NONE) 21 | val compilerState = compiler.createState() 22 | val replState = repl.createState() 23 | 24 | var line = compiler.compile(compilerState, ReplCodeLine(1, 1, "val x = 1")) 25 | var result = repl.eval(replState, line as ReplCompileResult.CompiledClasses) 26 | 27 | line = compiler.compile(compilerState, ReplCodeLine(2, 2, "x")) 28 | result = repl.eval(replState, line as ReplCompileResult.CompiledClasses) 29 | 30 | when (result) { 31 | is ReplEvalResult.ValueResult -> println(result.value) 32 | is ReplEvalResult.UnitResult -> println('_') 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/diagnostic/convertDiagnostic.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.diagnostic 2 | 3 | import org.eclipse.lsp4j.DiagnosticSeverity 4 | import org.javacs.kt.position.range 5 | import org.javacs.kt.util.toPath 6 | import org.jetbrains.kotlin.diagnostics.Severity 7 | import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages 8 | import java.nio.file.Path 9 | 10 | typealias LangServerDiagnostic = org.eclipse.lsp4j.Diagnostic 11 | typealias KotlinDiagnostic = org.jetbrains.kotlin.diagnostics.Diagnostic 12 | 13 | fun convertDiagnostic(diagnostic: KotlinDiagnostic): List> { 14 | val path = diagnostic.psiFile.toPath() 15 | val content = diagnostic.psiFile.text 16 | 17 | return diagnostic.textRanges.map { 18 | val d = LangServerDiagnostic( 19 | range(content, it), 20 | message(diagnostic), 21 | severity(diagnostic.severity), 22 | "kotlin", 23 | code(diagnostic)) 24 | Pair(path, d) 25 | } 26 | } 27 | 28 | private fun code(diagnostic: KotlinDiagnostic) = 29 | diagnostic.factory.name 30 | 31 | private fun message(diagnostic: KotlinDiagnostic) = 32 | DefaultErrorMessages.render(diagnostic) 33 | 34 | private fun severity(severity: Severity): DiagnosticSeverity = 35 | when (severity) { 36 | Severity.INFO -> DiagnosticSeverity.Information 37 | Severity.ERROR -> DiagnosticSeverity.Error 38 | Severity.WARNING -> DiagnosticSeverity.Warning 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/docs/findDoc.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.docs 2 | 3 | import org.javacs.kt.util.preOrderTraversal 4 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource 5 | import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag 6 | import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag 7 | import org.jetbrains.kotlin.psi.KtDeclaration 8 | import org.jetbrains.kotlin.psi.KtParameter 9 | import org.jetbrains.kotlin.psi.KtPrimaryConstructor 10 | import org.jetbrains.kotlin.psi.psiUtil.parents 11 | import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils 12 | 13 | fun findDoc(declaration: DeclarationDescriptorWithSource): KDocTag? { 14 | val source = DescriptorToSourceUtils.descriptorToDeclaration(declaration)?.navigationElement 15 | 16 | return when (source) { 17 | is KtParameter -> { 18 | var container = source.parents.filterIsInstance().firstOrNull() ?: return null 19 | if (container is KtPrimaryConstructor) 20 | container = container.parents.filterIsInstance().firstOrNull() ?: return null 21 | val doc = container.docComment ?: return null 22 | val descendants = doc.preOrderTraversal() 23 | val tags = descendants.filterIsInstance() 24 | val params = tags.filter { it.knownTag == KDocKnownTag.PARAM } 25 | val matchName = params.filter { it.getSubjectName() == declaration.name.toString() } 26 | 27 | return matchName.firstOrNull() 28 | } 29 | is KtPrimaryConstructor -> { 30 | val container = source.parents.filterIsInstance().firstOrNull() ?: return null 31 | val doc = container.docComment ?: return null 32 | doc.findSectionByTag(KDocKnownTag.CONSTRUCTOR) ?: doc.getDefaultSection() 33 | } 34 | is KtDeclaration -> { 35 | val doc = source.docComment ?: return null 36 | doc.getDefaultSection() 37 | } 38 | else -> null 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/AdditionalWorkspaceTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams 4 | import org.eclipse.lsp4j.WorkspaceFolder 5 | import org.eclipse.lsp4j.WorkspaceFoldersChangeEvent 6 | import org.hamcrest.Matchers.* 7 | import org.junit.Assert.assertThat 8 | import org.junit.Assert.fail 9 | import org.junit.Ignore 10 | import org.junit.Test 11 | 12 | class AdditionalWorkspaceTest: LanguageServerTestFixture("mainWorkspace") { 13 | val file = "MainWorkspaceFile.kt" 14 | 15 | fun addWorkspaceRoot() { 16 | val folder = WorkspaceFolder() 17 | folder.uri = absoluteWorkspaceRoot("additionalWorkspace").toUri().toString() 18 | 19 | val addWorkspace = DidChangeWorkspaceFoldersParams() 20 | addWorkspace.event = WorkspaceFoldersChangeEvent() 21 | addWorkspace.event.added = listOf(folder) 22 | 23 | languageServer.workspaceService.didChangeWorkspaceFolders(addWorkspace) 24 | } 25 | 26 | @Test fun `junit should be on classpath`() { 27 | addWorkspaceRoot() 28 | open(file) 29 | 30 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 5, 14)).get()!! 31 | 32 | assertThat(hover.contents, not(emptyList())) 33 | assertThat(hover.contents.first().right.value, containsString("fun assertTrue")) 34 | } 35 | 36 | @Ignore // TODO 37 | @Test fun `recompile all when classpath changes`() { 38 | open(file) 39 | 40 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 5, 14)).get() 41 | assertThat("No hover before JUnit is added to classpath", hover, nullValue()) 42 | 43 | addWorkspaceRoot() 44 | val hoverAgain = languageServer.textDocumentService.hover(textDocumentPosition(file, 5, 14)).get() ?: return fail("No hover") 45 | assertThat(hoverAgain.contents, not(emptyList())) 46 | assertThat(hoverAgain.contents.first().right.value, containsString("fun assertTrue")) 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/LogFormat.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import java.io.PrintWriter 4 | import java.io.StringWriter 5 | import java.util.* 6 | import java.util.logging.Formatter 7 | import java.util.logging.LogRecord 8 | import java.util.logging.Logger 9 | 10 | val LOG = LogFormat.createLogger() 11 | 12 | object LogFormat: Formatter() { 13 | fun createLogger(): Logger { 14 | val result = Logger.getLogger("main") 15 | val root = Logger.getLogger("") 16 | 17 | for (each in root.handlers) { 18 | each.formatter = LogFormat 19 | } 20 | 21 | return result 22 | } 23 | 24 | private const val format = "%1\$tT.%1\$tL %2\$s %3\$s %4\$s %5\$s%6\$s%n" 25 | private val date = Date() 26 | 27 | private var maxSource = 0 28 | private var maxThread = 0 29 | 30 | override fun format(record: LogRecord): String { 31 | date.time = record.millis 32 | var source: String 33 | if (record.sourceClassName != null) { 34 | source = record.sourceClassName.split(".").last() 35 | if (record.sourceMethodName != null) { 36 | source += "#" + record.sourceMethodName 37 | } 38 | } else { 39 | source = record.loggerName 40 | } 41 | maxSource = Math.max(maxSource, source.length + 1) 42 | maxSource = Math.min(maxSource, 50) 43 | source = source.padEnd(maxSource, ' ') 44 | 45 | val message = formatMessage(record) 46 | var throwable = "" 47 | if (record.thrown != null) { 48 | val sw = StringWriter() 49 | val pw = PrintWriter(sw) 50 | pw.println() 51 | record.thrown.printStackTrace(pw) 52 | pw.close() 53 | throwable = sw.toString() 54 | } 55 | 56 | var thread = Thread.currentThread().name 57 | maxThread = Math.max(maxThread, thread.length + 1) 58 | maxThread = Math.min(maxThread, 20) 59 | thread = thread.padEnd(maxThread, ' ') 60 | 61 | return String.format( 62 | format, date, record.level.localizedName, thread, source, message, throwable) 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/CompilerClassPath.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.javacs.kt.classpath.findClassPath 4 | import java.nio.file.Path 5 | 6 | class CompilerClassPath { 7 | private val workspaceRoots = mutableSetOf() 8 | private val classPath = mutableSetOf() 9 | var compiler = Compiler(classPath) 10 | private set 11 | 12 | private fun refresh() { 13 | val newClassPath = findClassPath(workspaceRoots) 14 | 15 | if (newClassPath != classPath) { 16 | val added = newClassPath - classPath 17 | val removed = classPath - newClassPath 18 | 19 | logAdded(added) 20 | logRemoved(removed) 21 | 22 | classPath.removeAll(removed) 23 | classPath.addAll(added) 24 | compiler = Compiler(classPath) 25 | } 26 | } 27 | 28 | fun addWorkspaceRoot(root: Path) { 29 | LOG.info("Searching for dependencies in workspace root ${root}") 30 | 31 | workspaceRoots.add(root) 32 | 33 | refresh() 34 | } 35 | 36 | fun removeWorkspaceRoot(root: Path) { 37 | LOG.info("Remove dependencies from workspace root ${root}") 38 | 39 | workspaceRoots.remove(root) 40 | 41 | refresh() 42 | } 43 | 44 | fun createdOnDisk(file: Path) { 45 | changedOnDisk(file) 46 | } 47 | 48 | fun deletedOnDisk(file: Path) { 49 | changedOnDisk(file) 50 | } 51 | 52 | fun changedOnDisk(file: Path) { 53 | if (file.fileName.toString() == "pom.xml") 54 | refresh() 55 | } 56 | } 57 | 58 | private fun logAdded(sources: Collection) { 59 | when { 60 | sources.isEmpty() -> return 61 | sources.size > 5 -> LOG.info("Adding ${sources.size} files to class path") 62 | else -> LOG.info("Adding ${sources.joinToString(", ")} to class path") 63 | } 64 | } 65 | 66 | private fun logRemoved(sources: Collection) { 67 | when { 68 | sources.isEmpty() -> return 69 | sources.size > 5 -> LOG.info("Removing ${sources.size} files from class path") 70 | else -> LOG.info("Removing ${sources.joinToString(", ")} from class path") 71 | } 72 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kotlin", 3 | "displayName": "Kotlin", 4 | "description": "Kotlin language support", 5 | "icon": "Icon128.png", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/georgewfraser/kotlin-language-server.git" 9 | }, 10 | "version": "0.1.0", 11 | "preview": true, 12 | "publisher": "georgewfraser", 13 | "engines": { 14 | "vscode": "^1.21.0" 15 | }, 16 | "categories": [ 17 | "Languages", 18 | "Linters" 19 | ], 20 | "keywords": [ 21 | "kotlin" 22 | ], 23 | "activationEvents": [ 24 | "onLanguage:kotlin" 25 | ], 26 | "main": "./out/extension", 27 | "files": [ 28 | "target/KotlinLanguageServer.jar" 29 | ], 30 | "contributes": { 31 | "languages": [ 32 | { 33 | "id": "kotlin", 34 | "aliases": [ 35 | "Kotlin" 36 | ], 37 | "extensions": [ 38 | ".kt", 39 | ".kts" 40 | ], 41 | "configuration": "./syntaxes/kotlin.configuration.json" 42 | } 43 | ], 44 | "grammars": [ 45 | { 46 | "language": "kotlin", 47 | "scopeName": "source.kotlin", 48 | "path": "./syntaxes/Kotlin.tmLanguage.json" 49 | }, 50 | { 51 | "scopeName": "markdown.kotlin.codeblock", 52 | "path": "./syntaxes/codeblock.json", 53 | "injectTo": [ 54 | "text.html.markdown" 55 | ], 56 | "embeddedLanguages": { 57 | "meta.embedded.block.kotlin": "kotlin" 58 | } 59 | } 60 | ] 61 | }, 62 | "scripts": { 63 | "vscode:prepublish": "npm run compile", 64 | "compile": "tsc -p ./", 65 | "watch": "tsc -watch -p ./", 66 | "postinstall": "node ./node_modules/vscode/bin/install", 67 | "test": "npm run compile && node ./node_modules/vscode/bin/test" 68 | }, 69 | "devDependencies": { 70 | "typescript": "^2.6.1", 71 | "vscode": "^1.1.6", 72 | "@types/node": "^7.0.43", 73 | "@types/mocha": "^2.2.42" 74 | }, 75 | "dependencies": { 76 | "vscode-languageclient": "^4.0.0" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.* 4 | import org.eclipse.lsp4j.services.WorkspaceService 5 | import org.javacs.kt.symbols.workspaceSymbols 6 | import java.net.URI 7 | import java.nio.file.Paths 8 | import java.util.concurrent.CompletableFuture 9 | 10 | class KotlinWorkspaceService(private val sf: SourceFiles, private val sp: SourcePath, private val cp: CompilerClassPath) : WorkspaceService { 11 | 12 | override fun didChangeWatchedFiles(params: DidChangeWatchedFilesParams) { 13 | for (change in params.changes) { 14 | val path = Paths.get(URI.create(change.uri)) 15 | 16 | when (change.type) { 17 | FileChangeType.Created -> { 18 | sf.createdOnDisk(path) 19 | cp.createdOnDisk(path) 20 | } 21 | FileChangeType.Deleted -> { 22 | sf.deletedOnDisk(path) 23 | cp.deletedOnDisk(path) 24 | } 25 | FileChangeType.Changed -> { 26 | sf.changedOnDisk(path) 27 | cp.changedOnDisk(path) 28 | } 29 | null -> { 30 | // Nothing to do 31 | } 32 | } 33 | } 34 | } 35 | 36 | override fun didChangeConfiguration(params: DidChangeConfigurationParams) { 37 | LOG.info(params.toString()) 38 | } 39 | 40 | override fun symbol(params: WorkspaceSymbolParams): CompletableFuture> { 41 | val result = workspaceSymbols(params.query, sp) 42 | 43 | return CompletableFuture.completedFuture(result) 44 | } 45 | 46 | override fun didChangeWorkspaceFolders(params: DidChangeWorkspaceFoldersParams) { 47 | for (change in params.event.added) { 48 | LOG.info("Adding workspace ${change.uri} to source path") 49 | 50 | val root = Paths.get(URI.create(change.uri)) 51 | 52 | sf.addWorkspaceRoot(root) 53 | cp.addWorkspaceRoot(root) 54 | } 55 | for (change in params.event.removed) { 56 | LOG.info("Dropping workspace ${change.uri} from source path") 57 | 58 | val root = Paths.get(URI.create(change.uri)) 59 | 60 | sf.removeWorkspaceRoot(root) 61 | cp.removeWorkspaceRoot(root) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.* 4 | import org.eclipse.lsp4j.jsonrpc.messages.Either 5 | import org.eclipse.lsp4j.services.LanguageClient 6 | import org.eclipse.lsp4j.services.LanguageClientAware 7 | import org.eclipse.lsp4j.services.LanguageServer 8 | import java.net.URI 9 | import java.nio.file.Paths 10 | import java.util.concurrent.CompletableFuture 11 | import java.util.concurrent.CompletableFuture.completedFuture 12 | 13 | class KotlinLanguageServer: LanguageServer, LanguageClientAware { 14 | val classPath = CompilerClassPath() 15 | val sourcePath = SourcePath(classPath) 16 | val sourceFiles = SourceFiles(sourcePath) 17 | private val workspaces = KotlinWorkspaceService(sourceFiles, sourcePath, classPath) 18 | private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath) 19 | 20 | override fun connect(client: LanguageClient) { 21 | textDocuments.connect(client) 22 | 23 | LOG.info("Connected to client") 24 | } 25 | 26 | override fun shutdown(): CompletableFuture { 27 | return completedFuture(null) 28 | } 29 | 30 | override fun getTextDocumentService(): KotlinTextDocumentService { 31 | return textDocuments 32 | } 33 | 34 | override fun exit() { 35 | } 36 | 37 | override fun initialize(params: InitializeParams): CompletableFuture { 38 | val capabilities = ServerCapabilities() 39 | capabilities.setTextDocumentSync(TextDocumentSyncKind.Incremental) 40 | capabilities.workspace = WorkspaceServerCapabilities() 41 | capabilities.workspace.workspaceFolders = WorkspaceFoldersOptions() 42 | capabilities.workspace.workspaceFolders.supported = true 43 | capabilities.workspace.workspaceFolders.changeNotifications = Either.forRight(true) 44 | capabilities.hoverProvider = true 45 | capabilities.completionProvider = CompletionOptions(false, listOf(".")) 46 | capabilities.signatureHelpProvider = SignatureHelpOptions(listOf("(", ",")) 47 | capabilities.definitionProvider = true 48 | capabilities.documentSymbolProvider = true 49 | capabilities.workspaceSymbolProvider = true 50 | capabilities.referencesProvider = true 51 | 52 | if (params.rootUri != null) { 53 | LOG.info("Adding workspace ${params.rootUri} to source path") 54 | 55 | val root = Paths.get(URI.create(params.rootUri)) 56 | 57 | sourceFiles.addWorkspaceRoot(root) 58 | classPath.addWorkspaceRoot(root) 59 | } 60 | 61 | return completedFuture(InitializeResult(capabilities)) 62 | } 63 | 64 | override fun getWorkspaceService(): KotlinWorkspaceService { 65 | return workspaces 66 | } 67 | } -------------------------------------------------------------------------------- /grammar/class_members.grm: -------------------------------------------------------------------------------- 1 | /** 2 | ### Class members 3 | */ 4 | 5 | /* 6 | class Example(a : Foo, i : Int) : Bar(i), Some { 7 | 8 | // functions 9 | abstract fun foo(a : Bar) 10 | 11 | fun foo(a : Bar) = 0 12 | 13 | fun foo(a : Bar) = { 14 | return 0 15 | } 16 | 17 | fun foo(a : Bar) { // return type is Unit 18 | 19 | // properties 20 | val x : Int = 5 21 | var y : Double = 7.0d 22 | var z : String = "SDfsdf" { 23 | get() = $z + "sdfsd" 24 | private set(s : String) { $z = s } 25 | } 26 | } 27 | */ 28 | 29 | memberDeclaration 30 | : companionObject 31 | : object 32 | : function 33 | : property 34 | : class 35 | : typeAlias 36 | : anonymousInitializer 37 | : secondaryConstructor 38 | ; 39 | 40 | anonymousInitializer 41 | : "init" block 42 | ; 43 | 44 | companionObject 45 | : modifiers "companion" "object" SimpleName? (":" delegationSpecifier{","})? classBody? 46 | ; 47 | 48 | valueParameters 49 | : "(" functionParameter{","}? ")" 50 | ; 51 | 52 | functionParameter 53 | : modifiers ("val" | "var")? parameter ("=" expression)? 54 | ; 55 | 56 | block 57 | : "{" statements "}" 58 | ; 59 | 60 | function 61 | : modifiers "fun" 62 | typeParameters? 63 | (type ".")? 64 | SimpleName 65 | typeParameters? valueParameters (":" type)? 66 | typeConstraints 67 | functionBody? 68 | ; 69 | 70 | functionBody 71 | : block 72 | : "=" expression 73 | ; 74 | 75 | variableDeclarationEntry 76 | : SimpleName (":" type)? 77 | ; 78 | 79 | multipleVariableDeclarations 80 | : "(" variableDeclarationEntry{","} ")" 81 | ; 82 | 83 | property 84 | : modifiers ("val" | "var") 85 | typeParameters? 86 | (type ".")? 87 | (multipleVariableDeclarations | variableDeclarationEntry) 88 | typeConstraints 89 | ("by" | "=" expression SEMI?)? 90 | (getter? setter? | setter? getter?) SEMI? 91 | ; 92 | /** 93 | See [Properties and Fields](properties.html) 94 | */ 95 | 96 | getter 97 | : modifiers "get" 98 | : modifiers "get" "(" ")" (":" type)? functionBody 99 | ; 100 | 101 | setter 102 | : modifiers "set" 103 | : modifiers "set" "(" modifiers (SimpleName | parameter) ")" functionBody 104 | ; 105 | 106 | parameter 107 | : SimpleName ":" type 108 | ; 109 | 110 | object 111 | : modifiers "object" SimpleName primaryConstructor? (":" delegationSpecifier{","})? classBody? 112 | ; 113 | 114 | secondaryConstructor 115 | : modifiers "constructor" valueParameters (":" constructorDelegationCall)? block 116 | ; 117 | 118 | constructorDelegationCall 119 | : "this" valueArguments 120 | : "super" valueArguments 121 | ; 122 | /** 123 | See [Object expressions and Declarations](object-declarations.html) 124 | */ 125 | -------------------------------------------------------------------------------- /grammar/lexical.grm: -------------------------------------------------------------------------------- 1 | /** 2 | # Lexical structure 3 | */ 4 | 5 | [helper] 6 | LongSuffix 7 | : "L" 8 | ; 9 | 10 | IntegerLiteral 11 | : DecimalLiteral LongSuffix? 12 | : HexadecimalLiteral LongSuffix? 13 | : BinaryLiteral LongSuffix? 14 | ; 15 | 16 | [helper] 17 | Digit 18 | : ["0".."9"] 19 | ; 20 | 21 | DecimalLiteral 22 | : Digit 23 | : Digit (Digit | "_")* Digit 24 | ; 25 | 26 | FloatLiteral 27 | : 28 | ; 29 | 30 | [helper] 31 | HexDigit 32 | : Digit | ["A".."F", "a".."f"] 33 | ; 34 | 35 | HexadecimalLiteral 36 | : "0" ("x" | "X") HexDigit 37 | : "0" ("x" | "X") HexDigit (HexDigit | "_")* HexDigit 38 | ; 39 | 40 | [helper] 41 | BinaryDigit 42 | : ("0" | "1") 43 | ; 44 | 45 | BinaryLiteral 46 | : "0" ("b" | "B") BinaryDigit 47 | : "0" ("b" | "B") BinaryDigit (BinaryDigit | "_")* BinaryDigit 48 | 49 | CharacterLiteral 50 | : 51 | ; 52 | 53 | /** 54 | See [Basic types](basic-types.html) 55 | */ 56 | 57 | NoEscapeString 58 | : <"""-quoted string> 59 | ; 60 | 61 | RegularStringPart 62 | : 63 | ; 64 | 65 | ShortTemplateEntryStart 66 | : "$" 67 | ; 68 | 69 | EscapeSequence 70 | : UnicodeEscapeSequence | RegularEscapeSequence 71 | ; 72 | 73 | UnicodeEscapeSequence 74 | : "\u" HexDigit{4} 75 | ; 76 | 77 | RegularEscapeSequence 78 | : "\" 79 | ; 80 | 81 | /** 82 | See [String templates](basic-types.html#string-templates) 83 | */ 84 | 85 | SEMI 86 | : 87 | ; 88 | 89 | SimpleName 90 | : 91 | : "`" "`" 92 | ; 93 | 94 | /** 95 | See [Java interoperability](java-interop.html) 96 | */ 97 | 98 | LabelName 99 | : SimpleName 100 | ; 101 | 102 | /** 103 | See [Returns and jumps](returns.html) 104 | */ 105 | 106 | /* Symbols: 107 | 108 | [](){}<> 109 | , 110 | . 111 | : 112 | <= >= == != === !== 113 | + - * / % 114 | = 115 | += -= *= /= %= 116 | ++ -- ! 117 | && || -- may be just & and | 118 | => 119 | .. 120 | ? 121 | ?: 122 | ?. 123 | */ 124 | 125 | /* Keywords: 126 | package 127 | as 128 | type 129 | class 130 | this 131 | val 132 | var 133 | fun 134 | extension 135 | for 136 | null 137 | typeof 138 | new 139 | true 140 | false 141 | is 142 | in 143 | throw 144 | return 145 | break 146 | continue 147 | object 148 | if 149 | else 150 | while 151 | do 152 | when 153 | out 154 | ref 155 | try 156 | 157 | Soft: 158 | where -- in class, function and type headers 159 | by -- in a class header (Delegation specifier) 160 | get, set -- in a property definition 161 | import 162 | final 163 | abstract 164 | enum 165 | open 166 | annotation 167 | override 168 | private 169 | public 170 | internal 171 | protected 172 | catch 173 | finally 174 | */ 175 | -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/SignatureHelpTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.hamcrest.Matchers.* 4 | import org.junit.Assert.assertThat 5 | import org.junit.Ignore 6 | import org.junit.Test 7 | 8 | class SignatureHelpTest: SingleFileTestFixture("signatureHelp", "SignatureHelp.kt") { 9 | @Test fun `provide signature help for a function call`() { 10 | val help = languageServer.textDocumentService.signatureHelp(textDocumentPosition(file, 3, 20)).get()!! 11 | 12 | assertThat(help.signatures, hasSize(2)) 13 | assertThat(help.activeParameter, equalTo(0)) 14 | assertThat(help.activeSignature, equalTo(0)) 15 | assertThat(help.signatures.map { it.label }, hasItems("fun foo(bar: String): Unit", "fun foo(bar: Int): Unit")) 16 | assertThat(help.signatures.map { it.documentation }, hasItems("Call foo with a String", "Call foo with an Int")) 17 | assertThat(help.signatures.flatMap { it.parameters.map { it.label }}, hasItems("bar: String", "bar: Int")) 18 | assertThat(help.signatures.flatMap { it.parameters.map { it.documentation }}, hasItems("String param", "Int param")) 19 | } 20 | 21 | @Test fun `provide signature help for a constructor`() { 22 | val help = languageServer.textDocumentService.signatureHelp(textDocumentPosition(file, 4, 21)).get()!! 23 | 24 | assertThat(help.signatures, hasSize(2)) 25 | assertThat(help.activeParameter, equalTo(0)) 26 | assertThat(help.activeSignature, equalTo(0)) 27 | assertThat(help.signatures.map { it.label }, hasItems("constructor Constructor(bar: String)", "constructor Constructor(bar: Int)")) 28 | assertThat(help.signatures.map { it.documentation }, hasItems("Construct with a String", "Construct with an Int")) 29 | assertThat(help.signatures.flatMap { it.parameters.map { it.label }}, hasItems("bar: String", "bar: Int")) 30 | assertThat(help.signatures.flatMap { it.parameters.map { it.documentation }}, hasItems("String param", "Int param")) 31 | } 32 | 33 | @Test fun `find active parameter`() { 34 | val help = languageServer.textDocumentService.signatureHelp(textDocumentPosition(file, 5, 32)).get()!! 35 | 36 | assertThat(help.activeParameter, equalTo(1)) 37 | } 38 | 39 | @Test fun `find active parameter in the middle of list`() { 40 | val help = languageServer.textDocumentService.signatureHelp(textDocumentPosition(file, 5, 30)).get()!! 41 | 42 | assertThat(help.activeParameter, equalTo(0)) 43 | } 44 | 45 | @Ignore @Test fun `find active signature using types`() { 46 | val help = languageServer.textDocumentService.signatureHelp(textDocumentPosition(file, 5, 32)).get()!! 47 | 48 | assertThat(help.signatures[help.activeSignature].label, equalTo("fun multiParam(first: String, second: String): Unit")) 49 | } 50 | 51 | @Test fun `find active signature using number of arguments`() { 52 | val help = languageServer.textDocumentService.signatureHelp(textDocumentPosition(file, 6, 34)).get()!! 53 | 54 | assertThat(help.signatures[help.activeSignature].label, equalTo("fun oneOrTwoArgs(first: String, second: String): Unit")) 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/position/position.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.position 2 | 3 | import com.intellij.openapi.util.TextRange 4 | import com.intellij.psi.PsiElement 5 | import org.eclipse.lsp4j.Location 6 | import org.eclipse.lsp4j.Position 7 | import org.eclipse.lsp4j.Range 8 | import org.javacs.kt.util.toPath 9 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 10 | import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi 11 | import kotlin.math.max 12 | 13 | /** 14 | * Convert from 0-based line and column to 0-based offset 15 | */ 16 | fun offset(content: String, line: Int, char: Int): Int { 17 | assert(!content.contains('\r')) 18 | 19 | val reader = content.reader() 20 | var offset = 0 21 | 22 | var lineOffset = 0 23 | while (lineOffset < line) { 24 | val nextChar = reader.read() 25 | 26 | if (nextChar == -1) 27 | throw RuntimeException("Reached end of file before reaching line $line") 28 | 29 | if (nextChar.toChar() == '\n') 30 | lineOffset++ 31 | 32 | offset++ 33 | } 34 | 35 | var charOffset = 0 36 | while (charOffset < char) { 37 | val nextChar = reader.read() 38 | 39 | if (nextChar == -1) 40 | throw RuntimeException("Reached end of file before reaching char $char") 41 | 42 | charOffset++ 43 | offset++ 44 | } 45 | 46 | return offset 47 | } 48 | 49 | fun position(content: String, offset: Int): Position { 50 | val reader = content.reader() 51 | var line = 0 52 | var char = 0 53 | 54 | var find = 0 55 | while (find < offset) { 56 | val nextChar = reader.read() 57 | 58 | if (nextChar == -1) 59 | throw RuntimeException("Reached end of file before reaching offset $offset") 60 | 61 | find++ 62 | char++ 63 | 64 | if (nextChar.toChar() == '\n') { 65 | line++ 66 | char = 0 67 | } 68 | } 69 | 70 | return Position(line, char) 71 | } 72 | 73 | fun range(content: String, range: TextRange) = 74 | Range(position(content, range.startOffset), position(content, range.endOffset)) 75 | 76 | fun location(declaration: DeclarationDescriptor): Location? { 77 | val target = declaration.findPsi() ?: return null 78 | 79 | return location(target) 80 | } 81 | 82 | fun location(expr: PsiElement): Location { 83 | val content = expr.containingFile.text 84 | val file = expr.containingFile.toPath().toUri().toString() 85 | 86 | return Location(file, range(content, expr.textRange)) 87 | } 88 | 89 | /** 90 | * Region that has been changed 91 | */ 92 | fun changedRegion(oldContent: String, newContent: String): Pair? { 93 | if (oldContent == newContent) return null 94 | 95 | val prefix = oldContent.commonPrefixWith(newContent).length 96 | val suffix = oldContent.commonSuffixWith(newContent).length 97 | val oldEnd = max(oldContent.length - suffix, prefix) 98 | val newEnd = max(newContent.length - suffix, prefix) 99 | 100 | return Pair(TextRange(prefix, oldEnd), TextRange(prefix, newEnd)) 101 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/symbols/symbols.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.symbols 2 | 3 | import com.intellij.psi.PsiElement 4 | import org.eclipse.lsp4j.Location 5 | import org.eclipse.lsp4j.SymbolInformation 6 | import org.eclipse.lsp4j.SymbolKind 7 | import org.javacs.kt.SourcePath 8 | import org.javacs.kt.completion.containsCharactersInOrder 9 | import org.javacs.kt.position.range 10 | import org.javacs.kt.util.preOrderTraversal 11 | import org.javacs.kt.util.toPath 12 | import org.jetbrains.kotlin.psi.* 13 | import org.jetbrains.kotlin.psi.psiUtil.parents 14 | 15 | fun documentSymbols(file: KtFile): List = 16 | doDocumentSymbols(file).mapNotNull(::symbolInformation).toList() 17 | 18 | private fun doDocumentSymbols(file: KtFile): Sequence = 19 | file.preOrderTraversal().mapNotNull { pickImportantElements(it, true) } 20 | 21 | private const val MAX_SYMBOLS = 50 22 | 23 | fun workspaceSymbols(query: String, sp: SourcePath): List = 24 | doWorkspaceSymbols(sp) 25 | .filter { containsCharactersInOrder(it.name!!, query, false) } 26 | .mapNotNull(::symbolInformation) 27 | .take(MAX_SYMBOLS) 28 | .toList() 29 | 30 | private fun doWorkspaceSymbols(sp: SourcePath): Sequence = 31 | sp.all().asSequence().flatMap(::fileSymbols) 32 | 33 | private fun fileSymbols(file: KtFile): Sequence = 34 | file.preOrderTraversal().mapNotNull { pickImportantElements(it, false) } 35 | 36 | private fun pickImportantElements(node: PsiElement, includeLocals: Boolean): KtNamedDeclaration? = 37 | when (node) { 38 | is KtClassOrObject -> if (node.name == null) null else node 39 | is KtTypeAlias -> node 40 | is KtConstructor<*> -> node 41 | is KtNamedFunction -> if (!node.isLocal || includeLocals) node else null 42 | is KtProperty -> if (!node.isLocal || includeLocals) node else null 43 | is KtVariableDeclaration -> if (includeLocals) node else null 44 | else -> null 45 | } 46 | 47 | private fun symbolInformation(d: KtNamedDeclaration): SymbolInformation? { 48 | val name = d.name ?: return null 49 | 50 | return SymbolInformation(name, symbolKind(d), symbolLocation(d), symbolContainer(d)) 51 | } 52 | 53 | private fun symbolKind(d: KtNamedDeclaration): SymbolKind = 54 | when (d) { 55 | is KtClassOrObject -> SymbolKind.Class 56 | is KtTypeAlias -> SymbolKind.Interface 57 | is KtConstructor<*> -> SymbolKind.Constructor 58 | is KtNamedFunction -> SymbolKind.Function 59 | is KtProperty -> SymbolKind.Property 60 | is KtVariableDeclaration -> SymbolKind.Variable 61 | else -> throw IllegalArgumentException("Unexpected symbol $d") 62 | } 63 | 64 | private fun symbolLocation(d: KtNamedDeclaration): Location { 65 | val file = d.containingFile 66 | val uri = file.toPath().toUri().toString() 67 | val range = range(file.text, d.textRange) 68 | 69 | return Location(uri, range) 70 | } 71 | 72 | private fun symbolContainer(d: KtNamedDeclaration): String? = 73 | d.parents 74 | .filterIsInstance() 75 | .firstOrNull() 76 | ?.fqName.toString() -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/CompilerTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.hamcrest.Matchers.hasToString 4 | import org.jetbrains.kotlin.psi.KtExpression 5 | import org.jetbrains.kotlin.psi.KtNamedFunction 6 | import org.jetbrains.kotlin.psi.KtReferenceExpression 7 | import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf 8 | import org.jetbrains.kotlin.resolve.BindingContext 9 | import org.junit.Assert.assertThat 10 | import org.junit.Test 11 | import java.nio.file.Files 12 | 13 | class CompilerTest { 14 | val compiler = Compiler(setOf()) 15 | val myTestResources = testResourcesRoot().resolve("compiler") 16 | val file = myTestResources.resolve("FileToEdit.kt") 17 | val editedText = """ 18 | private class FileToEdit { 19 | val someVal = 1 20 | }""" 21 | 22 | @Test fun compileFile() { 23 | val content = Files.readAllLines(file).joinToString("\n") 24 | val original = compiler.createFile(content, file) 25 | val (context, _) = compiler.compileFile(original, listOf(original)) 26 | val psi = original.findElementAt(45)!! 27 | val kt = psi.parentsWithSelf.filterIsInstance().first() 28 | 29 | assertThat(context.getType(kt), hasToString("String")) 30 | } 31 | 32 | @Test fun newFile() { 33 | val original = compiler.createFile(editedText, file) 34 | val (context, _) = compiler.compileFile(original, listOf(original)) 35 | val psi = original.findElementAt(46)!! 36 | val kt = psi.parentsWithSelf.filterIsInstance().first() 37 | 38 | assertThat(context.getType(kt), hasToString("Int")) 39 | } 40 | 41 | @Test fun editFile() { 42 | val content = Files.readAllLines(file).joinToString("\n") 43 | val original = compiler.createFile(content, file) 44 | var (context, _) = compiler.compileFile(original, listOf(original)) 45 | var psi = original.findElementAt(46)!! 46 | var kt = psi.parentsWithSelf.filterIsInstance().first() 47 | 48 | assertThat(context.getType(kt), hasToString("String")) 49 | 50 | val edited = compiler.createFile(editedText, file) 51 | context = compiler.compileFile(edited, listOf(edited)).first 52 | psi = edited.findElementAt(46)!! 53 | kt = psi.parentsWithSelf.filterIsInstance().first() 54 | 55 | assertThat(context.getType(kt), hasToString("Int")) 56 | } 57 | 58 | @Test fun editRef() { 59 | val file1 = testResourcesRoot().resolve("hover/Recover.kt") 60 | val content = Files.readAllLines(file1).joinToString("\n") 61 | val original = compiler.createFile(content, file1) 62 | val (context, _) = compiler.compileFile(original, listOf(original)) 63 | val function = original.findElementAt(49)!!.parentsWithSelf.filterIsInstance().first() 64 | val scope = context.get(BindingContext.LEXICAL_SCOPE, function.bodyExpression)!! 65 | val recompile = compiler.createDeclaration("""private fun singleExpressionFunction() = intFunction()""") 66 | val (recompileContext, _) = compiler.compileExpression(recompile, scope, setOf(original)) 67 | val intFunctionRef = recompile.findElementAt(41)!!.parentsWithSelf.filterIsInstance().first() 68 | val target = recompileContext.get(BindingContext.REFERENCE_TARGET, intFunctionRef)!! 69 | 70 | assertThat(target.name, hasToString("intFunction")) 71 | } 72 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/HoverTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class HoverLiteralsTest: SingleFileTestFixture("hover", "Literals.kt") { 7 | @Test fun `string reference`() { 8 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 3, 19)).get()!! 9 | val contents = hover.contents.first().right 10 | 11 | assertEquals("kotlin", contents.language) 12 | assertEquals("val stringLiteral: String", contents.value) 13 | } 14 | } 15 | 16 | class HoverFunctionReferenceTest: SingleFileTestFixture("hover", "FunctionReference.kt") { 17 | @Test fun `function reference`() { 18 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 2, 45)).get()!! 19 | val contents = hover.contents.first().right 20 | 21 | assertEquals("kotlin", contents.language) 22 | assertEquals("fun isFoo(s: String): Boolean", contents.value) 23 | } 24 | } 25 | 26 | class HoverObjectReferenceTest: SingleFileTestFixture("hover", "ObjectReference.kt") { 27 | @Test fun `object reference`() { 28 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 2, 7)).get()!! 29 | val contents = hover.contents.first().right 30 | 31 | assertEquals("kotlin", contents.language) 32 | assertEquals("object AnObject", contents.value) 33 | } 34 | 35 | @Test fun `object reference with incomplete method`() { 36 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 6, 7)).get()!! 37 | val contents = hover.contents.first().right 38 | 39 | assertEquals("kotlin", contents.language) 40 | assertEquals("object AnObject", contents.value) 41 | } 42 | 43 | @Test fun `object reference with method`() { 44 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 10, 7)).get()!! 45 | val contents = hover.contents.first().right 46 | 47 | assertEquals("kotlin", contents.language) 48 | assertEquals("object AnObject", contents.value) 49 | } 50 | 51 | @Test fun `object method`() { 52 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 10, 15)).get()!! 53 | val contents = hover.contents.first().right 54 | 55 | assertEquals("kotlin", contents.language) 56 | assertEquals("fun doh(): Unit", contents.value) 57 | } 58 | } 59 | 60 | class HoverRecoverTest: SingleFileTestFixture("hover", "Recover.kt") { 61 | @Test fun `incrementally repair a single-expression function`() { 62 | replace(file, 2, 9, "\"Foo\"", "intFunction()") 63 | 64 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 2, 11)).get()!! 65 | val contents = hover.contents.first().right 66 | 67 | assertEquals("kotlin", contents.language) 68 | assertEquals("fun intFunction(): Int", contents.value) 69 | } 70 | 71 | @Test fun `incrementally repair a block function`() { 72 | replace(file, 5, 13, "\"Foo\"", "intFunction()") 73 | 74 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(file, 5, 13)).get()!! 75 | val contents = hover.contents.first().right 76 | 77 | assertEquals("kotlin", contents.language) 78 | assertEquals("fun intFunction(): Int", contents.value) 79 | } 80 | } 81 | 82 | class HoverAcrossFilesTest: LanguageServerTestFixture("hover") { 83 | @Test fun `resolve across files`() { 84 | val from = "ResolveFromFile.kt" 85 | val to = "ResolveToFile.kt" 86 | open(from) 87 | open(to) 88 | 89 | val hover = languageServer.textDocumentService.hover(textDocumentPosition(from, 3, 26)).get()!! 90 | val contents = hover.contents.first().right 91 | 92 | assertEquals("kotlin", contents.language) 93 | assertEquals("fun target(): Unit", contents.value) 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/signatureHelp/signatureHelp.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.signatureHelp 2 | 3 | import org.eclipse.lsp4j.ParameterInformation 4 | import org.eclipse.lsp4j.SignatureHelp 5 | import org.eclipse.lsp4j.SignatureInformation 6 | import org.javacs.kt.CompiledFile 7 | import org.javacs.kt.completion.DECL_RENDERER 8 | import org.javacs.kt.completion.identifierOverloads 9 | import org.javacs.kt.completion.memberOverloads 10 | import org.javacs.kt.docs.findDoc 11 | import org.javacs.kt.util.findParent 12 | import org.javacs.kt.util.nullResult 13 | import org.jetbrains.kotlin.descriptors.CallableDescriptor 14 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource 15 | import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor 16 | import org.jetbrains.kotlin.psi.KtCallExpression 17 | import org.jetbrains.kotlin.psi.KtDotQualifiedExpression 18 | import org.jetbrains.kotlin.psi.KtNameReferenceExpression 19 | import org.jetbrains.kotlin.psi.psiUtil.startOffset 20 | 21 | fun signatureHelpAt(file: CompiledFile, cursor: Int): SignatureHelp? { 22 | val call = file.parseAtPoint(cursor)?.findParent() ?: return nullResult("No call around ${file.describePosition(cursor)}") 23 | val candidates = candidates(call, file) 24 | val activeDeclaration = activeDeclaration(call, candidates) 25 | val activeParameter = activeParameter(call, cursor) 26 | val signatures = candidates.map(::toSignature) 27 | 28 | return SignatureHelp(signatures, activeDeclaration, activeParameter) 29 | } 30 | 31 | private fun toSignature(desc: CallableDescriptor): SignatureInformation { 32 | val label = DECL_RENDERER.render(desc) 33 | val params = desc.valueParameters.map(::toParameter) 34 | val docstring = docstring(desc) 35 | 36 | return SignatureInformation(label, docstring, params) 37 | } 38 | 39 | private fun toParameter(param: ValueParameterDescriptor): ParameterInformation { 40 | val label = DECL_RENDERER.renderValueParameters(listOf(param), false) 41 | val removeParens = label.substring(1, label.length - 1) 42 | val docstring = docstring(param) 43 | 44 | return ParameterInformation(removeParens, docstring) 45 | } 46 | 47 | private fun docstring(declaration: DeclarationDescriptorWithSource): String { 48 | val doc = findDoc(declaration) ?: return "" 49 | 50 | return doc.getContent().trim() 51 | } 52 | 53 | private fun candidates(call: KtCallExpression, file: CompiledFile): List { 54 | val target = call.calleeExpression!! 55 | val identifier = target.text // TODO when call is foo.bar(), is this bar or foo.bar ? 56 | val dotParent = target.findParent() 57 | if (dotParent != null) { 58 | val type = file.typeAtPoint(dotParent.receiverExpression.startOffset) ?: return emptyList() 59 | 60 | return memberOverloads(type, identifier).toList() 61 | } 62 | val idParent = target.findParent() 63 | if (idParent != null) { 64 | val scope = file.scopeAtPoint(idParent.startOffset) ?: return emptyList() 65 | 66 | return identifierOverloads(scope, identifier).toList() 67 | } 68 | return emptyList() 69 | } 70 | 71 | private fun activeDeclaration(call: KtCallExpression, candidates: List): Int { 72 | return candidates.indexOfFirst { isCompatibleWith(call, it) } 73 | } 74 | 75 | private fun isCompatibleWith(call: KtCallExpression, candidate: CallableDescriptor): Boolean { 76 | val argumentList = call.valueArgumentList ?: return true 77 | val nArguments = argumentList.text.count { it == ',' } + 1 78 | if (nArguments > candidate.valueParameters.size) 79 | return false 80 | 81 | for (arg in call.valueArguments) { 82 | if (arg.isNamed()) { 83 | if (candidate.valueParameters.none { arg.name == it.name.identifier }) 84 | return false 85 | } 86 | // TODO consider types as well 87 | } 88 | 89 | return true 90 | } 91 | 92 | private fun activeParameter(call: KtCallExpression, cursor: Int): Int { 93 | val args = call.valueArgumentList ?: return -1 94 | val text = args.text 95 | val beforeCursor = text.subSequence(0, cursor - args.textRange.startOffset) 96 | 97 | return beforeCursor.count { it == ','} 98 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/LanguageServerTestFixture.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.* 4 | import org.eclipse.lsp4j.services.LanguageClient 5 | import org.junit.Before 6 | import java.nio.file.Path 7 | import java.nio.file.Paths 8 | import java.util.concurrent.CompletableFuture 9 | 10 | abstract class LanguageServerTestFixture(relativeWorkspaceRoot: String): LanguageClient { 11 | val workspaceRoot = absoluteWorkspaceRoot(relativeWorkspaceRoot) 12 | val languageServer = createLanguageServer() 13 | val diagnostics = mutableListOf() 14 | 15 | fun absoluteWorkspaceRoot(relativeWorkspaceRoot: String): Path { 16 | val testResources = testResourcesRoot() 17 | return testResources.resolve(relativeWorkspaceRoot) 18 | } 19 | 20 | private fun createLanguageServer(): KotlinLanguageServer { 21 | val languageServer = KotlinLanguageServer() 22 | val init = InitializeParams() 23 | 24 | init.rootUri = workspaceRoot.toUri().toString() 25 | languageServer.initialize(init) 26 | languageServer.connect(this) 27 | 28 | return languageServer 29 | } 30 | 31 | fun textDocumentPosition(relativePath: String, line: Int, column: Int): TextDocumentPositionParams { 32 | val file = workspaceRoot.resolve(relativePath) 33 | val fileId = TextDocumentIdentifier(file.toUri().toString()) 34 | val position = position(line, column) 35 | 36 | return TextDocumentPositionParams(fileId, position) 37 | } 38 | 39 | fun position(line: Int, column: Int) = Position(line - 1, column - 1) 40 | 41 | fun uri(relativePath: String) = 42 | workspaceRoot.resolve(relativePath).toUri() 43 | 44 | fun referenceParams(relativePath: String, line: Int, column: Int): ReferenceParams { 45 | val request = ReferenceParams(ReferenceContext(true)) 46 | request.textDocument = TextDocumentIdentifier(uri(relativePath).toString()) 47 | request.position = position(line, column) 48 | return request 49 | } 50 | 51 | fun open(relativePath: String) { 52 | val file = workspaceRoot.resolve(relativePath) 53 | val content = file.toFile().readText() 54 | val document = TextDocumentItem(file.toUri().toString(), "Kotlin", 0, content) 55 | 56 | languageServer.textDocumentService.didOpen(DidOpenTextDocumentParams(document)) 57 | } 58 | 59 | private var version = 1 60 | 61 | fun replace(relativePath: String, line: Int, char: Int, oldText: String, newText: String) { 62 | val range = Range(position(line, char), Position(line - 1, char - 1 + oldText.length)) 63 | val edit = TextDocumentContentChangeEvent(range, oldText.length, newText) 64 | val doc = VersionedTextDocumentIdentifier(version++) 65 | doc.uri = uri(relativePath).toString() 66 | 67 | languageServer.textDocumentService.didChange(DidChangeTextDocumentParams(doc, listOf(edit))) 68 | } 69 | 70 | // LanguageClient functions 71 | 72 | override fun publishDiagnostics(diagnostics: PublishDiagnosticsParams) { 73 | this.diagnostics.addAll(diagnostics.diagnostics) 74 | } 75 | 76 | override fun showMessageRequest(request: ShowMessageRequestParams?): CompletableFuture? { 77 | LOG.info(request.toString()) 78 | 79 | return null 80 | } 81 | 82 | override fun telemetryEvent(`object`: Any?) { 83 | LOG.info(`object`.toString()) 84 | } 85 | 86 | override fun logMessage(message: MessageParams?) { 87 | LOG.info(message.toString()) 88 | } 89 | 90 | override fun showMessage(message: MessageParams?) { 91 | LOG.info(message.toString()) 92 | } 93 | } 94 | 95 | fun testResourcesRoot(): Path { 96 | val anchorTxt = LanguageServerTestFixture::class.java.getResource("/Anchor.txt").toURI() 97 | return Paths.get(anchorTxt).parent!! 98 | } 99 | 100 | open class SingleFileTestFixture(relativeWorkspaceRoot: String, val file: String): LanguageServerTestFixture(relativeWorkspaceRoot) { 101 | @Before fun openFile() { 102 | open(file) 103 | 104 | // Wait for lint, so subsequent replace(...) operations cause recovery 105 | languageServer.textDocumentService.debounceLint.waitForPendingTask() 106 | } 107 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/SourcePath.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.jetbrains.kotlin.container.ComponentProvider 4 | import org.jetbrains.kotlin.psi.KtFile 5 | import org.jetbrains.kotlin.resolve.BindingContext 6 | import org.jetbrains.kotlin.resolve.CompositeBindingContext 7 | import java.nio.file.Path 8 | 9 | class SourcePath(private val cp: CompilerClassPath) { 10 | private val files = mutableMapOf() 11 | 12 | private inner class SourceFile( 13 | val file: Path, 14 | var content: String, 15 | var parsed: KtFile? = null, 16 | var compiledFile: KtFile? = null, 17 | var compiledContext: BindingContext? = null, 18 | var compiledContainer: ComponentProvider? = null) { 19 | 20 | fun put(newContent: String) { 21 | content = newContent 22 | } 23 | 24 | fun parseIfChanged(): SourceFile { 25 | if (content != parsed?.text) { 26 | parsed = cp.compiler.createFile(content, file) 27 | } 28 | 29 | return this 30 | } 31 | 32 | fun compileIfNull(): SourceFile = 33 | parseIfChanged().doCompileIfNull() 34 | 35 | private fun doCompileIfNull(): SourceFile = 36 | if (compiledFile == null) 37 | doCompileIfChanged() 38 | else 39 | this 40 | 41 | fun compileIfChanged(): SourceFile = 42 | parseIfChanged().doCompileIfChanged() 43 | 44 | private fun doCompileIfChanged(): SourceFile { 45 | if (parsed?.text != compiledFile?.text) { 46 | LOG.info("Compiling ${file.fileName}") 47 | 48 | val (context, container) = cp.compiler.compileFile(parsed!!, all()) 49 | compiledContext = context 50 | compiledContainer = container 51 | compiledFile = parsed 52 | } 53 | 54 | return this 55 | } 56 | 57 | fun prepareCompiledFile(): CompiledFile = 58 | parseIfChanged().compileIfNull().doPrepareCompiledFile() 59 | 60 | private fun doPrepareCompiledFile(): CompiledFile = 61 | CompiledFile(content, compiledFile!!, compiledContext!!, compiledContainer!!, all(), cp) 62 | } 63 | 64 | fun put(file: Path, content: String) { 65 | assert(!content.contains('\r')) 66 | 67 | if (files.contains(file)) 68 | files[file]!!.put(content) 69 | else 70 | files[file] = SourceFile(file, content) 71 | } 72 | 73 | fun delete(file: Path) { 74 | files.remove(file) 75 | } 76 | 77 | /** 78 | * Get the latest content of a file 79 | */ 80 | fun content(file: Path): String = files[file]!!.content 81 | 82 | fun parsedFile(file: Path): KtFile = files[file]!!.parseIfChanged().parsed!! 83 | 84 | /** 85 | * Compile the latest version of a file 86 | */ 87 | fun currentVersion(file: Path): CompiledFile = 88 | files[file]!!.compileIfChanged().prepareCompiledFile() 89 | 90 | /** 91 | * Return whatever is the most-recent already-compiled version of `file` 92 | */ 93 | fun latestCompiledVersion(file: Path): CompiledFile = 94 | files[file]!!.prepareCompiledFile() 95 | 96 | /** 97 | * Compile changed files 98 | */ 99 | fun compileFiles(all: Collection): BindingContext { 100 | // Figure out what has changed 101 | val sources = all.map { files[it]!! } 102 | val changed = sources.filter { it.content != it.compiledFile?.text } 103 | 104 | // Compile changed files 105 | val parse = changed.map { it.parseIfChanged().parsed!! } 106 | val (context, container) = cp.compiler.compileFiles(parse, all()) 107 | 108 | // Update cache 109 | for (f in changed) { 110 | f.compiledFile = f.parsed 111 | f.compiledContext = context 112 | f.compiledContainer = container 113 | } 114 | 115 | // Combine with past compilations 116 | val combined = mutableListOf(context) 117 | val same = sources - changed 118 | combined.addAll(same.map { it.compiledContext!! }) 119 | 120 | return CompositeBindingContext.create(combined) 121 | } 122 | 123 | /** 124 | * Get parsed trees for all .kt files on source path 125 | */ 126 | fun all(): Collection = 127 | files.values.map { it.parseIfChanged().parsed!! } 128 | } -------------------------------------------------------------------------------- /lib/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // The module 'vscode' contains the VS Code extensibility API 3 | // Import the module and reference it with the alias vscode in your code below 4 | import * as VSCode from 'vscode'; 5 | import * as Path from "path"; 6 | import * as FS from "fs"; 7 | import {LanguageClient, LanguageClientOptions, ServerOptions} from "vscode-languageclient"; 8 | 9 | // this method is called when your extension is activated 10 | // your extension is activated the very first time the command is executed 11 | export function activate(context: VSCode.ExtensionContext) { 12 | console.log('Activating Kotlin language server...'); 13 | 14 | let javaExecutablePath = findJavaExecutable('java'); 15 | 16 | if (javaExecutablePath == null) { 17 | VSCode.window.showErrorMessage("Couldn't locate java in $JAVA_HOME or $PATH"); 18 | 19 | return; 20 | } 21 | 22 | // Options to control the language client 23 | let clientOptions: LanguageClientOptions = { 24 | // Register the server for java documents 25 | documentSelector: ['kotlin'], 26 | synchronize: { 27 | // Synchronize the setting section 'kotlin' to the server 28 | // NOTE: this currently doesn't do anything 29 | configurationSection: 'kotlin', 30 | // Notify the server about file changes to 'javaconfig.json' files contain in the workspace 31 | // TODO this should be registered from the language server side 32 | fileEvents: [ 33 | VSCode.workspace.createFileSystemWatcher('**/*.kt'), 34 | VSCode.workspace.createFileSystemWatcher('**/*.kts'), 35 | VSCode.workspace.createFileSystemWatcher('**/pom.xml'), 36 | ] 37 | }, 38 | outputChannelName: 'Kotlin', 39 | revealOutputChannelOn: 4 // never 40 | } 41 | let fatJar = Path.resolve(context.extensionPath, "target", "KotlinLanguageServer.jar"); 42 | let args = [ 43 | '-Xverify:none', // helps VisualVM avoid 'error 62' 44 | '-jar', fatJar 45 | ]; 46 | // Start the child java process 47 | let serverOptions: ServerOptions = { 48 | command: javaExecutablePath, 49 | args: args, 50 | options: { cwd: VSCode.workspace.rootPath } 51 | } 52 | 53 | console.log(javaExecutablePath + ' ' + args.join(' ')); 54 | 55 | // Create the language client and start the client. 56 | let languageClient = new LanguageClient('kotlin', 'Kotlin Language Server', serverOptions, clientOptions); 57 | let disposable = languageClient.start(); 58 | 59 | // Push the disposable to the context's subscriptions so that the 60 | // client can be deactivated on extension deactivation 61 | context.subscriptions.push(disposable); 62 | } 63 | 64 | // this method is called when your extension is deactivated 65 | export function deactivate() { 66 | } 67 | 68 | function findJavaExecutable(binname: string) { 69 | binname = correctBinname(binname); 70 | 71 | // First search java.home setting 72 | let userJavaHome = VSCode.workspace.getConfiguration('java').get('home') as string; 73 | 74 | if (userJavaHome != null) { 75 | console.log('Looking for java in settings java.home ' + userJavaHome + '...'); 76 | 77 | let candidate = findJavaExecutableInJavaHome(userJavaHome, binname); 78 | 79 | if (candidate != null) 80 | return candidate; 81 | } 82 | 83 | // Then search each JAVA_HOME 84 | let envJavaHome = process.env['JAVA_HOME']; 85 | 86 | if (envJavaHome) { 87 | console.log('Looking for java in environment variable JAVA_HOME ' + envJavaHome + '...'); 88 | 89 | let candidate = findJavaExecutableInJavaHome(envJavaHome, binname); 90 | 91 | if (candidate != null) 92 | return candidate; 93 | } 94 | 95 | // Then search PATH parts 96 | if (process.env['PATH']) { 97 | console.log('Looking for java in PATH'); 98 | 99 | let pathparts = process.env['PATH'].split(Path.delimiter); 100 | for (let i = 0; i < pathparts.length; i++) { 101 | let binpath = Path.join(pathparts[i], binname); 102 | if (FS.existsSync(binpath)) { 103 | return binpath; 104 | } 105 | } 106 | } 107 | 108 | // Else return the binary name directly (this will likely always fail downstream) 109 | return null; 110 | } 111 | 112 | function correctBinname(binname: string) { 113 | if (process.platform === 'win32') 114 | return binname + '.exe'; 115 | else 116 | return binname; 117 | } 118 | 119 | function findJavaExecutableInJavaHome(javaHome: string, binname: string) { 120 | let workspaces = javaHome.split(Path.delimiter); 121 | 122 | for (let i = 0; i < workspaces.length; i++) { 123 | let binpath = Path.join(workspaces[i], 'bin', binname); 124 | 125 | if (FS.existsSync(binpath)) 126 | return binpath; 127 | } 128 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/SourceFiles.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import com.intellij.openapi.util.text.StringUtil.convertLineSeparators 4 | import org.eclipse.lsp4j.TextDocumentContentChangeEvent 5 | import java.io.BufferedReader 6 | import java.io.StringReader 7 | import java.io.StringWriter 8 | import java.nio.file.FileSystems 9 | import java.nio.file.Files 10 | import java.nio.file.Path 11 | import java.util.stream.Collectors 12 | 13 | private class SourceVersion(val content: String, val version: Int) 14 | 15 | /** 16 | * Notify SourcePath whenever a file changes 17 | */ 18 | private class NotifySourcePath(private val sp: SourcePath) { 19 | private val files = mutableMapOf() 20 | 21 | operator fun get(file: Path): SourceVersion? = files[file] 22 | 23 | operator fun set(file: Path, source: SourceVersion) { 24 | val content = convertLineSeparators(source.content) 25 | 26 | files[file] = source 27 | sp.put(file, content) 28 | } 29 | 30 | fun remove(file: Path) { 31 | files.remove(file) 32 | sp.delete(file) 33 | } 34 | 35 | fun removeAll(rm: Collection) { 36 | files -= rm 37 | 38 | rm.forEach(sp::delete) 39 | } 40 | 41 | val keys get() = files.keys 42 | } 43 | 44 | /** 45 | * Keep track of the text of all files in the workspace 46 | */ 47 | class SourceFiles(private val sp: SourcePath) { 48 | private val workspaceRoots = mutableSetOf() 49 | private val files = NotifySourcePath(sp) 50 | private val open = mutableSetOf() 51 | 52 | fun open(file: Path, content: String, version: Int) { 53 | files[file] = SourceVersion(content, version) 54 | open.add(file) 55 | } 56 | 57 | fun close(file: Path) { 58 | open.remove(file) 59 | 60 | val disk = readFromDisk(file) 61 | 62 | if (disk != null) 63 | files[file] = disk 64 | else 65 | files.remove(file) 66 | } 67 | 68 | fun edit(file: Path, newVersion: Int, contentChanges: List) { 69 | val existing = files[file]!! 70 | var newText = existing.content 71 | 72 | if (newVersion <= existing.version) { 73 | LOG.warning("Ignored ${file.fileName} version $newVersion") 74 | return 75 | } 76 | 77 | for (change in contentChanges) { 78 | if (change.range == null) newText = change.text 79 | else newText = patch(newText, change) 80 | } 81 | 82 | files[file] = SourceVersion(newText, newVersion) 83 | } 84 | 85 | fun createdOnDisk(file: Path) { 86 | changedOnDisk(file) 87 | } 88 | 89 | fun deletedOnDisk(file: Path) { 90 | if (isSource(file)) 91 | files.remove(file) 92 | } 93 | 94 | fun changedOnDisk(file: Path) { 95 | if (isSource(file)) 96 | files[file] = readFromDisk(file)!! 97 | } 98 | 99 | private fun readFromDisk(file: Path): SourceVersion? { 100 | if (!Files.exists(file)) return null 101 | 102 | val content = Files.readAllLines(file).joinToString("\n") 103 | 104 | return SourceVersion(content, -1) 105 | } 106 | 107 | private fun isSource(file: Path): Boolean { 108 | val name = file.fileName.toString() 109 | 110 | return name.endsWith(".kt") || name.endsWith(".kts") 111 | } 112 | 113 | fun addWorkspaceRoot(root: Path) { 114 | val addSources = findSourceFiles(root) 115 | 116 | logAdded(addSources, root) 117 | 118 | for (file in addSources) { 119 | files[file] = readFromDisk(file)!! 120 | } 121 | 122 | workspaceRoots.add(root) 123 | } 124 | 125 | fun removeWorkspaceRoot(root: Path) { 126 | val rmSources = files.keys.filter { it.startsWith(root) } 127 | 128 | logRemoved(rmSources, root) 129 | 130 | files.removeAll(rmSources) 131 | workspaceRoots.remove(root) 132 | } 133 | 134 | fun isOpen(file: Path): Boolean = open.contains(file) 135 | } 136 | 137 | private fun patch(sourceText: String, change: TextDocumentContentChangeEvent): String { 138 | val range = change.range 139 | val reader = BufferedReader(StringReader(sourceText)) 140 | val writer = StringWriter() 141 | 142 | // Skip unchanged lines 143 | var line = 0 144 | 145 | while (line < range.start.line) { 146 | writer.write(reader.readLine() + '\n') 147 | line++ 148 | } 149 | 150 | // Skip unchanged chars 151 | for (character in 0 until range.start.character) 152 | writer.write(reader.read()) 153 | 154 | // Write replacement text 155 | writer.write(change.text) 156 | 157 | // Skip replaced text 158 | reader.skip(change.rangeLength!!.toLong()) 159 | 160 | // Write remaining text 161 | while (true) { 162 | val next = reader.read() 163 | 164 | if (next == -1) return writer.toString() 165 | else writer.write(next) 166 | } 167 | } 168 | 169 | private fun findSourceFiles(root: Path): Set { 170 | val pattern = FileSystems.getDefault().getPathMatcher("glob:*.{kt,kts}") 171 | return Files.walk(root).filter { pattern.matches(it.fileName) } .collect(Collectors.toSet()) 172 | } 173 | 174 | private fun logAdded(sources: Collection, rootPath: Path?) { 175 | LOG.info("Adding ${describeFiles(sources)} under $rootPath to source path") 176 | } 177 | 178 | private fun logRemoved(sources: Collection, rootPath: Path?) { 179 | LOG.info("Removing ${describeFiles(sources)} under $rootPath to source path") 180 | } 181 | 182 | fun describeFiles(files: Collection): String { 183 | return if (files.isEmpty()) "0 files" 184 | else if (files.size > 5) "${files.size} files" 185 | else files.map { it.fileName }.joinToString(", ") 186 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.javacs 5 | kotlin-language-server 6 | jar 7 | 1.0-SNAPSHOT 8 | kotlin-language-server 9 | http://maven.apache.org 10 | 11 | 1.2.31 12 | 4.10 13 | 1.20 14 | org.javacs.kt.MainKt 15 | 16 | 17 | 18 | ${project.basedir}/src/main/kotlin 19 | ${project.basedir}/src/test/kotlin 20 | 21 | 22 | 23 | kotlin-maven-plugin 24 | org.jetbrains.kotlin 25 | ${kotlin.version} 26 | 27 | 1.8 28 | enable 29 | 30 | 31 | 32 | compile 33 | compile 34 | 35 | 36 | ${project.basedir}/src/main/kotlin 37 | ${project.basedir}/src/main/java 38 | 39 | 40 | 41 | 42 | test-compile 43 | test-compile 44 | 45 | 46 | ${project.basedir}/src/test/kotlin 47 | ${project.basedir}/src/test/java 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-compiler-plugin 57 | 3.5.1 58 | 59 | 1.8 60 | 1.8 61 | 62 | 63 | 64 | 65 | default-compile 66 | none 67 | 68 | 69 | 70 | default-testCompile 71 | none 72 | 73 | 74 | java-compile 75 | compile 76 | compile 77 | 78 | 79 | java-test-compile 80 | test-compile 81 | testCompile 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-assembly-plugin 89 | 2.6 90 | 91 | 92 | make-assembly 93 | package 94 | single 95 | 96 | 97 | 98 | ${main.class} 99 | 100 | 101 | 102 | jar-with-dependencies 103 | 104 | KotlinLanguageServer 105 | false 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.eclipse.lsp4j 116 | org.eclipse.lsp4j 117 | 0.4.0.M6 118 | 119 | 120 | 121 | org.jetbrains.kotlin 122 | kotlin-compiler 123 | ${kotlin.version} 124 | 125 | 126 | org.jetbrains.kotlin 127 | kotlin-stdlib 128 | ${kotlin.version} 129 | 130 | 131 | 132 | org.openjdk.jmh 133 | jmh-core 134 | ${jmh.version} 135 | test 136 | 137 | 138 | org.openjdk.jmh 139 | jmh-generator-annprocess 140 | ${jmh.version} 141 | provided 142 | 143 | 144 | 145 | org.hamcrest 146 | hamcrest-all 147 | 1.3 148 | test 149 | 150 | 151 | junit 152 | junit 153 | ${junit.version} 154 | test 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/completion/RenderCompletionItem.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.completion 2 | 3 | import org.eclipse.lsp4j.CompletionItem 4 | import org.eclipse.lsp4j.CompletionItemKind.* 5 | import org.eclipse.lsp4j.CompletionItemKind.Function 6 | import org.eclipse.lsp4j.InsertTextFormat.PlainText 7 | import org.eclipse.lsp4j.InsertTextFormat.Snippet 8 | import org.jetbrains.kotlin.descriptors.* 9 | import org.jetbrains.kotlin.renderer.ClassifierNamePolicy 10 | import org.jetbrains.kotlin.renderer.DescriptorRenderer 11 | import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy 12 | import org.jetbrains.kotlin.types.ErrorUtils 13 | import org.jetbrains.kotlin.types.UnresolvedType 14 | 15 | val DECL_RENDERER = DescriptorRenderer.withOptions { 16 | withDefinedIn = false 17 | modifiers = emptySet() 18 | classifierNamePolicy = ClassifierNamePolicy.SHORT 19 | parameterNameRenderingPolicy = ParameterNameRenderingPolicy.ONLY_NON_SYNTHESIZED 20 | typeNormalizer = { 21 | when (it) { 22 | is UnresolvedType -> ErrorUtils.createErrorTypeWithCustomDebugName(it.presentableName) 23 | else -> it 24 | } 25 | } 26 | } 27 | 28 | private val GOOD_IDENTIFIER = Regex("[a-zA-Z]\\w*") 29 | 30 | class RenderCompletionItem : DeclarationDescriptorVisitor { 31 | 32 | private val result = CompletionItem() 33 | 34 | private fun escape(id: String): String = 35 | if (id.matches(GOOD_IDENTIFIER)) id 36 | else "`$id`" 37 | 38 | private fun setDefaults(declaration: DeclarationDescriptor) { 39 | result.label = declaration.label() 40 | result.filterText = declaration.label() 41 | result.insertText = escape(declaration.label()!!) 42 | result.insertTextFormat = PlainText 43 | result.detail = DECL_RENDERER.render(declaration) 44 | } 45 | 46 | override fun visitPropertySetterDescriptor(desc: PropertySetterDescriptor, nothing: Unit?): CompletionItem { 47 | setDefaults(desc) 48 | 49 | result.kind = Property 50 | 51 | return result 52 | } 53 | 54 | override fun visitConstructorDescriptor(desc: ConstructorDescriptor, nothing: Unit?): CompletionItem { 55 | setDefaults(desc) 56 | 57 | result.kind = Constructor 58 | result.insertText = functionInsertText(desc) 59 | result.insertTextFormat = Snippet 60 | 61 | return result 62 | } 63 | 64 | override fun visitReceiverParameterDescriptor(desc: ReceiverParameterDescriptor, nothing: Unit?): CompletionItem { 65 | setDefaults(desc) 66 | 67 | result.kind = Variable 68 | 69 | return result 70 | } 71 | 72 | override fun visitPackageViewDescriptor(desc: PackageViewDescriptor, nothing: Unit?): CompletionItem { 73 | setDefaults(desc) 74 | 75 | result.kind = Module 76 | 77 | return result 78 | } 79 | 80 | override fun visitFunctionDescriptor(desc: FunctionDescriptor, nothing: Unit?): CompletionItem { 81 | setDefaults(desc) 82 | 83 | result.kind = Function 84 | result.insertText = functionInsertText(desc) 85 | result.insertTextFormat = Snippet 86 | 87 | return result 88 | } 89 | 90 | private fun functionInsertText(desc: FunctionDescriptor): String { 91 | val name = escape(desc.label()!!) 92 | 93 | return if (desc.valueParameters.isEmpty()) 94 | "$name()" 95 | else 96 | "$name(\$0)" 97 | } 98 | 99 | override fun visitModuleDeclaration(desc: ModuleDescriptor, nothing: Unit?): CompletionItem { 100 | setDefaults(desc) 101 | 102 | result.kind = Module 103 | 104 | return result 105 | } 106 | 107 | override fun visitClassDescriptor(desc: ClassDescriptor, nothing: Unit?): CompletionItem { 108 | setDefaults(desc) 109 | 110 | result.kind = Class 111 | 112 | return result 113 | } 114 | 115 | override fun visitPackageFragmentDescriptor(desc: PackageFragmentDescriptor, nothing: Unit?): CompletionItem { 116 | setDefaults(desc) 117 | 118 | result.kind = Module 119 | 120 | return result 121 | } 122 | 123 | override fun visitValueParameterDescriptor(desc: ValueParameterDescriptor, nothing: Unit?): CompletionItem { 124 | setDefaults(desc) 125 | 126 | result.kind = Variable 127 | 128 | return result 129 | } 130 | 131 | override fun visitTypeParameterDescriptor(desc: TypeParameterDescriptor, nothing: Unit?): CompletionItem { 132 | setDefaults(desc) 133 | 134 | result.kind = Variable 135 | 136 | return result 137 | } 138 | 139 | override fun visitScriptDescriptor(desc: ScriptDescriptor, nothing: Unit?): CompletionItem { 140 | setDefaults(desc) 141 | 142 | result.kind = Module 143 | 144 | return result 145 | } 146 | 147 | override fun visitTypeAliasDescriptor(desc: TypeAliasDescriptor, nothing: Unit?): CompletionItem { 148 | setDefaults(desc) 149 | 150 | result.kind = Variable 151 | 152 | return result 153 | } 154 | 155 | override fun visitPropertyGetterDescriptor(desc: PropertyGetterDescriptor, nothing: Unit?): CompletionItem { 156 | setDefaults(desc) 157 | 158 | result.kind = Property 159 | 160 | return result 161 | } 162 | 163 | override fun visitVariableDescriptor(desc: VariableDescriptor, nothing: Unit?): CompletionItem { 164 | setDefaults(desc) 165 | 166 | result.kind = Variable 167 | 168 | return result 169 | } 170 | 171 | override fun visitPropertyDescriptor(desc: PropertyDescriptor, nothing: Unit?): CompletionItem { 172 | setDefaults(desc) 173 | 174 | result.kind = Property 175 | 176 | return result 177 | } 178 | } 179 | 180 | private fun DeclarationDescriptor.label(): String? { 181 | return when { 182 | this is ConstructorDescriptor -> this.containingDeclaration.name.identifier 183 | this.name.isSpecial -> null 184 | else -> this.name.identifier 185 | } 186 | } -------------------------------------------------------------------------------- /grammar/expressions.grm: -------------------------------------------------------------------------------- 1 | /** 2 | ## Expressions 3 | 4 | 5 | 6 | ### Precedence 7 | 8 | | Precedence | Title | Symbols | 9 | |------------|-------|---------| 10 | | Highest | Postfix | `++`, `--`, `.`, `?.`, `?` | 11 | | | Prefix | `-`, `+`, `++`, `--`, `!`, [`labelDefinition`](#labelDefinition) | 12 | | | Type RHS | `:`, `as`, `as?` | 13 | | | Multiplicative | `*`, `/`, `%` | 14 | | | Additive | `+`, `-` | 15 | | | Range | `..` | 16 | | | Infix function | [`SimpleName`](#SimpleName) | 17 | | | Elvis | `?:` | 18 | | | Named checks | `in`, `!in`, `is`, `!is` | 19 | | | Comparison | `<`, `>`, `<=`, `>=` | 20 | | | Equality | `==`, `\!==` | 21 | | | Conjunction | `&&` | 22 | | | Disjunction | `||` | 23 | | Lowest | Assignment | `=`, `+=`, `-=`, `*=`, `/=`, `%=` | 24 | 25 | ### Rules 26 | 27 | */ 28 | 29 | /* 30 | Decreasing precedence: 31 | memberAccessOperation 32 | postfixUnaryOperation 33 | prefixUnaryOperation 34 | multiplicativeOperation 35 | additiveOperation 36 | ".." 37 | SimpleName 38 | "?:" 39 | namedInfixOrTypeOperation 40 | comparisonOperation 41 | equalityOperation 42 | "&&" 43 | "||" 44 | assignmentOperator 45 | */ 46 | 47 | expression 48 | : disjunction (assignmentOperator disjunction)* 49 | ; 50 | 51 | disjunction 52 | : conjunction ("||" conjunction)* 53 | ; 54 | 55 | conjunction 56 | : equalityComparison ("&&" equalityComparison)* 57 | ; 58 | 59 | equalityComparison 60 | : comparison (equalityOperation comparison)* 61 | ; 62 | 63 | comparison 64 | : namedInfix (comparisonOperation namedInfix)* 65 | ; 66 | 67 | namedInfix 68 | : elvisExpression (inOperation elvisExpression)* 69 | : elvisExpression (isOperation type)? 70 | ; 71 | 72 | elvisExpression 73 | : infixFunctionCall ("?:" infixFunctionCall)* 74 | ; 75 | 76 | infixFunctionCall 77 | : rangeExpression (SimpleName rangeExpression)* 78 | ; 79 | 80 | rangeExpression 81 | : additiveExpression (".." additiveExpression)* 82 | ; 83 | 84 | additiveExpression 85 | : multiplicativeExpression (additiveOperation multiplicativeExpression)* 86 | ; 87 | 88 | multiplicativeExpression 89 | : typeRHS (multiplicativeOperation typeRHS)* 90 | ; 91 | 92 | typeRHS 93 | : prefixUnaryExpression (typeOperation prefixUnaryExpression)* 94 | ; 95 | 96 | prefixUnaryExpression 97 | : prefixUnaryOperation* postfixUnaryExpression 98 | ; 99 | 100 | postfixUnaryExpression 101 | : atomicExpression postfixUnaryOperation* 102 | : callableReference postfixUnaryOperation* 103 | ; 104 | 105 | // TODO: update this rule to include class literals and bound callable references 106 | callableReference 107 | : (userType "?"*)? "::" SimpleName typeArguments? 108 | ; 109 | 110 | // !!! When you add here, remember to update the FIRST set in the parser 111 | atomicExpression 112 | : "(" expression ")" 113 | : literalConstant 114 | : functionLiteral 115 | : "this" labelReference? 116 | : "super" ("<" type ">")? labelReference? 117 | : if 118 | : when 119 | : try 120 | : objectLiteral 121 | : jump 122 | : loop 123 | : collectionLiteral 124 | : SimpleName 125 | ; 126 | 127 | labelReference 128 | : "@" ++ LabelName 129 | ; 130 | 131 | labelDefinition 132 | : LabelName ++ "@" 133 | ; 134 | 135 | literalConstant 136 | : "true" | "false" 137 | : stringTemplate 138 | : NoEscapeString 139 | : IntegerLiteral 140 | : CharacterLiteral 141 | : FloatLiteral 142 | : "null" 143 | ; 144 | 145 | stringTemplate 146 | : "\"" stringTemplateElement* "\"" 147 | ; 148 | 149 | stringTemplateElement 150 | : RegularStringPart 151 | : ShortTemplateEntryStart (SimpleName | "this") 152 | : EscapeSequence 153 | : longTemplate 154 | ; 155 | 156 | longTemplate 157 | : "${" expression "}" 158 | ; 159 | 160 | declaration 161 | : function 162 | : property 163 | : class 164 | : typeAlias 165 | : object 166 | ; 167 | 168 | statement 169 | : declaration 170 | : blockLevelExpression 171 | ; 172 | 173 | blockLevelExpression 174 | : annotations ("\n")+ expression 175 | ; 176 | 177 | multiplicativeOperation 178 | : "*" : "/" : "%" 179 | ; 180 | 181 | additiveOperation 182 | : "+" : "-" 183 | ; 184 | 185 | inOperation 186 | : "in" : "!in" 187 | ; 188 | 189 | typeOperation 190 | : "as" : "as?" : ":" 191 | ; 192 | 193 | isOperation 194 | : "is" : "!is" 195 | ; 196 | 197 | comparisonOperation 198 | : "<" : ">" : ">=" : "<=" 199 | ; 200 | 201 | equalityOperation 202 | : "!=" : "==" 203 | ; 204 | 205 | assignmentOperator 206 | : "=" 207 | : "+=" : "-=" : "*=" : "/=" : "%=" 208 | ; 209 | 210 | prefixUnaryOperation 211 | : "-" : "+" 212 | : "++" : "--" 213 | : "!" 214 | : annotations 215 | : labelDefinition 216 | ; 217 | 218 | postfixUnaryOperation 219 | : "++" : "--" : "!!" 220 | : callSuffix 221 | : arrayAccess 222 | : memberAccessOperation postfixUnaryExpression // TODO: Review 223 | ; 224 | 225 | callSuffix 226 | : typeArguments? valueArguments annotatedLambda 227 | : typeArguments annotatedLambda 228 | ; 229 | 230 | annotatedLambda 231 | : ("@" unescapedAnnotation)* labelDefinition? functionLiteral 232 | ; 233 | 234 | memberAccessOperation 235 | : "." : "?." : "?" 236 | ; 237 | 238 | typeArguments 239 | : "<" type{","} ">" 240 | ; 241 | 242 | valueArguments 243 | : "(" ((SimpleName "=")? "*"? expression){","} ")" 244 | ; 245 | 246 | jump 247 | : "throw" expression 248 | : "return" ++ labelReference? expression? 249 | : "continue" ++ labelReference? 250 | : "break" ++ labelReference? 251 | // yield ? 252 | ; 253 | 254 | functionLiteral 255 | : "{" statements "}" 256 | : "{" lambdaParameter{","} "->" statements "}" 257 | ; 258 | 259 | lambdaParameter 260 | : variableDeclarationEntry 261 | : multipleVariableDeclarations (":" type)? 262 | ; 263 | 264 | statements 265 | : SEMI* statement{SEMI+} SEMI* 266 | ; 267 | 268 | constructorInvocation 269 | : userType callSuffix 270 | ; 271 | 272 | arrayAccess 273 | : "[" expression{","} "]" 274 | ; 275 | 276 | objectLiteral 277 | : "object" (":" delegationSpecifier{","})? classBody 278 | ; 279 | 280 | collectionLiteral 281 | : "[" element{","}? "]" 282 | ; 283 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/classpath/findClassPath.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.classpath 2 | 3 | import java.util.logging.Level 4 | import org.javacs.kt.LOG 5 | import org.jetbrains.kotlin.utils.ifEmpty 6 | import java.io.File 7 | import java.nio.file.Files 8 | import java.nio.file.Path 9 | import java.nio.file.Paths 10 | import java.nio.file.attribute.BasicFileAttributes 11 | import java.util.stream.Collectors 12 | import java.util.function.BiPredicate 13 | import java.util.Comparator 14 | 15 | fun findClassPath(workspaceRoots: Collection): Set = 16 | workspaceRoots 17 | .flatMap { pomFiles(it) } 18 | .flatMap { readPom(it) } 19 | .toSet() 20 | .ifEmpty { backupClassPath() } 21 | 22 | private fun backupClassPath() = 23 | listOfNotNull(findKotlinStdlib()).toSet() 24 | 25 | private fun pomFiles(workspaceRoot: Path): Set = 26 | Files.walk(workspaceRoot) 27 | .filter { it.endsWith("pom.xml") } 28 | .collect(Collectors.toSet()) 29 | 30 | private fun readPom(pom: Path): Set { 31 | val mavenOutput = genDependencyList(pom) 32 | val artifacts = readDependencyList(mavenOutput) 33 | 34 | when { 35 | artifacts.isEmpty() -> LOG.warning("No artifacts found in $pom") 36 | artifacts.size < 5 -> LOG.info("Found ${artifacts.joinToString(", ")} in $pom") 37 | else -> LOG.info("Found ${artifacts.size} artifacts in $pom") 38 | } 39 | 40 | return artifacts.mapNotNull({ findArtifact(it, false) }).toSet() 41 | } 42 | 43 | private fun genDependencyList(pom: Path): Path { 44 | val mavenOutput = Files.createTempFile("deps", ".txt") 45 | val workingDirectory = pom.toAbsolutePath().parent.toFile() 46 | val cmd = "${mvnCommand()} dependency:list -DincludeScope=test -DoutputFile=$mavenOutput" 47 | LOG.info("Run ${cmd} in $workingDirectory") 48 | val status = Runtime.getRuntime().exec(cmd, null, workingDirectory).waitFor() 49 | 50 | assert(status == 0, { "$cmd failed" }) 51 | 52 | return mavenOutput 53 | } 54 | 55 | private val artifact = Regex(".*:.*:.*:.*:.*") 56 | 57 | private fun readDependencyList(mavenOutput: Path): Set = 58 | mavenOutput.toFile() 59 | .readLines() 60 | .filter { it.matches(artifact) } 61 | .map(::parseArtifact) 62 | .toSet() 63 | 64 | private fun parseArtifact(string: String): Artifact { 65 | val parts = string.trim().split(':') 66 | 67 | return when (parts.size) { 68 | 3 -> Artifact(parts[0], parts[1], parts[2]) 69 | 5 -> Artifact(parts[0], parts[1], parts[3]) 70 | else -> throw IllegalArgumentException("$string is not a properly formed maven artifact") 71 | } 72 | } 73 | 74 | private data class Artifact(val group: String, val artifact: String, val version: String) { 75 | override fun toString() = "$group:$artifact:$version" 76 | } 77 | 78 | private val userHome = Paths.get(System.getProperty("user.home")) 79 | private val mavenHome = userHome.resolve(".m2") 80 | 81 | fun findKotlinStdlib(): Path? { 82 | val group = "org.jetbrains.kotlin" 83 | val artifact = "kotlin-stdlib" 84 | val artifactDir = mavenHome.resolve("repository") 85 | .resolve(group.replace('.', File.separatorChar)) 86 | .resolve(artifact) 87 | val isKotlinStdlib = BiPredicate { file, attr -> 88 | val name = file.fileName.toString() 89 | val version = file.parent.fileName.toString() 90 | val expected = "kotlin-stdlib-${version}.jar" 91 | name == expected 92 | } 93 | val found = Files.find(artifactDir, 2, isKotlinStdlib) 94 | .sorted(::compareVersions).findFirst().orElse(null) 95 | if (found == null) LOG.warning("Couldn't find kotlin stdlib in ${artifactDir}") 96 | else LOG.info("Found kotlin stdlib ${found}") 97 | return found 98 | } 99 | 100 | private fun compareVersions(left: Path, right: Path): Int { 101 | val leftVersion = extractVersion(left) 102 | val rightVersion = extractVersion(right) 103 | 104 | for (i in 0 until Math.min(leftVersion.size, rightVersion.size)) { 105 | val leftRev = leftVersion[i].reversed() 106 | val rightRev = rightVersion[i].reversed() 107 | val compare = leftRev.compareTo(rightRev) 108 | if (compare != 0) 109 | return -compare 110 | } 111 | 112 | return -leftVersion.size.compareTo(rightVersion.size) 113 | } 114 | 115 | private fun extractVersion(artifact: Path): List { 116 | return artifact.parent.toString().split(".") 117 | } 118 | 119 | private fun findArtifact(a: Artifact, source: Boolean): Path? { 120 | val result = mavenHome.resolve("repository") 121 | .resolve(a.group.replace('.', File.separatorChar)) 122 | .resolve(a.artifact) 123 | .resolve(a.version) 124 | .resolve(mavenJarName(a, source)) 125 | 126 | if (Files.exists(result)) 127 | return result 128 | else { 129 | LOG.warning("Couldn't find $a in $result") 130 | 131 | return null 132 | } 133 | } 134 | 135 | private fun mavenJarName(a: Artifact, source: Boolean) = 136 | if (source) "${a.artifact}-${a.version}-sources.jar" 137 | else "${a.artifact}-${a.version}.jar" 138 | 139 | private var cacheMvnCommand: Path? = null 140 | 141 | private fun mvnCommand(): Path { 142 | if (cacheMvnCommand == null) 143 | cacheMvnCommand = doMvnCommand() 144 | 145 | return cacheMvnCommand!! 146 | } 147 | 148 | private fun doMvnCommand() = 149 | if (File.separatorChar == '\\') windowsMvnCommand() 150 | else unixMvnCommand() 151 | 152 | private fun windowsMvnCommand() = 153 | findExecutableOnPath("mvn.cmd") ?: findExecutableOnPath("mvn.bat") 154 | 155 | private fun unixMvnCommand() = 156 | findExecutableOnPath("mvn") 157 | 158 | private fun findExecutableOnPath(fileName: String): Path? { 159 | for (dir in System.getenv("PATH").split(File.pathSeparator)) { 160 | val file = File(dir, fileName) 161 | 162 | if (file.isFile && file.canExecute()) { 163 | LOG.info("Found $fileName at ${file.absolutePath}") 164 | 165 | return Paths.get(file.absolutePath) 166 | } 167 | } 168 | 169 | return null 170 | } 171 | -------------------------------------------------------------------------------- /src/test/java/org/javacs/kt/OneFilePerformance.java: -------------------------------------------------------------------------------- 1 | package org.javacs.kt; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.vfs.StandardFileSystems; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.intellij.openapi.vfs.VirtualFileManager; 7 | import com.intellij.openapi.vfs.VirtualFileSystem; 8 | import com.intellij.psi.PsiManager; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport; 11 | import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; 12 | import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; 13 | import org.jetbrains.kotlin.config.CommonConfigurationKeys; 14 | import org.jetbrains.kotlin.config.CompilerConfiguration; 15 | import org.jetbrains.kotlin.container.ComponentProvider; 16 | import org.jetbrains.kotlin.descriptors.CallableDescriptor; 17 | import org.jetbrains.kotlin.load.java.JvmAbi; 18 | import org.jetbrains.kotlin.psi.KtElement; 19 | import org.jetbrains.kotlin.psi.KtFile; 20 | import org.jetbrains.kotlin.psi.KtPsiFactory; 21 | import org.jetbrains.kotlin.resolve.BindingTraceContext; 22 | import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer; 23 | import org.jetbrains.kotlin.resolve.TopDownAnalysisMode; 24 | import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; 25 | import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; 26 | import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory; 27 | import org.junit.Test; 28 | import org.openjdk.jmh.annotations.Benchmark; 29 | import org.openjdk.jmh.annotations.Scope; 30 | import org.openjdk.jmh.annotations.State; 31 | import org.openjdk.jmh.runner.Runner; 32 | import org.openjdk.jmh.runner.RunnerException; 33 | import org.openjdk.jmh.runner.options.Options; 34 | import org.openjdk.jmh.runner.options.OptionsBuilder; 35 | 36 | import java.net.URL; 37 | import java.util.Arrays; 38 | import java.util.Collection; 39 | import java.util.Collections; 40 | 41 | public class OneFilePerformance { 42 | @State(Scope.Thread) 43 | public static class ReusableParts { 44 | CompilerConfiguration config = new CompilerConfiguration(); 45 | { 46 | config.put(CommonConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME); 47 | } 48 | KotlinCoreEnvironment env = KotlinCoreEnvironment.createForProduction(new Disposable() { 49 | @Override 50 | public void dispose() { 51 | // Do nothing 52 | } 53 | }, config, EnvironmentConfigFiles.JVM_CONFIG_FILES); 54 | KtPsiFactory parser = new KtPsiFactory(env.getProject()); 55 | VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL); 56 | KtFile bigFile = openFile("/kotlinCompilerPerformance/BigFile.kt"); 57 | 58 | KtFile openFile(String resourcePath) { 59 | URL locate = OneFilePerformance.class.getResource(resourcePath); 60 | VirtualFile file = fileSystem.findFileByPath(locate.getPath()); 61 | return (KtFile) PsiManager.getInstance(env.getProject()).findFile(file); 62 | } 63 | 64 | BindingTraceContext compile(Collection compile, Collection sourcePath) { 65 | BindingTraceContext trace = new CliLightClassGenerationSupport.CliBindingTrace(); 66 | ComponentProvider container = CompilerFixtures.createContainer( 67 | env.getProject(), 68 | sourcePath, 69 | trace, 70 | env.getConfiguration(), 71 | env::createPackagePartProvider); 72 | LazyTopDownAnalyzer analyze = container.create(LazyTopDownAnalyzer.class); 73 | 74 | analyze.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, compile, DataFlowInfoFactory.EMPTY); 75 | 76 | return trace; 77 | } 78 | } 79 | 80 | @Benchmark 81 | public void recompileBigFile(ReusableParts state) { 82 | state.compile(Collections.singletonList(state.bigFile), Collections.singletonList(state.bigFile)); 83 | } 84 | 85 | @Benchmark 86 | public void recompileSmallFile(ReusableParts state) { 87 | KtFile smallFile = state.openFile("/kotlinCompilerPerformance/ReferencesBigFile.kt"); 88 | BindingTraceContext analyze = state.compile(Arrays.asList(smallFile), Arrays.asList(smallFile, state.bigFile)); 89 | 90 | // Check reference 91 | KtElement ref = PsiTreeUtil.getNonStrictParentOfType(smallFile.findElementAt(80), KtElement.class); 92 | ResolvedCall call = CallUtilKt.getParentResolvedCall(ref, analyze.getBindingContext(), false); 93 | if (!call.getCandidateDescriptor().getName().asString().equals("max")) 94 | throw new RuntimeException("Expected BigFile.max but found " + call); 95 | } 96 | 97 | @Benchmark 98 | public void recompileBoth(ReusableParts state) { 99 | KtFile bigFile = state.openFile("/kotlinCompilerPerformance/BigFile.kt"); 100 | KtFile smallFile = state.openFile("/kotlinCompilerPerformance/ReferencesBigFile.kt"); 101 | BindingTraceContext analyze = state.compile(Arrays.asList(smallFile, bigFile), Arrays.asList(smallFile, bigFile)); 102 | 103 | // Check reference 104 | KtElement ref = PsiTreeUtil.getNonStrictParentOfType(smallFile.findElementAt(80), KtElement.class); 105 | ResolvedCall call = CallUtilKt.getParentResolvedCall(ref, analyze.getBindingContext(), false); 106 | if (!call.getCandidateDescriptor().getName().asString().equals("max")) 107 | throw new RuntimeException("Expected BigFile.max but found " + call); 108 | } 109 | 110 | @Test 111 | public void checkRecompileBoth() { 112 | ReusableParts state = new ReusableParts(); 113 | recompileBoth(state); 114 | } 115 | 116 | @Test 117 | public void checkRecompileOne() { 118 | ReusableParts state = new ReusableParts(); 119 | state.compile(Collections.singleton(state.bigFile), Collections.singleton(state.bigFile)); 120 | recompileSmallFile(state); 121 | } 122 | 123 | public static void main(String[] args) throws RunnerException, InterruptedException { 124 | Options opt = new OptionsBuilder().include(OneFilePerformance.class.getSimpleName()) 125 | .forks(1) 126 | .threads(1) 127 | .warmupIterations(5) 128 | .measurementIterations(5) 129 | .build(); 130 | new Runner(opt).run(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/Compiler.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.vfs.StandardFileSystems 5 | import com.intellij.openapi.vfs.VirtualFileManager 6 | import com.intellij.psi.PsiFileFactory 7 | import org.jetbrains.kotlin.cli.common.script.CliScriptDefinitionProvider 8 | import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport.CliBindingTrace 9 | import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles 10 | import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment 11 | import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM 12 | import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot 13 | import org.jetbrains.kotlin.config.CommonConfigurationKeys 14 | import org.jetbrains.kotlin.config.CompilerConfiguration 15 | import org.jetbrains.kotlin.config.JVMConfigurationKeys 16 | import org.jetbrains.kotlin.container.ComponentProvider 17 | import org.jetbrains.kotlin.container.get 18 | import org.jetbrains.kotlin.idea.KotlinLanguage 19 | import org.jetbrains.kotlin.load.java.JvmAbi 20 | import org.jetbrains.kotlin.psi.* 21 | import org.jetbrains.kotlin.resolve.BindingContext 22 | import org.jetbrains.kotlin.resolve.BindingTraceContext 23 | import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer 24 | import org.jetbrains.kotlin.resolve.TopDownAnalysisMode.TopLevelDeclarations 25 | import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo 26 | import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory 27 | import org.jetbrains.kotlin.resolve.scopes.LexicalScope 28 | import org.jetbrains.kotlin.script.KotlinScriptDefinition 29 | import org.jetbrains.kotlin.script.ScriptDefinitionProvider 30 | import org.jetbrains.kotlin.types.TypeUtils 31 | import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices 32 | import java.nio.file.Path 33 | import java.nio.file.Paths 34 | import java.util.concurrent.locks.ReentrantLock 35 | import kotlin.concurrent.withLock 36 | 37 | /** 38 | * Incrementally compiles files and expressions. 39 | * The basic strategy for compiling one file at-a-time is outlined in OneFilePerformance. 40 | */ 41 | class Compiler(classPath: Set) { 42 | private val config = CompilerConfiguration().apply { 43 | put(CommonConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME) 44 | addAll(JVMConfigurationKeys.CONTENT_ROOTS, classPath.map { JvmClasspathRoot(it.toFile())}) 45 | } 46 | private val env = KotlinCoreEnvironment.createForProduction( 47 | parentDisposable = Disposable { }, 48 | configuration = config, 49 | configFiles = EnvironmentConfigFiles.JVM_CONFIG_FILES) 50 | private val parser = KtPsiFactory(env.project) 51 | private val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL) 52 | private val scripts = ScriptDefinitionProvider.getInstance(env.project) as CliScriptDefinitionProvider 53 | 54 | init { 55 | scripts.setScriptDefinitions(listOf(KotlinScriptDefinition(Any::class))) 56 | } 57 | 58 | fun createFile(content: String, file: Path = Paths.get("dummy.kt")): KtFile { 59 | assert(!content.contains('\r')) 60 | 61 | val factory = PsiFileFactory.getInstance(env.project) 62 | val new = factory.createFileFromText(file.toString(), KotlinLanguage.INSTANCE, content, true, false) as KtFile 63 | 64 | assert(new.virtualFile != null) 65 | 66 | return new 67 | } 68 | 69 | fun createExpression(content: String, file: Path = Paths.get("dummy.kt")): KtExpression { 70 | val property = parseDeclaration("val x = $content", file) as KtProperty 71 | 72 | return property.initializer!! 73 | } 74 | 75 | fun createDeclaration(content: String, file: Path = Paths.get("dummy.kt")): KtDeclaration = 76 | parseDeclaration(content, file) 77 | 78 | private fun parseDeclaration(content: String, file: Path): KtDeclaration { 79 | val parse = createFile(content, file) 80 | val declarations = parse.declarations 81 | 82 | assert(declarations.size == 1) { "${declarations.size} declarations in $content" } 83 | 84 | val onlyDeclaration = declarations.first() 85 | 86 | if (onlyDeclaration is KtScript) { 87 | val scriptDeclarations = onlyDeclaration.declarations 88 | 89 | assert(declarations.size == 1) { "${declarations.size} declarations in script in $content" } 90 | 91 | return scriptDeclarations.first() 92 | } 93 | else return onlyDeclaration 94 | } 95 | 96 | fun createContainer(sourcePath: Collection): Pair { 97 | val trace = CliBindingTrace() 98 | val container = TopDownAnalyzerFacadeForJVM.createContainer( 99 | project = env.project, 100 | files = listOf(), 101 | trace = trace, 102 | configuration = env.configuration, 103 | packagePartProvider = env::createPackagePartProvider, 104 | // TODO FileBasedDeclarationProviderFactory keeps indices, re-use it across calls 105 | declarationProviderFactory = { storageManager, _ -> FileBasedDeclarationProviderFactory(storageManager, sourcePath) }) 106 | return Pair(container, trace) 107 | } 108 | 109 | fun compileFile(file: KtFile, sourcePath: Collection): Pair = 110 | compileFiles(listOf(file), sourcePath) 111 | 112 | // TODO lock at file-level 113 | private val compileLock = ReentrantLock() 114 | 115 | fun compileFiles(files: Collection, sourcePath: Collection): Pair { 116 | compileLock.withLock { 117 | val (container, trace) = createContainer(sourcePath) 118 | val topDownAnalyzer = container.get() 119 | topDownAnalyzer.analyzeDeclarations(TopLevelDeclarations, files) 120 | 121 | return Pair(trace.bindingContext, container) 122 | } 123 | } 124 | 125 | fun compileExpression(expression: KtExpression, scopeWithImports: LexicalScope, sourcePath: Collection): Pair { 126 | LOG.info("Compiling ${expression.text}") 127 | val (container, trace) = createContainer(sourcePath) 128 | val incrementalCompiler = container.get() 129 | incrementalCompiler.getTypeInfo( 130 | scopeWithImports, 131 | expression, 132 | TypeUtils.NO_EXPECTED_TYPE, 133 | DataFlowInfo.EMPTY, 134 | trace, 135 | true) 136 | return Pair(trace.bindingContext, container) 137 | } 138 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/CompiledFile.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import com.intellij.openapi.util.TextRange 4 | import org.javacs.kt.position.changedRegion 5 | import org.javacs.kt.position.position 6 | import org.javacs.kt.util.findParent 7 | import org.javacs.kt.util.nullResult 8 | import org.javacs.kt.util.toPath 9 | import org.jetbrains.kotlin.container.ComponentProvider 10 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 11 | import org.jetbrains.kotlin.psi.* 12 | import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf 13 | import org.jetbrains.kotlin.resolve.BindingContext 14 | import org.jetbrains.kotlin.resolve.scopes.LexicalScope 15 | import org.jetbrains.kotlin.types.KotlinType 16 | 17 | class CompiledFile( 18 | val content: String, 19 | val parse: KtFile, 20 | val compile: BindingContext, 21 | val container: ComponentProvider, 22 | val sourcePath: Collection, 23 | val classPath: CompilerClassPath) { 24 | 25 | /** 26 | * Find the type of the expression at `cursor` 27 | */ 28 | fun typeAtPoint(cursor: Int): KotlinType? { 29 | var cursorExpr = parseAtPoint(cursor)?.findParent() ?: return nullResult("Couldn't find expression at ${describePosition(cursor)}") 30 | val surroundingExpr = expandForType(cursor, cursorExpr) 31 | val scope = scopeAtPoint(cursor) ?: return nullResult("Couldn't find scope at ${describePosition(cursor)}") 32 | val (context, _) = classPath.compiler.compileExpression(surroundingExpr, scope, sourcePath) 33 | return context.getType(surroundingExpr) 34 | } 35 | 36 | private fun expandForType(cursor: Int, surroundingExpr: KtExpression): KtExpression { 37 | val dotParent = surroundingExpr.parent as? KtDotQualifiedExpression 38 | if (dotParent != null && dotParent.selectorExpression?.textRange?.contains(cursor) ?: false) { 39 | return expandForType(cursor, dotParent) 40 | } 41 | else return surroundingExpr 42 | } 43 | 44 | fun referenceAtPoint(cursor: Int): Pair? { 45 | var cursorExpr = parseAtPoint(cursor)?.findParent() ?: return nullResult("Couldn't find expression at ${describePosition(cursor)}") 46 | val surroundingExpr = expandForReference(cursor, cursorExpr) 47 | val scope = scopeAtPoint(cursor) ?: return nullResult("Couldn't find scope at ${describePosition(cursor)}") 48 | val (context, _) = classPath.compiler.compileExpression(surroundingExpr, scope, sourcePath) 49 | val targets = context.getSliceContents(BindingContext.REFERENCE_TARGET) 50 | return targets.asSequence() 51 | .filter { cursor in it.key.textRange } 52 | .sortedBy { it.key.textRange.length } 53 | .map { it.toPair() } 54 | .firstOrNull() 55 | } 56 | 57 | private fun expandForReference(cursor: Int, surroundingExpr: KtExpression): KtExpression { 58 | // foo.bar 59 | val dotParent = surroundingExpr.parent as? KtDotQualifiedExpression 60 | if (dotParent != null) return expandForReference(cursor, dotParent) 61 | // foo() 62 | val callParent = surroundingExpr.parent as? KtCallExpression 63 | if (callParent != null) return expandForReference(cursor, callParent) 64 | 65 | return surroundingExpr 66 | } 67 | 68 | /** 69 | * Parse the expression at `cursor` 70 | */ 71 | fun parseAtPoint(cursor: Int): KtElement? { 72 | val oldCursor = oldOffset(cursor) 73 | val oldChanged = changedRegion(parse.text, content)?.first ?: TextRange(cursor, cursor) 74 | val psi = parse.findElementAt(oldCursor) ?: return nullResult("Couldn't find anything at ${describePosition(cursor)}") 75 | val oldParent = psi.parentsWithSelf 76 | .filterIsInstance() 77 | .firstOrNull { it.textRange.contains(oldChanged) } ?: parse 78 | val recoveryRange = oldParent.textRange 79 | LOG.info("Re-parsing ${describeRange(recoveryRange, true)}") 80 | val surroundingContent = content.substring(recoveryRange.startOffset, content.length - (parse.text.length - recoveryRange.endOffset)) 81 | val padOffset = " ".repeat(recoveryRange.startOffset) 82 | val recompile = classPath.compiler.createFile(padOffset + surroundingContent) 83 | return recompile.findElementAt(cursor)?.findParent() 84 | } 85 | 86 | /** 87 | * Get the typed, compiled element at `cursor`. 88 | * This may be out-of-date if the user is typing quickly. 89 | */ 90 | fun elementAtPoint(cursor: Int): KtElement? { 91 | val oldCursor = oldOffset(cursor) 92 | val psi = parse.findElementAt(oldCursor) ?: return nullResult("Couldn't find anything at ${describePosition(cursor)}") 93 | return psi.findParent() 94 | } 95 | 96 | /** 97 | * Find the lexical-scope surrounding `cursor`. 98 | * This may be out-of-date if the user is typing quickly. 99 | */ 100 | fun scopeAtPoint(cursor: Int): LexicalScope? { 101 | val oldCursor = oldOffset(cursor) 102 | return compile.getSliceContents(BindingContext.LEXICAL_SCOPE).asSequence() 103 | .filter { it.key.textRange.startOffset <= oldCursor && oldCursor <= it.key.textRange.endOffset } 104 | .sortedBy { it.key.textRange.length } 105 | .map { it.value } 106 | .firstOrNull() 107 | } 108 | 109 | fun lineBefore(cursor: Int): String = content.substring(0, cursor).substringAfterLast('\n') 110 | 111 | fun lineAfter(cursor: Int): String = content.substring(cursor).substringBefore('\n') 112 | 113 | private fun oldOffset(cursor: Int): Int { 114 | val (oldChanged, newChanged) = changedRegion(parse.text, content) ?: return cursor 115 | 116 | return when { 117 | cursor <= newChanged.startOffset -> cursor 118 | cursor < newChanged.endOffset -> { 119 | val newRelative = cursor - newChanged.startOffset 120 | val oldRelative = newRelative * oldChanged.length / newChanged.length 121 | oldChanged.startOffset + oldRelative 122 | } 123 | else -> parse.text.length - (content.length - cursor) 124 | } 125 | } 126 | 127 | fun describePosition(offset: Int, oldContent: Boolean = false): String { 128 | val c = if (oldContent) parse.text else content 129 | val pos = position(c, offset) 130 | val file = parse.toPath().fileName 131 | 132 | return "$file ${pos.line + 1}:${pos.character + 1}" 133 | } 134 | 135 | private fun describeRange(range: TextRange, oldContent: Boolean = false): String { 136 | val c = if (oldContent) parse.text else content 137 | val start = position(c, range.startOffset) 138 | val end = position(c, range.endOffset) 139 | val file = parse.toPath().fileName 140 | 141 | return "$file ${start.line}:${start.character + 1}-${end.line + 1}:${end.character + 1}" 142 | } 143 | } 144 | 145 | private fun fileName(file: KtFile): String { 146 | val parts = file.name.split('/') 147 | 148 | return parts.last() 149 | } -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/ReferencesTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.hamcrest.Matchers.* 4 | import org.junit.Assert.assertThat 5 | import org.junit.Test 6 | 7 | class ReferencesTest: SingleFileTestFixture("references", "ReferenceTo.kt") { 8 | @Test fun `find referencs to foo`() { 9 | val request = referenceParams(file, 2, 11) 10 | val references = languageServer.textDocumentService.references(request).get() 11 | 12 | assertThat("Finds references within a file", references, hasItem(hasProperty("uri", containsString("ReferenceTo.kt")))) 13 | assertThat("Finds references across files", references, hasItem(hasProperty("uri", containsString("ReferenceFrom.kt")))) 14 | } 15 | } 16 | 17 | class ReferenceCollectionishTest: SingleFileTestFixture("references", "ReferenceCollectionish.kt") { 18 | @Test fun `find references to iterator`() { 19 | val request = referenceParams(file, 2, 21) 20 | val references = languageServer.textDocumentService.references(request).get() 21 | 22 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 23 | assertThat("Finds for-loop reference", references, hasItem(hasToString(containsString("line = 8")))) 24 | assertThat("Finds iterator() reference", references, hasItem(hasToString(containsString("line = 9")))) 25 | } 26 | 27 | @Test fun `find references to contains`() { 28 | val request = referenceParams(file, 3, 21) 29 | val references = languageServer.textDocumentService.references(request).get() 30 | 31 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 32 | assertThat("Finds reference using in", references, hasItem(hasToString(containsString("line = 10")))) 33 | assertThat("Finds contains() reference", references, hasItem(hasToString(containsString("line = 11")))) 34 | } 35 | 36 | @Test fun `find references to rangeTo`() { 37 | val request = referenceParams(file, 4, 21) 38 | val references = languageServer.textDocumentService.references(request).get() 39 | 40 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 41 | } 42 | } 43 | 44 | class ReferenceComponentsTest: SingleFileTestFixture("references", "ReferenceComponents.kt") { 45 | @Test fun `find references to component1`() { 46 | val request = referenceParams(file, 2, 21) 47 | val references = languageServer.textDocumentService.references(request).get() 48 | 49 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 50 | assertThat("Finds destructuring reference", references, hasItem(hasToString(containsString("line = 6")))) 51 | assertThat("Finds component1() reference", references, hasItem(hasToString(containsString("line = 7")))) 52 | } 53 | } 54 | 55 | class ReferenceConstructorTest: SingleFileTestFixture("references", "ReferenceConstructor.kt") { 56 | @Test fun `find reference to main constructor`() { 57 | val request = referenceParams(file, 1, 24) 58 | val references = languageServer.textDocumentService.references(request).get() 59 | 60 | assertThat("Finds reference to the main constructor", references, hasItem(hasProperty("uri", containsString("ReferenceConstructor.kt")))) 61 | } 62 | 63 | @Test fun `find reference to secondary constructor`() { 64 | val request = referenceParams(file, 2, 10) 65 | val references = languageServer.textDocumentService.references(request).get() 66 | 67 | assertThat("Finds reference to a secondary constructor", references, hasItem(hasProperty("uri", containsString("ReferenceConstructor.kt")))) 68 | } 69 | } 70 | 71 | class ReferenceGetSetValueTest: SingleFileTestFixture("references", "ReferenceGetSetValue.kt") { 72 | @Test fun `find references to getValue`() { 73 | val request = referenceParams(file, 4, 21) 74 | val references = languageServer.textDocumentService.references(request).get() 75 | 76 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 77 | assertThat(references, hasItem(hasToString(containsString("line = 8")))) 78 | } 79 | 80 | @Test fun `find references to setValue`() { 81 | val request = referenceParams(file, 5, 21) 82 | val references = languageServer.textDocumentService.references(request).get() 83 | 84 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 85 | assertThat(references, hasItem(hasToString(containsString("line = 8")))) 86 | } 87 | } 88 | 89 | class ReferenceGetterSetterTest: SingleFileTestFixture("references", "ReferenceGetterSetter.kt") { 90 | @Test fun `find references to get`() { 91 | val request = referenceParams(file, 2, 19) 92 | val references = languageServer.textDocumentService.references(request).get() 93 | 94 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 95 | assertThat(references, hasItem(hasToString(containsString("line = 7")))) 96 | } 97 | 98 | @Test fun `find references to set`() { 99 | val request = referenceParams(file, 3, 19) 100 | val references = languageServer.textDocumentService.references(request).get() 101 | 102 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 103 | assertThat(references, hasItem(hasToString(containsString("line = 8")))) 104 | } 105 | } 106 | 107 | class ReferenceInvokeTest: SingleFileTestFixture("references", "ReferenceInvoke.kt") { 108 | @Test fun `find references to invoke`() { 109 | val request = referenceParams(file, 2, 21) 110 | val references = languageServer.textDocumentService.references(request).get() 111 | 112 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 113 | assertThat(references, hasItem(hasToString(containsString("line = 6")))) 114 | } 115 | } 116 | 117 | class ReferenceOperatorTest: SingleFileTestFixture("references", "ReferenceOperator.kt") { 118 | @Test fun `find references to equals`() { 119 | val request = referenceParams(file, 2, 30) 120 | val references = languageServer.textDocumentService.references(request).get() 121 | 122 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 123 | } 124 | 125 | @Test fun `find references to compareTo`() { 126 | val request = referenceParams(file, 3, 22) 127 | val references = languageServer.textDocumentService.references(request).get() 128 | 129 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 130 | } 131 | 132 | @Test fun `find references to inc`() { 133 | val request = referenceParams(file, 4, 20) 134 | val references = languageServer.textDocumentService.references(request).get() 135 | 136 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 137 | } 138 | 139 | @Test fun `find references to dec`() { 140 | val request = referenceParams(file, 5, 20) 141 | val references = languageServer.textDocumentService.references(request).get() 142 | 143 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 144 | } 145 | 146 | @Test fun `find references to plus`() { 147 | val request = referenceParams(file, 6, 20) 148 | val references = languageServer.textDocumentService.references(request).get() 149 | 150 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 151 | } 152 | 153 | @Test fun `find references to minus`() { 154 | val request = referenceParams(file, 7, 20) 155 | val references = languageServer.textDocumentService.references(request).get() 156 | 157 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 158 | } 159 | 160 | @Test fun `find references to not`() { 161 | val request = referenceParams(file, 8, 20) 162 | val references = languageServer.textDocumentService.references(request).get() 163 | 164 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 165 | } 166 | 167 | @Test fun `find references to equals without operator keyword`() { 168 | val request = referenceParams(file, 12, 21) 169 | val references = languageServer.textDocumentService.references(request).get() 170 | 171 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 172 | } 173 | } 174 | 175 | class ReferenceOperatorUsingNameTest: SingleFileTestFixture("references", "ReferenceOperatorUsingName.kt") { 176 | @Test fun `find references to equals`() { 177 | val request = referenceParams(file, 2, 30) 178 | val references = languageServer.textDocumentService.references(request).get() 179 | 180 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 181 | } 182 | 183 | @Test fun `find references to compareTo`() { 184 | val request = referenceParams(file, 3, 22) 185 | val references = languageServer.textDocumentService.references(request).get() 186 | 187 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 188 | } 189 | 190 | @Test fun `find references to inc`() { 191 | val request = referenceParams(file, 4, 20) 192 | val references = languageServer.textDocumentService.references(request).get() 193 | 194 | assertThat(references, hasItem(hasProperty("uri", containsString(file)))) 195 | } 196 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.* 4 | import org.eclipse.lsp4j.jsonrpc.messages.Either 5 | import org.eclipse.lsp4j.services.LanguageClient 6 | import org.eclipse.lsp4j.services.TextDocumentService 7 | import org.javacs.kt.completion.completions 8 | import org.javacs.kt.definition.goToDefinition 9 | import org.javacs.kt.diagnostic.convertDiagnostic 10 | import org.javacs.kt.hover.hoverAt 11 | import org.javacs.kt.position.offset 12 | import org.javacs.kt.references.findReferences 13 | import org.javacs.kt.signatureHelp.signatureHelpAt 14 | import org.javacs.kt.symbols.documentSymbols 15 | import org.javacs.kt.util.noFuture 16 | import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics 17 | import java.net.URI 18 | import java.nio.file.Path 19 | import java.nio.file.Paths 20 | import java.time.Duration 21 | import java.util.concurrent.CompletableFuture 22 | 23 | class KotlinTextDocumentService(private val sf: SourceFiles, private val sp: SourcePath) : TextDocumentService { 24 | 25 | private lateinit var client: LanguageClient 26 | 27 | fun connect(client: LanguageClient) { 28 | this.client = client 29 | } 30 | 31 | private fun recover(position: TextDocumentPositionParams, recompile: Boolean): Pair { 32 | val file = Paths.get(URI.create(position.textDocument.uri)) 33 | val content = sp.content(file) 34 | val offset = offset(content, position.position.line, position.position.character) 35 | val compiled = if(recompile) sp.currentVersion(file) else sp.latestCompiledVersion(file) 36 | return Pair(compiled, offset) 37 | } 38 | 39 | override fun codeAction(params: CodeActionParams): CompletableFuture> { 40 | TODO("not implemented") 41 | } 42 | 43 | override fun hover(position: TextDocumentPositionParams): CompletableFuture { 44 | reportTime { 45 | LOG.info("Hovering at ${position.textDocument.uri} ${position.position.line}:${position.position.character}") 46 | 47 | val (file, cursor) = recover(position, true) 48 | val hover = hoverAt(file, cursor) ?: return noFuture("No hover found at ${describePosition(position)}", null) 49 | 50 | return CompletableFuture.completedFuture(hover) 51 | } 52 | } 53 | 54 | override fun documentHighlight(position: TextDocumentPositionParams): CompletableFuture> { 55 | TODO("not implemented") 56 | } 57 | 58 | override fun onTypeFormatting(params: DocumentOnTypeFormattingParams): CompletableFuture> { 59 | TODO("not implemented") 60 | } 61 | 62 | override fun definition(position: TextDocumentPositionParams): CompletableFuture> { 63 | reportTime { 64 | LOG.info("Go-to-definition at ${describePosition(position)}") 65 | 66 | val (file, cursor) = recover(position, false) 67 | val location = goToDefinition(file, cursor) ?: return noFuture("Couldn't find definition at ${describePosition(position)}", emptyList()) 68 | 69 | return CompletableFuture.completedFuture(listOf(location)) 70 | } 71 | } 72 | 73 | override fun rangeFormatting(params: DocumentRangeFormattingParams): CompletableFuture> { 74 | TODO("not implemented") 75 | } 76 | 77 | override fun codeLens(params: CodeLensParams): CompletableFuture> { 78 | TODO("not implemented") 79 | } 80 | 81 | override fun rename(params: RenameParams): CompletableFuture { 82 | TODO("not implemented") 83 | } 84 | 85 | override fun completion(position: TextDocumentPositionParams): CompletableFuture, CompletionList>> { 86 | reportTime { 87 | LOG.info("Completing at ${describePosition(position)}") 88 | 89 | val (file, cursor) = recover(position, false) 90 | val completions = completions(file, cursor) 91 | 92 | LOG.info("Found ${completions.items.size} items") 93 | 94 | return CompletableFuture.completedFuture(Either.forRight(completions)) 95 | } 96 | } 97 | 98 | override fun resolveCompletionItem(unresolved: CompletionItem): CompletableFuture { 99 | TODO("not implemented") 100 | } 101 | 102 | override fun documentSymbol(params: DocumentSymbolParams): CompletableFuture> { 103 | LOG.info("Find symbols in ${params.textDocument}") 104 | 105 | reportTime { 106 | val path = Paths.get(URI(params.textDocument.uri)) 107 | val file = sp.parsedFile(path) 108 | val infos = documentSymbols(file) 109 | 110 | return CompletableFuture.completedFuture(infos) 111 | } 112 | } 113 | 114 | override fun didOpen(params: DidOpenTextDocumentParams) { 115 | val file = Paths.get(URI.create(params.textDocument.uri)) 116 | 117 | sf.open(file, params.textDocument.text, params.textDocument.version) 118 | lintNow(file) 119 | } 120 | 121 | override fun didSave(params: DidSaveTextDocumentParams) { 122 | } 123 | 124 | override fun signatureHelp(position: TextDocumentPositionParams): CompletableFuture { 125 | reportTime { 126 | LOG.info("Signature help at ${describePosition(position)}") 127 | 128 | val (file, cursor) = recover(position, false) 129 | val result = signatureHelpAt(file, cursor) ?: return noFunctionCall(position) 130 | 131 | return CompletableFuture.completedFuture(result) 132 | } 133 | } 134 | 135 | private fun noFunctionCall(position: TextDocumentPositionParams): CompletableFuture { 136 | LOG.info("No function call around ${describePosition(position)}") 137 | 138 | return CompletableFuture.completedFuture(null) 139 | } 140 | 141 | override fun didClose(params: DidCloseTextDocumentParams) { 142 | val file = Paths.get(URI.create(params.textDocument.uri)) 143 | 144 | sf.close(file) 145 | clearDiagnostics(file) 146 | } 147 | 148 | override fun formatting(params: DocumentFormattingParams): CompletableFuture> { 149 | TODO("not implemented") 150 | } 151 | 152 | override fun didChange(params: DidChangeTextDocumentParams) { 153 | val file = Paths.get(URI.create(params.textDocument.uri)) 154 | 155 | sf.edit(file, params.textDocument.version, params.contentChanges) 156 | lintLater(file) 157 | } 158 | 159 | override fun references(position: ReferenceParams): CompletableFuture> { 160 | val file = Paths.get(URI.create(position.textDocument.uri)) 161 | val content = sp.content(file) 162 | val offset = offset(content, position.position.line, position.position.character) 163 | val found = findReferences(file, offset, sp) 164 | 165 | return CompletableFuture.completedFuture(found) 166 | } 167 | 168 | override fun resolveCodeLens(unresolved: CodeLens): CompletableFuture { 169 | TODO("not implemented") 170 | } 171 | 172 | private fun describePosition(position: TextDocumentPositionParams): String { 173 | val path = Paths.get(URI.create(position.textDocument.uri)) 174 | return "${path.fileName} ${position.position.line + 1}:${position.position.character + 1}" 175 | } 176 | 177 | val debounceLint = DebounceDelay(Duration.ofMillis(200)) 178 | val lintTodo = mutableSetOf() 179 | var lintCount = 0 180 | 181 | private fun clearLint(): List { 182 | val result = lintTodo.toList() 183 | lintTodo.clear() 184 | return result 185 | } 186 | 187 | private fun lintLater(file: Path) { 188 | lintTodo.add(file) 189 | debounceLint.submit(::doLint) 190 | } 191 | 192 | private fun lintNow(file: Path) { 193 | lintTodo.add(file) 194 | debounceLint.submitImmediately(::doLint) 195 | } 196 | 197 | private fun doLint() { 198 | LOG.info("Linting ${describeFiles(lintTodo)}") 199 | val files = clearLint() 200 | val context = sp.compileFiles(files) 201 | reportDiagnostics(files, context.diagnostics) 202 | lintCount++ 203 | } 204 | 205 | private fun reportDiagnostics(compiled: Collection, kotlinDiagnostics: Diagnostics) { 206 | val langServerDiagnostics = kotlinDiagnostics.flatMap(::convertDiagnostic) 207 | val byFile = langServerDiagnostics.groupBy({ it.first }, { it.second }) 208 | 209 | for ((file, diagnostics) in byFile) { 210 | if (sf.isOpen(file)) { 211 | client.publishDiagnostics(PublishDiagnosticsParams(file.toUri().toString(), diagnostics)) 212 | 213 | LOG.info("Reported ${diagnostics.size} diagnostics in ${file.fileName}") 214 | } 215 | else LOG.info("Ignore ${diagnostics.size} diagnostics in ${file.fileName} because it's not open") 216 | } 217 | 218 | val noErrors = compiled - byFile.keys 219 | for (file in noErrors) { 220 | clearDiagnostics(file) 221 | 222 | LOG.info("No diagnostics in ${file.fileName}") 223 | } 224 | 225 | // LOG.log(Level.WARNING, "LINT", Exception()) 226 | 227 | lintCount++ 228 | } 229 | 230 | private fun clearDiagnostics(file: Path) { 231 | client.publishDiagnostics(PublishDiagnosticsParams(file.toUri().toString(), listOf())) 232 | } 233 | } 234 | 235 | private inline fun reportTime(block: () -> T): T { 236 | val started = System.currentTimeMillis() 237 | try { 238 | return block() 239 | } finally { 240 | val finished = System.currentTimeMillis() 241 | LOG.info("Finished in ${finished - started} ms") 242 | } 243 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/javacs/kt/references/findReferences.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt.references 2 | 3 | import org.eclipse.lsp4j.Location 4 | import org.javacs.kt.LOG 5 | import org.javacs.kt.SourcePath 6 | import org.javacs.kt.position.location 7 | import org.javacs.kt.util.emptyResult 8 | import org.javacs.kt.util.findParent 9 | import org.javacs.kt.util.preOrderTraversal 10 | import org.javacs.kt.util.toPath 11 | import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor 12 | import org.jetbrains.kotlin.descriptors.ConstructorDescriptor 13 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 14 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 15 | import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi 16 | import org.jetbrains.kotlin.lexer.KtSingleValueToken 17 | import org.jetbrains.kotlin.lexer.KtTokens 18 | import org.jetbrains.kotlin.name.Name 19 | import org.jetbrains.kotlin.psi.* 20 | import org.jetbrains.kotlin.resolve.BindingContext 21 | import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe 22 | import org.jetbrains.kotlin.types.expressions.OperatorConventions 23 | import org.jetbrains.kotlin.util.OperatorNameConventions 24 | import java.nio.file.Path 25 | 26 | fun findReferences(file: Path, cursor: Int, sp: SourcePath): List { 27 | return doFindReferences(file, cursor, sp) 28 | .map { location(it) } 29 | .toList() 30 | } 31 | 32 | private fun doFindReferences(file: Path, cursor: Int, sp: SourcePath): Collection { 33 | val recover = sp.currentVersion(file) 34 | val element = recover.elementAtPoint(cursor)?.findParent() ?: return emptyResult("No declaration at ${recover.describePosition(cursor)}") 35 | val declaration = recover.compile[BindingContext.DECLARATION_TO_DESCRIPTOR, element] ?: return emptyResult("Declaration ${element.fqName} has no descriptor") 36 | val maybes = possibleReferences(declaration, sp).map { it.toPath() } 37 | LOG.info("Scanning ${maybes.size} files for references to ${element.fqName}") 38 | val recompile = sp.compileFiles(maybes) 39 | 40 | return when { 41 | isComponent(declaration) -> findComponentReferences(element, recompile) + findNameReferences(element, recompile) 42 | isIterator(declaration) -> findIteratorReferences(element, recompile) + findNameReferences(element, recompile) 43 | isPropertyDelegate(declaration) -> findDelegateReferences(element, recompile) + findNameReferences(element, recompile) 44 | else -> findNameReferences(element, recompile) 45 | } 46 | } 47 | 48 | private fun findNameReferences(element: KtNamedDeclaration, recompile: BindingContext): List { 49 | val references = recompile.getSliceContents(BindingContext.REFERENCE_TARGET) 50 | 51 | return references.filter { matchesReference(it.value, element) }.map { it.key } 52 | } 53 | 54 | private fun findDelegateReferences(element: KtNamedDeclaration, recompile: BindingContext): List { 55 | val references = recompile.getSliceContents(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL) 56 | 57 | return references 58 | .filter { matchesReference(it.value.candidateDescriptor, element) } 59 | .map { it.value.call.callElement } 60 | } 61 | 62 | private fun findIteratorReferences(element: KtNamedDeclaration, recompile: BindingContext): List { 63 | val references = recompile.getSliceContents(BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL) 64 | 65 | return references 66 | .filter { matchesReference( it.value.candidateDescriptor, element) } 67 | .map { it.value.call.callElement } 68 | } 69 | 70 | private fun findComponentReferences(element: KtNamedDeclaration, recompile: BindingContext): List { 71 | val references = recompile.getSliceContents(BindingContext.COMPONENT_RESOLVED_CALL) 72 | 73 | return references 74 | .filter { matchesReference(it.value.candidateDescriptor, element) } 75 | .map { it.value.call.callElement } 76 | } 77 | 78 | // TODO use imports to limit search 79 | private fun possibleReferences(declaration: DeclarationDescriptor, sp: SourcePath): Set { 80 | if (declaration is ClassConstructorDescriptor) { 81 | return possibleNameReferences(declaration.constructedClass.name, sp) 82 | } 83 | if (isComponent(declaration)) { 84 | return possibleComponentReferences(sp) + possibleNameReferences(declaration.name, sp) 85 | } 86 | if (isPropertyDelegate(declaration)) { 87 | return hasPropertyDelegates(sp) + possibleNameReferences(declaration.name, sp) 88 | } 89 | if (isGetSet(declaration)) { 90 | return possibleGetSets(sp) + possibleNameReferences(declaration.name, sp) 91 | } 92 | if (isIterator(declaration)) { 93 | return hasForLoops(sp) + possibleNameReferences(declaration.name, sp) 94 | } 95 | if (declaration is FunctionDescriptor && declaration.isOperator && declaration.name == OperatorNameConventions.INVOKE) { 96 | return possibleInvokeReferences(declaration, sp) + possibleNameReferences(declaration.name, sp) 97 | } 98 | if (declaration is FunctionDescriptor) { 99 | val operators = operatorNames(declaration.name) 100 | 101 | return possibleTokenReferences(operators, sp) + possibleNameReferences(declaration.name, sp) 102 | } 103 | return possibleNameReferences(declaration.name, sp) 104 | } 105 | 106 | private fun isPropertyDelegate(declaration: DeclarationDescriptor) = 107 | declaration is FunctionDescriptor && 108 | declaration.isOperator && 109 | (declaration.name == OperatorNameConventions.GET_VALUE || declaration.name == OperatorNameConventions.SET_VALUE) 110 | 111 | private fun hasPropertyDelegates(sp: SourcePath): Set = 112 | sp.all().filter(::hasPropertyDelegate).toSet() 113 | 114 | fun hasPropertyDelegate(source: KtFile): Boolean = 115 | source.preOrderTraversal().filterIsInstance().any() 116 | 117 | private fun isIterator(declaration: DeclarationDescriptor) = 118 | declaration is FunctionDescriptor && 119 | declaration.isOperator && 120 | declaration.name == OperatorNameConventions.ITERATOR 121 | 122 | private fun hasForLoops(sp: SourcePath): Set = 123 | sp.all().filter(::hasForLoop).toSet() 124 | 125 | private fun hasForLoop(source: KtFile): Boolean = 126 | source.preOrderTraversal().filterIsInstance().any() 127 | 128 | private fun isGetSet(declaration: DeclarationDescriptor) = 129 | declaration is FunctionDescriptor && 130 | declaration.isOperator && 131 | (declaration.name == OperatorNameConventions.GET || declaration.name == OperatorNameConventions.SET) 132 | 133 | private fun possibleGetSets(sp: SourcePath): Set = 134 | sp.all().filter(::possibleGetSet).toSet() 135 | 136 | private fun possibleGetSet(source: KtFile) = 137 | source.preOrderTraversal().filterIsInstance().any() 138 | 139 | private fun possibleInvokeReferences(declaration: FunctionDescriptor, sp: SourcePath) = 140 | sp.all().filter { possibleInvokeReference(declaration, it) }.toSet() 141 | 142 | // TODO this is not very selective 143 | private fun possibleInvokeReference(declaration: FunctionDescriptor, source: KtFile): Boolean = 144 | source.preOrderTraversal().filterIsInstance().any() 145 | 146 | private fun isComponent(declaration: DeclarationDescriptor): Boolean = 147 | declaration is FunctionDescriptor && 148 | declaration.isOperator && 149 | OperatorNameConventions.COMPONENT_REGEX.matches(declaration.name.identifier) 150 | 151 | private fun possibleComponentReferences(sp: SourcePath): Set = 152 | sp.all().filter { possibleComponentReference(it) }.toSet() 153 | 154 | private fun possibleComponentReference(source: KtFile): Boolean = 155 | source.preOrderTraversal() 156 | .filterIsInstance() 157 | .any() 158 | 159 | private fun possibleTokenReferences(find: List, sp: SourcePath): Set = 160 | sp.all().filter { possibleTokenReference(find, it) }.toSet() 161 | 162 | private fun possibleTokenReference(find: List, source: KtFile): Boolean = 163 | source.preOrderTraversal() 164 | .filterIsInstance() 165 | .any { it.operationSignTokenType in find } 166 | 167 | private fun possibleNameReferences(declaration: Name, sp: SourcePath): Set = 168 | sp.all().filter { possibleNameReference(declaration, it) }.toSet() 169 | 170 | private fun possibleNameReference(declaration: Name, source: KtFile): Boolean = 171 | source.preOrderTraversal() 172 | .filterIsInstance() 173 | .any { it.getReferencedNameAsName() == declaration } 174 | 175 | private fun matchesReference(found: DeclarationDescriptor, search: KtNamedDeclaration): Boolean { 176 | if (found is ConstructorDescriptor && found.isPrimary) 177 | return search is KtClass && found.constructedClass.fqNameSafe == search.fqName 178 | else 179 | return found.findPsi() == search 180 | } 181 | 182 | private fun operatorNames(name: Name): List = 183 | when (name) { 184 | OperatorNameConventions.EQUALS -> listOf(KtTokens.EQEQ) 185 | OperatorNameConventions.COMPARE_TO -> listOf(KtTokens.GT, KtTokens.LT, KtTokens.LTEQ, KtTokens.GTEQ) 186 | else -> { 187 | val token = OperatorConventions.UNARY_OPERATION_NAMES.inverse()[name] ?: 188 | OperatorConventions.BINARY_OPERATION_NAMES.inverse()[name] ?: 189 | OperatorConventions.ASSIGNMENT_OPERATIONS.inverse()[name] ?: 190 | OperatorConventions.BOOLEAN_OPERATIONS.inverse()[name] 191 | listOfNotNull(token) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/test/kotlin/org/javacs/kt/CompletionsTest.kt: -------------------------------------------------------------------------------- 1 | package org.javacs.kt 2 | 3 | import org.eclipse.lsp4j.CompletionList 4 | import org.hamcrest.Matchers.* 5 | import org.junit.Assert.assertThat 6 | import org.junit.Test 7 | 8 | class InstanceMemberTest: SingleFileTestFixture("completions", "InstanceMember.kt") { 9 | @Test fun `complete instance members`() { 10 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 3, 15)).get().right!! 11 | val labels = completions.items.map { it.label } 12 | 13 | assertThat(labels, hasItem("instanceFoo")) 14 | assertThat(labels, hasItem("extensionFoo")) 15 | assertThat(labels, hasItem("fooVar")) 16 | assertThat(labels, not(hasItem("privateInstanceFoo"))) 17 | assertThat(labels, not(hasItem("getFooVar"))) 18 | assertThat(labels, not(hasItem("setFooVar"))) 19 | 20 | assertThat("Reports instanceFoo only once", completions.items.filter { it.label == "instanceFoo" }, hasSize(1)) 21 | assertThat("Reports extensionFoo only once", completions.items.filter { it.label == "extensionFoo" }, hasSize(1)) 22 | } 23 | 24 | @Test fun `find count extension function`() { 25 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 8, 14)).get().right!! 26 | val labels = completions.items.map { it.label } 27 | 28 | assertThat(labels, hasItem("count")) 29 | } 30 | 31 | @Test fun `complete method reference`() { 32 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 13, 16)).get().right!! 33 | val labels = completions.items.map { it.label } 34 | 35 | assertThat(labels, hasItem("instanceFoo")) 36 | // assertThat(labels, not(hasItem("extensionFoo"))) Kotlin will probably implement this later 37 | assertThat(labels, hasItem("fooVar")) 38 | assertThat(labels, not(hasItem("privateInstanceFoo"))) 39 | assertThat(labels, not(hasItem("getFooVar"))) 40 | assertThat(labels, not(hasItem("setFooVar"))) 41 | 42 | assertThat(completions.items.filter { it.label == "instanceFoo" }.firstOrNull(), hasProperty("insertText", equalTo("instanceFoo"))) 43 | } 44 | 45 | @Test fun `complete unqualified function reference`() { 46 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 17, 8)).get().right!! 47 | val labels = completions.items.map { it.label } 48 | 49 | assertThat(labels, hasItem("findFunctionReference")) 50 | } 51 | 52 | @Test fun `complete a function name within a call`() { 53 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 22, 27)).get().right!! 54 | val labels = completions.items.map { it.label } 55 | 56 | assertThat(labels, hasItem("findFunctionReference")) 57 | assertThat(labels, not(hasItem("instanceFoo"))) 58 | } 59 | } 60 | 61 | class InstanceMembersJava: SingleFileTestFixture("completions", "InstanceMembersJava.kt") { 62 | @Test fun `convert getFileName to fileName`() { 63 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 4, 14)).get().right!! 64 | val labels = completions.items.map { it.label } 65 | 66 | assertThat(labels, hasItem("fileName")) 67 | assertThat(labels, not(hasItem("getFileName"))) 68 | } 69 | } 70 | 71 | class FunctionScopeTest: SingleFileTestFixture("completions", "FunctionScope.kt") { 72 | @Test fun `complete identifiers in function scope`() { 73 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 4, 10)).get().right!! 74 | val labels = completions.items.map { it.label } 75 | 76 | assertThat(labels, hasItem("anArgument")) 77 | assertThat(labels, hasItem("aLocal")) 78 | assertThat(labels, hasItem("aClassVal")) 79 | assertThat(labels, hasItem("aClassFun")) 80 | assertThat(labels, hasItem("aCompanionVal")) 81 | assertThat(labels, hasItem("aCompanionFun")) 82 | 83 | assertThat("Reports anArgument only once", completions.items.filter { it.label == "anArgument" }, hasSize(1)) 84 | assertThat("Reports aLocal only once", completions.items.filter { it.label == "aLocal" }, hasSize(1)) 85 | assertThat("Reports aClassVal only once", completions.items.filter { it.label == "aClassVal" }, hasSize(1)) 86 | assertThat("Reports aClassFun only once", completions.items.filter { it.label == "aClassFun" }, hasSize(1)) 87 | assertThat("Reports aCompanionVal only once", completions.items.filter { it.label == "aCompanionVal" }, hasSize(1)) 88 | assertThat("Reports aCompanionFun only once", completions.items.filter { it.label == "aCompanionFun" }, hasSize(1)) 89 | } 90 | } 91 | 92 | class TypesTest: SingleFileTestFixture("completions", "Types.kt") { 93 | @Test fun `complete a type name`() { 94 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 2, 25)).get().right!! 95 | val labels = completions.items.map { it.label } 96 | 97 | assertThat(labels, hasItem("SomeInnerClass")) 98 | assertThat(labels, hasItem("String")) 99 | assertThat(labels, hasItem("SomeInnerObject")) 100 | assertThat(labels, hasItem("SomeAlias")) 101 | } 102 | } 103 | 104 | class FillEmptyBodyTest: SingleFileTestFixture("completions", "FillEmptyBody.kt") { 105 | @Test fun `fill an empty body`() { 106 | replace(file, 2, 16, "", """" 107 | Callee. 108 | """) 109 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 3, 20)).get().right!! 110 | val labels = completions.items.map { it.label } 111 | 112 | assertThat(labels, hasItem("bar")) 113 | } 114 | } 115 | 116 | class ConstructorTest: SingleFileTestFixture("completions", "Constructor.kt") { 117 | @Test fun `complete a constructor`() { 118 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 2, 10)).get().right!! 119 | val labels = completions.items.map { it.label } 120 | 121 | assertThat(labels, hasItem("SomeConstructor")) 122 | } 123 | } 124 | 125 | class MiddleOfFunctionTest: SingleFileTestFixture("completions", "MiddleOfFunction.kt") { 126 | @Test fun `complete in the middle of a function`() { 127 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 3, 11)).get().right!! 128 | val labels = completions.items.map { it.label } 129 | 130 | assertThat(labels, hasItem("subSequence")) 131 | } 132 | } 133 | 134 | class BackquotedFunctionTest: SingleFileTestFixture("completions", "BackquotedFunction.kt") { 135 | @Test fun `complete with backquotes`() { 136 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 2, 7)).get().right!! 137 | val insertText = completions.items.map { it.insertText } 138 | 139 | assertThat(insertText, hasItem("`fun that needs backquotes`()")) 140 | } 141 | } 142 | 143 | class CompleteStaticsTest: SingleFileTestFixture("completions", "Statics.kt") { 144 | @Test fun `java static method`() { 145 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 4, 16)).get().right!! 146 | val labels = completions.items.map { it.label } 147 | 148 | assertThat(labels, hasItem("isNull")) 149 | } 150 | 151 | @Test fun `java static method reference`() { 152 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 7, 17)).get().right!! 153 | val labels = completions.items.map { it.label } 154 | 155 | assertThat(labels, hasItem("isNull")) 156 | } 157 | 158 | @Test fun `object method`() { 159 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 5, 17)).get().right!! 160 | val labels = completions.items.map { it.label } 161 | 162 | assertThat(labels, hasItem("objectFun")) 163 | } 164 | 165 | @Test fun `companion object method`() { 166 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 6, 16)).get().right!! 167 | val labels = completions.items.map { it.label } 168 | 169 | assertThat(labels, hasItem("companionFun")) 170 | } 171 | } 172 | 173 | class VisibilityTest: SingleFileTestFixture("completions", "Visibility.kt") { 174 | @Test fun `find tricky visibility members`() { 175 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 3, 10)).get().right!! 176 | val labels = completions.items.map { it.label } 177 | 178 | assertThat(labels, hasItems("privateThisFun", "protectedThisFun", "publicThisFun", "privateThisCompanionFun", "protectedThisCompanionFun", "publicThisCompanionFun", "privateTopLevelFun")) 179 | assertThat(labels, hasItems("protectedSuperFun", "publicSuperFun", "protectedSuperCompanionFun", "publicSuperCompanionFun")) 180 | assertThat(labels, not(hasItems("privateSuperFun", "privateSuperCompanionFun", "publicExtensionFun"))) 181 | } 182 | } 183 | 184 | class ImportsTest: SingleFileTestFixture("completions", "Imports.kt") { 185 | @Test fun `complete import from java-nio-path-P`() { 186 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 1, 23)).get().right!! 187 | val labels = completions.items.map { it.label } 188 | 189 | assertThat(labels, hasItems("Path", "Paths")) 190 | } 191 | 192 | @Test fun `complete import from java-nio-`() { 193 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 3, 25)).get().right!! 194 | val labels = completions.items.map { it.label } 195 | 196 | assertThat(labels, hasItems("MethodHandle")) 197 | } 198 | } 199 | 200 | class DoubleDotTest: SingleFileTestFixture("completions", "DoubleDot.kt") { 201 | @Test fun `complete nested select`() { 202 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 2, 15)).get().right!! 203 | val labels = completions.items.map { it.label } 204 | 205 | assertThat(labels, not(hasItem("chars"))) 206 | assertThat(labels, hasItem("anyMatch")) 207 | } 208 | } 209 | 210 | class QuestionDotTest: SingleFileTestFixture("completions", "QuestionDot.kt") { 211 | @Test fun `complete null-safe select`() { 212 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 2, 8)).get().right!! 213 | val labels = completions.items.map { it.label } 214 | 215 | assertThat(labels, hasItem("chars")) 216 | } 217 | } 218 | 219 | class OuterDotInnerTest: SingleFileTestFixture("completions", "OuterDotInner.kt") { 220 | @Test fun `complete as OuterClass-InnerClass`() { 221 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 2, 24)).get().right!! 222 | val labels = completions.items.map { it.label } 223 | 224 | assertThat(labels, hasItem("InnerClass")) 225 | } 226 | 227 | @Test fun `complete static OuterClass-InnerClass`() { 228 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 6, 19)).get().right!! 229 | val labels = completions.items.map { it.label } 230 | 231 | assertThat(labels, hasItem("InnerClass")) 232 | } 233 | } 234 | 235 | class EditCallTest: SingleFileTestFixture("completions", "EditCall.kt") { 236 | @Test fun `edit existing function`() { 237 | val completions = languageServer.textDocumentService.completion(textDocumentPosition(file, 2, 11)).get().right!! 238 | val labels = completions.items.map { it.label } 239 | 240 | assertThat(labels, hasItem("println")) 241 | assertThat(completions.items.filter { it.label == "println" }.firstOrNull(), hasProperty("insertText", equalTo("println"))) 242 | } 243 | } -------------------------------------------------------------------------------- /syntaxes/Kotlin.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kotlin", 3 | "scopeName": "source.kotlin", 4 | "patterns": [ 5 | { 6 | "include": "#import" 7 | }, 8 | { 9 | "include": "#package" 10 | }, 11 | { 12 | "include": "#code" 13 | } 14 | ], 15 | "repository": { 16 | "import": { 17 | "begin": "\\b(import)\\b\\s*", 18 | "beginCaptures": { 19 | "1": { 20 | "name": "storage.type.import.kotlin" 21 | } 22 | }, 23 | "end": ";|$", 24 | "name": "meta.import.kotlin", 25 | "contentName": "entity.name.package.kotlin", 26 | "patterns": [ 27 | { 28 | "include": "#comments" 29 | }, 30 | { 31 | "match": "\\*", 32 | "name": "variable.language.wildcard.kotlin" 33 | } 34 | ] 35 | }, 36 | "package": { 37 | "begin": "\\b(package)\\b\\s*", 38 | "beginCaptures": { 39 | "1": { 40 | "name": "storage.type.package.kotlin" 41 | } 42 | }, 43 | "end": ";|$", 44 | "name": "meta.package.kotlin", 45 | "contentName": "entity.name.package.kotlin", 46 | "patterns": [ 47 | { 48 | "include": "#comments" 49 | } 50 | ] 51 | }, 52 | "code": { 53 | "patterns": [ 54 | { 55 | "include": "#comments" 56 | }, 57 | { 58 | "include": "#keywords" 59 | }, 60 | { 61 | "include": "#annotation-simple" 62 | }, 63 | { 64 | "include": "#annotation-site-list" 65 | }, 66 | { 67 | "include": "#annotation-site" 68 | }, 69 | { 70 | "include": "#class-declaration" 71 | }, 72 | { 73 | "include": "#object-declaration" 74 | }, 75 | { 76 | "include": "#type-alias" 77 | }, 78 | { 79 | "include": "#function-declaration" 80 | }, 81 | { 82 | "include": "#variable-declaration" 83 | }, 84 | { 85 | "include": "#type-constraint" 86 | }, 87 | { 88 | "include": "#type-annotation" 89 | }, 90 | { 91 | "include": "#function-call" 92 | }, 93 | { 94 | "include": "#method-reference" 95 | }, 96 | { 97 | "include": "#key" 98 | }, 99 | { 100 | "include": "#string" 101 | }, 102 | { 103 | "include": "#string-empty" 104 | }, 105 | { 106 | "include": "#string-multiline" 107 | }, 108 | { 109 | "include": "#character" 110 | }, 111 | { 112 | "include": "#operators" 113 | }, 114 | { 115 | "include": "#self-reference" 116 | }, 117 | { 118 | "include": "#decimal-literal" 119 | }, 120 | { 121 | "include": "#hex-literal" 122 | }, 123 | { 124 | "include": "#binary-literal" 125 | }, 126 | { 127 | "include": "#boolean-literal" 128 | }, 129 | { 130 | "include": "#null-literal" 131 | } 132 | ] 133 | }, 134 | "comments": { 135 | "patterns": [ 136 | { 137 | "include": "#comment-line" 138 | }, 139 | { 140 | "include": "#comment-block" 141 | }, 142 | { 143 | "include": "#comment-javadoc" 144 | } 145 | ] 146 | }, 147 | "comment-line": { 148 | "begin": "//", 149 | "end": "$", 150 | "name": "comment.line.double-slash.kotlin" 151 | }, 152 | "comment-block": { 153 | "begin": "/\\*(?!\\*)", 154 | "end": "\\*/", 155 | "name": "comment.block.kotlin" 156 | }, 157 | "comment-javadoc": { 158 | "patterns": [ 159 | { 160 | "begin": "/\\*\\*", 161 | "end": "\\*/", 162 | "name": "comment.block.javadoc.kotlin", 163 | "patterns": [ 164 | { 165 | "match": "@(author|deprecated|return|see|serial|since|version)\\b", 166 | "name": "keyword.other.documentation.javadoc.kotlin" 167 | }, 168 | { 169 | "match": "(@param)\\s+(\\S+)", 170 | "captures": { 171 | "1": { 172 | "name": "keyword.other.documentation.javadoc.kotlin" 173 | }, 174 | "2": { 175 | "name": "variable.parameter.kotlin" 176 | } 177 | } 178 | }, 179 | { 180 | "match": "(@(?:exception|throws))\\s+(\\S+)", 181 | "captures": { 182 | "1": { 183 | "name": "keyword.other.documentation.javadoc.kotlin" 184 | }, 185 | "2": { 186 | "name": "entity.name.type.class.kotlin" 187 | } 188 | } 189 | }, 190 | { 191 | "match": "{(@link)\\s+(\\S+)?#([\\w$]+\\s*\\([^\\(\\)]*\\)).*}", 192 | "captures": { 193 | "1": { 194 | "name": "keyword.other.documentation.javadoc.kotlin" 195 | }, 196 | "2": { 197 | "name": "entity.name.type.class.kotlin" 198 | }, 199 | "3": { 200 | "name": "variable.parameter.kotlin" 201 | } 202 | } 203 | } 204 | ] 205 | } 206 | ] 207 | }, 208 | "keywords": { 209 | "patterns": [ 210 | { 211 | "include": "#prefix-modifiers" 212 | }, 213 | { 214 | "include": "#postfix-modifiers" 215 | }, 216 | { 217 | "include": "#soft-keywords" 218 | }, 219 | { 220 | "include": "#hard-keywords" 221 | } 222 | ] 223 | }, 224 | "prefix-modifiers": { 225 | "match": "\\b(abstract|final|enum|open|annotation|sealed|data|override|final|abstract|lateinit|private|protected|public|internal|inner|companion|noinline|crossinline|vararg|reified|tailrec|operator|infix|inline|external|const|suspend)\\b", 226 | "name": "storage.modifier.other.kotlin" 227 | }, 228 | "postfix-modifiers": { 229 | "match": "\\b(where|by|get|set)\\b", 230 | "name": "storage.modifier.other.kotlin" 231 | }, 232 | "soft-keywords": { 233 | "match": "\\b(catch|finally)\\b", 234 | "name": "keyword.soft.kotlin" 235 | }, 236 | "hard-keywords": { 237 | "match": "\\b(as|for|typeof|is|in|throw|return|break|continue|if|else|while|do|when|try)\\b", 238 | "name": "keyword.hard.kotlin" 239 | }, 240 | "annotation-simple": { 241 | "match": "(?<([^<>]|\\g)+>)?", 278 | "captures": { 279 | "1": { 280 | "name": "storage.type.class.kotlin" 281 | }, 282 | "2": { 283 | "name": "entity.name.type.class.kotlin" 284 | }, 285 | "3": { 286 | "patterns": [ 287 | { 288 | "include": "#type-parameter" 289 | } 290 | ] 291 | } 292 | } 293 | }, 294 | "object-declaration": { 295 | "match": "\\b(object)\\s+(\\b\\w+\\b|`[^`]+`)", 296 | "captures": { 297 | "1": { 298 | "name": "storage.type.object.kotlin" 299 | }, 300 | "2": { 301 | "name": "entity.name.type.object.kotlin" 302 | } 303 | } 304 | }, 305 | "type-alias": { 306 | "match": "\\b(typealias)\\s+(\\b\\w+\\b|`[^`]+`)\\s*(?<([^<>]|\\g)+>)?", 307 | "captures": { 308 | "1": { 309 | "name": "storage.type.alias.kotlin" 310 | }, 311 | "2": { 312 | "name": "entity.name.type.kotlin" 313 | }, 314 | "3": { 315 | "patterns": [ 316 | { 317 | "include": "#type-parameter" 318 | } 319 | ] 320 | } 321 | } 322 | }, 323 | "function-declaration": { 324 | "match": "\\b(fun)\\b\\s*(?<([^<>]|\\g)+>)?\\s*(?:(\\w+)\\.)?(\\b\\w+\\b|`[^`]+`)", 325 | "captures": { 326 | "1": { 327 | "name": "storage.type.function.kotlin" 328 | }, 329 | "2": { 330 | "patterns": [ 331 | { 332 | "include": "#type-parameter" 333 | } 334 | ] 335 | }, 336 | "4": { 337 | "name": "entity.name.type.class.extension.kotlin" 338 | }, 339 | "5": { 340 | "name": "entity.name.function.declaration.kotlin" 341 | } 342 | } 343 | }, 344 | "variable-declaration": { 345 | "match": "\\b(val|var)\\b\\s*(?<([^<>]|\\g)+>)?", 346 | "captures": { 347 | "1": { 348 | "name": "storage.type.variable.kotlin" 349 | }, 350 | "2": { 351 | "patterns": [ 352 | { 353 | "include": "#type-parameter" 354 | } 355 | ] 356 | } 357 | } 358 | }, 359 | "type-parameter": { 360 | "patterns": [ 361 | { 362 | "match": "\\b\\w+\\b", 363 | "name": "entity.name.type.kotlin" 364 | }, 365 | { 366 | "match": "\\b(in|out)\\b", 367 | "name": "storage.modifier.kotlin" 368 | } 369 | ] 370 | }, 371 | "type-annotation": { 372 | "match": "(?|(?[<(]([^<>()]|\\g)+[)>]))+", 373 | "captures": { 374 | "0": { 375 | "patterns": [ 376 | { 377 | "include": "#type-parameter" 378 | } 379 | ] 380 | } 381 | } 382 | }, 383 | "function-call": { 384 | "match": "\\??\\.?(\\b\\w+\\b|`[^`]+`)\\s*(?<([^<>]|\\g)+>)?\\s*(?=[({])", 385 | "captures": { 386 | "1": { 387 | "name": "entity.name.function.call.kotlin" 388 | }, 389 | "2": { 390 | "patterns": [ 391 | { 392 | "include": "#type-parameter" 393 | } 394 | ] 395 | } 396 | } 397 | }, 398 | "method-reference": { 399 | "match": "\\??::(\\b\\w+\\b|`[^`]+`)", 400 | "captures": { 401 | "1": { 402 | "name": "entity.name.function.reference.kotlin" 403 | } 404 | } 405 | }, 406 | "key": { 407 | "match": "\\b(\\w=)\\s*(=)", 408 | "captures": { 409 | "1": { 410 | "name": "variable.parameter.kotlin" 411 | }, 412 | "2": { 413 | "name": "keyword.operator.assignment.kotlin" 414 | } 415 | } 416 | }, 417 | "string-empty": { 418 | "match": "(?=|===|==|=>|=|\\!==|\\!=|\\+=|\\+\\+|\\+|-=|--|-|\\*=|\\*|/=|/|%=|%|!|\\&\\&|\\&|\\|\\||\\|..", 508 | "name": "keyword.operator.kotlin" 509 | }, 510 | "self-reference": { 511 | "match": "\\b(this|super)(@\\w+)?\\b", 512 | "name": "variable.language.this.kotlin" 513 | } 514 | } 515 | } --------------------------------------------------------------------------------