Kotlin plugin consists of several plugins, here is a short description for each of them:
-
kotlin-bundled-compiler
: This plugin is used as a dependency for all other plugins, it exports main jars to work with Kotlin (kotlin-compiler.jar
,kotlin-ide-common.jar
...). Kotlin compiler will be used as the bundled compiler in the built plugin and as a library during development.Also,
kotlin-bundled-compiler
plugin contains several helper classes for IDE-features (such as formatter) that are coming from IntelliJ IDEA. -
kotlin-eclipse-aspects
: This plugin provides several aspects to weave into Eclipse and JDT internals. -
kotlin-eclipse-core
: This plugin is used to interact with the Kotlin compiler to configure it and provide such features as analysis, compilation and interoperability with Java. -
kotlin-eclipse-maven
: This plugin depends onm2e
plugin and provides functionality to configure maven project with Kotlin. -
kotlin-eclipse-ui
: This plugin provides IDE features through the standard Eclipse and JDT extension points. -
kotlin-eclipse-test-framework
: This plugin contains useful utils and mock classes to write tests -
kotlin-eclipse-ui-test
: This plugin contains functional tests for IDE features
Existing Java code can be called from Kotlin in a natural way, and Kotlin code can be used from Java. Java code in Eclipse should understand Kotlin. Features such as navigation, refactorings, find usages, and others should work together with Kotlin and Java.
Note that Kotlin does not have a presentation compiler, like Java or Scala. Instead of this, the
Kotlin plugin generates so called "light class files": Kotlin source code translated to bytecode declarations without bodies.
Each project with Kotlin in Eclipse depends on KOTLIN_CONTAINER
(see KotlinClasspathContainer
)
which contains kotlin-stdlib.jar
, kotlin-reflect.jar
and a folder with light classfiles (kotlin_bin
).
Light classes are stored only in virtual memory and are managed by a special file system (see KotlinFileSystem
, KotlinFileStore
),
so they do not affect the runtime.
Let us describe what is happening on each file save.
On each file save Eclipse triggers Kotlin builder, then the method KotlinLightClassGeneration.updateLightClasses()
is called which takes affected files and computes names of class files that can be created from the affected source files.
If we don't find a light class in our cache, we create a new empty class file in our file system. If file exists, we touch that file.
After this, Eclipse determines that some class files on the classpath were added or changed which triggers reindex for those files
by calling method KotlinFileStore.openInputStream
.
This method generates bytecode for the light class by calling the Kotlin compiler in special mode (KotlinLightClassGeneration.buildLightClasses
).
Basically, this allows Java to see Kotlin sources as special binary dependency.
Existence of light classes allows to call Kotlin code from Java in Eclipse, but to navigate from Java to Kotlin source code we have to map light classes to the source code.
Otherwise we would navigate to the binary code. Unfortunately, Eclipse JDT does not provide any extension point to handle such case, and to do so, we use aspects to weave
into Java navigation mechanism. We provide a simple aspect (KotlinOpenEditorAspect.aj
),
which weaves into org.eclipse.jdt.internal.ui.javaeditor.EditorUtility.openInEditor
method and checks input element.
If this element belongs to our special file system, then we are trying to find corresponding source element in Kotlin and navigate to it.
Kotlin plugin provides editors for usual Kotlin files (.kt
), Kotlin script files (.kts
) and Kotlin binary files (.class
files).
Each editor implements common interface KotlinEditor
.
Editors for Kotlin files and script files also implement KotlinCommonEditor
.
KotlinCommonEditor
extends Java editor (CompilationUnitEditor
) and provides own editor actions (see createActions
)
As an example of editor action let's consider how organize imports works. Organize imports action is registered in createActions
method with the corresponding action ID. As we reuse the Java editor, we don't have to set up shortcuts, they will be the same as for Java editor.
The main method for this action is KotlinOrganizeImportsAction.run
.
First of all, it collects missing imports, adds them to the existing imports and then runs KotlinOrganizeImportsAction.optimizeImports
.
This method removes duplicates and unused imports, reorganizes imports and replaces some explicit imports with the star import. The important part here is that we
reuse the code to optimize imports from the Kotlin plugin for IntelliJ IDEA. The original method that is called from the Eclipse plugin is buildOptimizedImports
,
which is also used in the plugin for IDEA.
Eclipse plugin depends on kotlin-ide-common.jar
artifact, which provides common functionality for IDEA and Eclipse plugin.
Basically, this is a module (ide-common
) in Kotlin project with minimum dependencies,
so it can be used in Eclipse or Netbeans plugin. This module provides several features that are used across the Eclipse plugin.
For example, completion in the Eclipse plugin mostly reuses parts of completion from the IDEA plugin
(KotlinCompletionUtils.getReferenceVariants
uses ReferenceVarianceHelper
).
Generally, it's a preferable way to implement features in the Eclipse plugin, i.e. to reuse parts from the IDEA plugin. Unfortunately, there is no
common way to do this because of different models of IDEs.
Kotlin Eclipse plugin does not support incremental compilation or presentation compiler. Kotlin files are compiled using the
Kotlin compiler in KotlinCompiler.compileKotlinFiles
.
Kotlin plugin uses the Eclipse builder concept to track changes and compile Kotlin files if needed. For a usual change in project,
Kotlin builder only updates light classes. Then, if project is being built to launch the application (KotlinBuilder.isBuildingForLaunch
),
Kotlin builder compiles Kotlin files (in KotlinBuilder.build
).
Therefore, Kotlin builder should always precede Java builder.
There is a Kotlin nature to mark Kotlin projects.
Kotlin Eclipse plugin is using the standard debugger for Java in Eclipse.
For example, there are KotlinToggleBreakpointAdapter
and KotlinRunToLineAdapter
adapters to add breakpoint to a specific line and to support action "run to cursor".
In order to analyse files and use compiler API, KotlinEnvironment
have to be configured. Basically, KotlinEnvironment
is created for each project in Eclipse and maps external (from Eclipse) project model to the internal one.
Also, it registers various services and configures dependencies, see KotlinEnvironment.configureClasspath
.
Important note: if classpath was changed, corresponding Kotlin environment should be recreated.
There are PSI and Kt elements that are basically represent concrete syntax tree of Kotlin program. (The term "PSI" is used in IntelliJ IDEA to refer to the syntax tree, and stands for "Program Structure Interface".) To get the parsed version of a source file
KotlinPsiManager
should be used. Note that it caches last version of KtFile
, so to get actual KtFile
, source code of file can be
explicitly passed to the getKotlinFileIfExist
method, or you can use commitFile
to reparse and cache changed file.
Let's consider how "Remove explicit type" quick assist works. This quick assist removes explicitly written type reference for property,
function and loop parameter, i.e. it converts val s: String = "value"
to val s = "value"
.
First of all, when user invokes quick assist on some element (ctrl+1
), method
KotlinQuickAssist.isApplicable
is called. isApplicable
method obtains current PSI element (getActiveElement
),
it gets PSI file and then calls findElementAt
to get concrete element at specific offset.
Once we get active PSI element, we pass it to our quick assists and check for theirs applicability.
KotlinRemoveExplicitTypeAssistProposal.isApplicable
checks that active PSI element is actually a property, function or loop parameter with a type reference.
In method KotlinRemoveExplicitTypeAssistProposal.apply
quick assist is executed and removes corresponding type reference.
This and other quick assists and actions rely completely on the knowledge of CST for Kotlin. In order to make it easier, there is an action "View Psi Structure for Current File" in the context menu for Kotlin file, which can be used to examine structure of the Kotlin CST.
Many features in IDE requires more deep knowledge of the Kotlin compiler implementation. For example, KotlinLineAnnotationsReconciler
is used to show diagnostics from the compiler. It uses concept of ReconcilingStrategy
and runs after each change in active file,
when analysis results for the file will be ready and cached. The main line there is KotlinAnalyzer.analyzeFile(file)...
, which returns
analysis results and can be used to get diagnostics from the compiler.
Kotlin compiler uses map called BindingContext
to contain all types and internal representations (descriptors) for expressions in program.
To get binding context one can use KotlinAnalyzer.analyzeFile(file).bindingContext
.
See KotlinSemanticHighlighter
for an example of the compiler analysis use. There are several methods in KotlinSemanticHighlightingVisitor
,
which uses binding context to obtain information either from a declaration (visitProperty
),
or from a reference (visitSimpleNameExpression
).