dkandalov / live-plugin

IntelliJ plugin for writing IntelliJ plugins at runtime ⚡️
https://plugins.jetbrains.com/plugin/7282
Apache License 2.0
858 stars 67 forks source link

An idea to support multiple src files highlight and more functions #188

Closed grsky360 closed 3 days ago

grsky360 commented 2 months ago

I make a live plugin to support some useful functions when coding live plugins, hope helpful for enhance some ability of LivePlugin

  1. Make all plugins as module, to support i. multiple src file highlight (Works for both kotlin & groovy plugins) ii. full context menu of right click in "Scratches and Consoles" and project view iii. auto refresh plugin modules
  2. Enable @Grab dependencies highlight in Groovy plugin
//file:noinspection GrMethodMayBeStatic

import com.intellij.openapi.application.PathManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFileEvent
import com.intellij.openapi.vfs.VirtualFileListener
import com.intellij.util.PathUtil
import groovy.io.FileType
import liveplugin.implementation.LivePlugin
import liveplugin.implementation.LivePluginPaths
import org.jetbrains.annotations.NotNull

import static liveplugin.PluginUtil.runWriteAction

moduleNamePrefix = 'live-plugins'

moduleManager = ModuleManager.getInstance(project)
root = LocalFileSystem.getInstance().findFileByIoFile(new File(pluginPath).parentFile)

classpath = [:]

classpath += [("jar://" + PathUtil.getJarPathForClass(LivePlugin.class) + "!/"): OrderRootType.SOURCES]
classpath += LivePluginPaths.INSTANCE.livePluginLibPath
    .listFiles { it.name.endsWith(".jar") }
    .collectEntries { ["jar://${it.value}!/", OrderRootType.CLASSES] }
classpath += LivePluginPaths.INSTANCE.ideJarsPath
    .listFiles { it.name.endsWith(".jar") }
    .collectEntries { ["jar://${it.value}!/", OrderRootType.CLASSES] }

classpath += scan("${System.getenv("HOME")}/.groovy/grapes").collectEntries { [it, OrderRootType.CLASSES] }

classpath += scan("${PathManager.getPreInstalledPluginsPath()}/java").collectEntries { [it, OrderRootType.CLASSES] }

def expose(pluginRoot) {
    def moduleName = moduleNamePrefix + '.' + pluginRoot.name
    def module = moduleManager.newNonPersistentModule(moduleName, "JAVA")
    def rootManager = ModuleRootManager.getInstance(module)
    def modifier = rootManager.modifiableModel
    def entry = modifier.addContentEntry(pluginRoot)
    entry.addSourceFolder(entry.file.url, false)
    modifier.inheritSdk()
    modifier.commit()
    addLibrary(module, "LivePlugin Module Dependencies", classpath)
}

def exposeAll() {
    root.children.findAll { it.isDirectory() }.each {
        expose(it)
    }
}

def dispose(pluginRoot) {
    def moduleName = moduleNamePrefix + '.' + pluginRoot.name
    moduleManager.modules.findAll { it.name == moduleName }.each {
        moduleManager.disposeModule(it)
    }
}

def disposeAll() {
    moduleManager.modules.findAll { it.name.startsWith(moduleNamePrefix + '.') }.each {
        moduleManager.disposeModule(it)
    }
}

boolean hasLibrary(Module module, String libraryName) {
    def modifiableModel = ModuleRootManager.getInstance(module).modifiableModel
    def libraryTable = modifiableModel.moduleLibraryTable
    def library = libraryTable.getLibraryByName(libraryName)
    modifiableModel.dispose()
    return library != null
}

void removeLibrary(Module module, String libraryName) {
    if (!hasLibrary(module, libraryName)) {
        return
    }
    def modifiableModel = ModuleRootManager.getInstance(module).modifiableModel
    def libraryTable = modifiableModel.moduleLibraryTable
    def library = libraryTable.getLibraryByName(libraryName)
    if (library != null) {
        libraryTable.removeLibrary(library)
    }

    modifiableModel.commit()
}

void addLibrary(Module module, String libraryName, Map<String, OrderRootType> classpath) {
    if (hasLibrary(module, libraryName)) {
        return
    }

    def modifiableModel = ModuleRootManager.getInstance(module).modifiableModel
    def libraryTable = modifiableModel.moduleLibraryTable

    def library = libraryTable.createLibrary(libraryName)
    library.modifiableModel.with {
        classpath.forEach { path, rootType ->
            it.addRoot(path, rootType)
        }
        it.commit()
    }

    def libraryOrderEntry = modifiableModel.findLibraryOrderEntry(library)
    if (libraryOrderEntry != null) {
        libraryOrderEntry.scope = DependencyScope.PROVIDED
    }
    modifiableModel.commit()
}

List<String> scan(String path) {
    def jars = []
    new File(path).eachFileRecurse(FileType.FILES) {
        if (it.name.endsWith(".jar")) {
            jars << "jar://${it.absolutePath}!/"
        }
    }
    return jars
}

def listen(VirtualFileEvent event, boolean delete) {
    def file = event.file
    if (!file.directory || file.parent != root) {
        return
    }
    if (delete) {
        dispose(file)
    } else {
        expose(file)
    }
}

runWriteAction {
    disposeAll()
    exposeAll()
}

LocalFileSystem.getInstance().addVirtualFileListener(new VirtualFileListener() {
    @Override
    void fileCreated(@NotNull VirtualFileEvent event) {
        listen(event, false)
    }

    @Override
    void fileDeleted(@NotNull VirtualFileEvent event) {
        listen(event, true)
    }
}, pluginDisposable)

Disposer.register(pluginDisposable) {
    runWriteAction {
        disposeAll()
    }
}