diff --git a/annotation-processors/.gitignore b/annotation-processors/.gitignore new file mode 100644 index 000000000..b63da4551 --- /dev/null +++ b/annotation-processors/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/annotation-processors/build.gradle.kts b/annotation-processors/build.gradle.kts new file mode 100644 index 000000000..8f0b8ac51 --- /dev/null +++ b/annotation-processors/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +plugins { + kotlin("jvm") +} + +group = "com.intellij" +version = "SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.google.devtools.ksp:symbol-processing-api:1.8.0-1.0.8") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.0") +} \ No newline at end of file diff --git a/annotation-processors/src/main/kotlin/com/intellij/vim/FileWriter.kt b/annotation-processors/src/main/kotlin/com/intellij/vim/FileWriter.kt new file mode 100644 index 000000000..550a1f9ff --- /dev/null +++ b/annotation-processors/src/main/kotlin/com/intellij/vim/FileWriter.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.intellij.vim + +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import java.io.File + +class FileWriter { + fun generateResourceFile(fileName: String, content: String, environment: SymbolProcessorEnvironment) { + val resourcesDir = environment.options["resourcesDir"] + val file = File("$resourcesDir/$fileName") + file.writeText(content) + } +} \ No newline at end of file diff --git a/annotation-processors/src/main/kotlin/com/intellij/vim/annotations/VimscriptFunction.kt b/annotation-processors/src/main/kotlin/com/intellij/vim/annotations/VimscriptFunction.kt new file mode 100644 index 000000000..43045e530 --- /dev/null +++ b/annotation-processors/src/main/kotlin/com/intellij/vim/annotations/VimscriptFunction.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.intellij.vim.annotations + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class VimscriptFunction(val name: String) \ No newline at end of file diff --git a/annotation-processors/src/main/kotlin/com/intellij/vim/model/LazyInstance.kt b/annotation-processors/src/main/kotlin/com/intellij/vim/model/LazyInstance.kt new file mode 100644 index 000000000..d900a88c3 --- /dev/null +++ b/annotation-processors/src/main/kotlin/com/intellij/vim/model/LazyInstance.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.intellij.vim.model + +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType + +open class LazyInstance<T>(private val className: String, private val classLoader: ClassLoader) { + val instance: T by lazy { + val aClass = classLoader.loadClass(className) + val lookup = MethodHandles.privateLookupIn(aClass, MethodHandles.lookup()) + val instance = lookup.findConstructor(aClass, MethodType.methodType(Void.TYPE)).invoke() + @Suppress("UNCHECKED_CAST") + instance as T + } +} \ No newline at end of file diff --git a/annotation-processors/src/main/kotlin/com/intellij/vim/processors/VimscriptFunctionProcessor.kt b/annotation-processors/src/main/kotlin/com/intellij/vim/processors/VimscriptFunctionProcessor.kt new file mode 100644 index 000000000..f577c65e2 --- /dev/null +++ b/annotation-processors/src/main/kotlin/com/intellij/vim/processors/VimscriptFunctionProcessor.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.intellij.vim.processors + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getAnnotationsByType +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSFile +import com.google.devtools.ksp.symbol.KSVisitorVoid +import com.intellij.vim.FileWriter +import com.intellij.vim.annotations.VimscriptFunction + +class VimscriptFunctionProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor { + private val visitor = VimscriptFunctionVisitor() + private val writer = FileWriter() + private val nameToFunction = mutableMapOf<String, KSClassDeclaration>() + + + override fun process(resolver: Resolver): List<KSAnnotated> { + resolver.getAllFiles().forEach { it.accept(visitor, Unit) } + writer.generateResourceFile("VimscriptFunctions.yaml", generateFunctionDict(), environment) + return emptyList() + } + + private fun generateFunctionDict(): String { + val mapper = YAMLMapper() + val dictToWrite: Map<String, String> = nameToFunction + .map { it.key to it.value.qualifiedName!!.asString() } + .toMap() + return mapper.writeValueAsString(dictToWrite) + } + + // todo inspection that annotation is properly used on proper classes + private inner class VimscriptFunctionVisitor : KSVisitorVoid() { + @OptIn(KspExperimental::class) + override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { + val vimscriptFunctionAnnotation = classDeclaration.getAnnotationsByType(VimscriptFunction::class).firstOrNull() ?: return + val functionName = vimscriptFunctionAnnotation.name + nameToFunction[functionName] = classDeclaration + } + + override fun visitFile(file: KSFile, data: Unit) { + file.declarations.forEach { it.accept(this, Unit) } + } + } +} + diff --git a/annotation-processors/src/main/kotlin/com/intellij/vim/providers/VimscriptFunctionProcessorProvider.kt b/annotation-processors/src/main/kotlin/com/intellij/vim/providers/VimscriptFunctionProcessorProvider.kt new file mode 100644 index 000000000..81b19390e --- /dev/null +++ b/annotation-processors/src/main/kotlin/com/intellij/vim/providers/VimscriptFunctionProcessorProvider.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.intellij.vim.providers + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.intellij.vim.processors.VimscriptFunctionProcessor + +public class VimscriptFunctionProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return VimscriptFunctionProcessor(environment) + } +} \ No newline at end of file diff --git a/annotation-processors/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/annotation-processors/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 000000000..aa17eb85c --- /dev/null +++ b/annotation-processors/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +com.intellij.vim.providers.VimscriptFunctionProcessorProvider \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 41858e268..ed2891bd8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,4 +11,5 @@ pluginManagement { rootProject.name = 'IdeaVIM' include 'vim-engine' include 'scripts' +include 'annotation-processors' diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimscriptFunctionService.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimscriptFunctionService.kt index 3938eb40a..218dd8882 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimscriptFunctionService.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimscriptFunctionService.kt @@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.api import com.maddyhome.idea.vim.vimscript.model.VimLContext import com.maddyhome.idea.vim.vimscript.model.expressions.Scope import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler +import com.maddyhome.idea.vim.vimscript.model.functions.LazyVimscriptFunction import com.maddyhome.idea.vim.vimscript.model.statements.FunctionDeclaration public interface VimscriptFunctionService { @@ -22,5 +23,5 @@ public interface VimscriptFunctionService { public fun getUserDefinedFunction(scope: Scope?, name: String, vimContext: VimLContext): FunctionDeclaration? public fun getBuiltInFunction(name: String): FunctionHandler? public fun registerHandlers() - public fun addHandler(handlerHolder: Any) + public fun addHandler(handler: LazyVimscriptFunction) } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/functions/LazyVimscriptFunction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/functions/LazyVimscriptFunction.kt new file mode 100644 index 000000000..7189f9ed3 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/functions/LazyVimscriptFunction.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.vimscript.model.functions + +import com.intellij.vim.model.LazyInstance + +public class LazyVimscriptFunction(public val name: String, className: String, classLoader: ClassLoader): + LazyInstance<FunctionHandler>(className, classLoader) \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/functions/VimscriptFunctionProvider.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/functions/VimscriptFunctionProvider.kt new file mode 100644 index 000000000..985cfc10a --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/functions/VimscriptFunctionProvider.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.vimscript.model.functions + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper +import java.io.InputStream +import javax.swing.InputMap + +public interface VimscriptFunctionProvider { + public val functionListFile: InputStream + + public fun getFunctions(): Collection<LazyVimscriptFunction> { + val mapper = YAMLMapper() + val classLoader = this.javaClass.classLoader + val typeReference = object : TypeReference<HashMap<String, String>>() {} + val functionDict = mapper.readValue(functionListFile, typeReference) + return functionDict.map { LazyVimscriptFunction(it.key, it.value, classLoader) } + } +} \ No newline at end of file