Skip to content

Latest commit

 

History

History
158 lines (113 loc) · 15.4 KB

dev-documentation.md

File metadata and controls

158 lines (113 loc) · 15.4 KB

Kotlin Eclipse Plugin Developer Documentation

Kotlin Eclipse plugins overview

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 on m2e 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

Interoperability with JDT

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.

Light classes

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.

Light classes to Kotlin source code

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.

Editor Actions

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)

Organize imports action example

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.

Code reuse from the IDEA plugin

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 compilation and launch

Kotlin Eclipse plugin does not support incremental compilation or presentation compiler. Kotlin files are compiled using the Kotlin compiler in KotlinCompiler.compileKotlinFiles.

Kotlin Builder

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.

Kotlin Nature

There is a Kotlin nature to mark Kotlin projects.

Kotlin Debugger

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".

Kotlin environment and project analysis

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.

Kotlin parsing

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.

"Remove explicit type" quick assist example

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.

Kotlin analyzer

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).