Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import com.intellij.ide.DataManager
- import com.intellij.ide.IdeEventQueue
- import com.intellij.notification.NotificationDisplayType
- import com.intellij.notification.NotificationGroup
- import com.intellij.notification.NotificationType
- import com.intellij.notification.Notifications
- import com.intellij.openapi.Disposable
- import com.intellij.openapi.actionSystem.PlatformDataKeys
- import com.intellij.openapi.project.Project
- import com.intellij.openapi.util.Disposer
- import com.intellij.openapi.vcs.VcsDataKeys
- import com.intellij.openapi.vfs.VirtualFile
- import com.intellij.openapi.wm.IdeFocusManager
- import com.intellij.psi.PsiDirectory
- import com.intellij.psi.PsiElement
- import com.intellij.util.ui.update.MergingUpdateQueue
- import java.lang.IllegalStateException
- import java.util.concurrent.TimeUnit
- import kotlin.streams.toList
- val notificationGroup: NotificationGroup = NotificationGroup("LivePluginDebug", NotificationDisplayType.STICKY_BALLOON, true)
- val IDE: com.intellij.ide.script.IDE = bindings.get("IDE") as com.intellij.ide.script.IDE
- val project = IDE.project!!
- // Fix Kotlin scripting console output
- fun println(s: String) {
- kotlin.io.println(s)
- IDE.print(s)
- val notification = notificationGroup.createNotification("OnFileChange", s, NotificationType.INFORMATION, null)
- Notifications.Bus.notify(notification)
- }
- fun println(s: Any?) {
- println("" + s?.toString())
- }
- private object OnFileChangeId {}
- fun deregisterQueue(name: String) {
- val mergingUpdateQueue = IDE.get("liveplugin-queue-$name") as MergingUpdateQueue?
- if (mergingUpdateQueue != null) {
- } else {
- println("No queue named $name")
- }
- }
- fun registerQueue(name: String, queue: MergingUpdateQueue) {
- deregisterQueue("liveplugin-queue-$name")
- IDE.put("liveplugin-queue-$name", queue)
- }
- inline fun <reified T> registerObject(type: String, name: String, queue: T) {
- val objectKey = "liveplugin-$type-$name"
- if (IDE.get(objectKey) != null) {
- throw IllegalStateException("Object is already registered, type: $type name: $name, object: ${IDE.get(objectKey)}")
- }
- IDE.put("liveplugin-queue-$name", queue)
- }
- inline fun <reified T> registerObject(registeredObject: RegisteredObject<T>, queue: T) {
- val objectKey = registeredObject.name
- if (IDE.get(objectKey) != null) {
- throw IllegalStateException("Object already registered, type: name: $objectKey, object: ${IDE.get(objectKey)}")
- }
- IDE.put(objectKey, queue)
- }
- inline fun <reified T> registerOrUpdateObject(registeredObject: RegisteredObject<T>, newObject: T): T? {
- val objectKey = registeredObject.name
- val oldObject = IDE.get(objectKey)
- IDE.put(objectKey, newObject)
- return oldObject as? T
- }
- inline fun <reified T> registeredObject(type: String, name: String, queue: T, makeT: () -> T): T {
- val objectKey = "liveplugin-$type-$name"
- if (IDE.get(objectKey) == null) {
- IDE.put(objectKey, makeT())
- }
- return IDE.get(objectKey) as T
- }
- inline fun <reified T> getRegisteredObject(type: String, name: String): T? {
- deregisterQueue("liveplugin-$type-$name")
- return IDE.get("liveplugin-queue-$name") as T?
- }
- inline fun <reified T> registeredObjectOnce(registeredObject: RegisteredObject<T>, makeT: () -> T): T {
- val key = registeredObject.name
- val oldObject = IDE.get(key)
- if (oldObject !is T) {
- if (oldObject != null) {
- println("Replacing ${oldObject} due to type mismatch")
- }
- IDE.put(key, makeT())
- }
- return IDE.get(key) as T
- }
- inline fun <reified T> reloadableService(version: Int, type: String, name: String, onDispose: (T) -> Unit, makeT: () -> T) {
- val key = "liveplugin-$type-$name"
- (IDE.get(key) as T)?.let {
- IDE.put(key, null)
- onDispose(it)
- }
- registerObject(type, name, makeT())
- }
- inline fun reloadableService(version: Int, reloadableService: ReloadableService, onDispose: () -> Unit, makeT: () -> Unit) {
- val registeredObject = objectKey<Unit>(reloadableService.type, reloadableService.name)
- val registeredVersion = objectKey<Int>(reloadableService.type, "${reloadableService.name}-version")
- val currentVersion = registeredVersion.fromIde()
- val key = registeredObject.name
- if (currentVersion == null || currentVersion != version) {
- if (currentVersion != null) {
- // Running a different version...
- (IDE.get(key) as Unit?)?.let {
- IDE.put(registeredVersion.name, null)
- IDE.put(key, null)
- onDispose()
- }
- }
- registerObject(registeredObject, makeT())
- registerObject(registeredVersion, version)
- }
- }
- data class RegisteredObject<T>(val name: String)
- interface NameBasedService<T> {
- fun register(id: String, impl: T, parentDisposable: Disposable? = null)
- fun dispose(id: String)
- fun reset()
- }
- typealias OnRegister<T> = (abc: String, T) -> Unit
- typealias OnDispose<T> = (abc: String) -> Unit
- interface ServiceRegistryContext<T> {
- val previousService: T?
- val newService: T?
- }
- interface ServiceInitContext<T> {
- val allServices: MutableList<T>
- val delegatedDisposable: Disposable
- }
- fun <T> makeNameBasedServiceManager(type: String, name: String, onCreate: ServiceInitContext<T>.() -> Unit): NameBasedService<T> {
- val listKey = objectKey<MutableList<T>>(type, "$name-list")
- val mapKey = objectKey<MutableMap<String, T>>(type, "$name-by-name")
- val serviceKey = ReloadableService(type, "$name-service")
- val onDispose = objectKey<OnDispose<T>>(type, "$name-on-dispose")
- val onRegister = objectKey<OnRegister<T>>(type, "$name-on-register")
- registeredObjectOnce(listKey, { mutableListOf()})
- registeredObjectOnce(mapKey, { mutableMapOf() })
- reloadableService(9, serviceKey, {
- println("Discarding service $type $name")
- }, {
- println("Running ServiceInit")
- println("Registering new onRegister")
- registerOrUpdateObject(onRegister, { id, newInstance ->
- mapKey.fromIde()?.apply {
- if (contains(id)) {
- println("Replacing $id")
- val oldInstance = get(id)!!
- listKey.applyFromIde {
- val position = indexOf(oldInstance)
- set(position, newInstance)
- put(id, newInstance)
- }
- } else if(containsValue(newInstance)) {
- throw IllegalStateException("Cannot register same instance mulitple times under different keys")
- } else {
- listKey.applyFromIde {
- println("Adding $id")
- add(newInstance)
- put(id, newInstance)
- }
- }
- }
- })
- println("Registering new dispose")
- registerOrUpdateObject(onDispose, { id ->
- mapKey.fromIde()?.apply {
- if (contains(id)) {
- println("Removing $id")
- val oldInstance = get(id)!!
- listKey.applyFromIde {
- val position = indexOf(oldInstance)
- removeAt(position)
- }
- } else {
- println("Failed to dispose")
- }
- }
- })
- // Clean up
- mapKey.fromIde()?.let { byName ->
- listKey.fromIde()?.retainAll(byName.values)
- byName.values.retainAll(listKey.fromIde()!!)
- }
- onCreate(object: ServiceInitContext<T> {
- override val allServices: MutableList<T>
- get() = listKey.fromIde()!!
- override val delegatedDisposable: Disposable
- get() = Disposable {
- println("Does not yet work")
- }
- })
- })
- // val oldRegister = onRegister.fromIde()
- // val oldOnDispose = oldOnDispose.fromIde()
- return object : NameBasedService<T> {
- override fun reset() {
- listKey.fromIde()?.apply { clear() }
- mapKey.fromIde()?.apply { clear() }
- }
- override fun register(id: String, impl: T, parentDisposable: Disposable?) {
- if (parentDisposable != null) {
- Disposer.register(parentDisposable, Disposable { onDispose.fromIde()?.invoke(id) })
- }
- onRegister.fromIde()?.invoke(id, impl)
- }
- override fun dispose(id: String) {
- onDispose.fromIde()?.invoke(id)
- }
- }
- }
- val ActivityListenerService = makeNameBasedServiceManager<Runnable>("activitylistener", "service") {
- IdeEventQueue.getInstance().addActivityListener(Runnable {
- allServices.forEach {
- it.run()
- }
- }, delegatedDisposable)
- }
- data class ReloadableService(val type: String, val name: String)
- fun <T> objectKey(type: String, name: String): RegisteredObject<T> {
- return RegisteredObject("liveplugin-$type-$name")
- }
- val ActivityListenersByName = objectKey<MutableMap<String, Runnable>>("activitylistener", "delegates-by-name")
- val ActivityListeners = objectKey<MutableList<Runnable>>("activitylistener", "delegate-list")
- inline fun <reified T> RegisteredObject<T>.with(block: (T) -> Unit) {
- (IDE.get(this.name) as T?)?.let(block)
- }
- inline fun <reified T> RegisteredObject<T>.applyFromIde(block: T.() -> Unit) {
- (IDE.get(this.name) as T?)?.apply(block)
- }
- inline fun <reified T> RegisteredObject<T>.fromIde(): T? {
- return (IDE.get(this.name) as T)
- }
- inline fun <reified T> RegisteredObject<T>.fromIdeAsAny(): Any? {
- return IDE.get(this.name)
- }
- inline fun <reified T> RegisteredObject<T>.fromIdeIfTypeMatches(): T? {
- return (IDE.get(this.name) as? T)
- }
- interface OnSelectedFilesChange {
- val selectedFiles: List<VirtualFile>
- }
- fun onFileChange(delay: Int, timeUnit: TimeUnit, project: Project, onSelectedFilesChange: OnSelectedFilesChange.() -> Unit, parentDisposable: Disposable? = null) {
- var previousResult: Set<VirtualFile>? = null
- val seenTypes = mutableSetOf<Class<*>>()
- class MyUpdate(identity: OnFileChangeId) : com.intellij.util.ui.update.Update(identity) {
- override fun run() {
- IdeFocusManager.getInstance(project).doWhenFocusSettlesDown {
- // println("Focus settled down")
- fun getListOfFiles(): List<VirtualFile>? {
- val focusOwner = IdeFocusManager.getInstance(project).focusOwner
- if (focusOwner != null) {
- val dataContext = DataManager.getInstance().getDataContext(focusOwner)
- // println(dataContext);
- val selectedItems = PlatformDataKeys.SELECTED_ITEMS.getData(dataContext)
- if (selectedItems != null) {
- selectedItems.forEach {
- if (seenTypes.add(it::class.java)) {
- println(">>> NEW TYPE: ${it::class.java.canonicalName}")
- }
- }
- // println(Arrays.toString(selectedItems))
- val directories = selectedItems.filterIsInstance(PsiDirectory::class.java)
- val dirFiles = directories.map { it.virtualFile }
- val result = (selectedItems.filterIsInstance(PsiElement::class.java).mapNotNull { it.containingFile?.virtualFile } + dirFiles)
- return result
- }
- println("PsiElement")
- println(PlatformDataKeys.PSI_ELEMENT.getData(dataContext))
- println("PlatformDataKeys.SELECTED_ITEM")
- println(PlatformDataKeys.SELECTED_ITEM.getData(dataContext))
- println("VcsDataKeys.VIRTUAL_FILE_STREAM")
- println(VcsDataKeys.VIRTUAL_FILE_STREAM.getData(dataContext)?.toList())
- println("End data context")
- }
- return null
- }
- val listOfFiles = getListOfFiles()
- val setOfFiles = listOfFiles?.toSet()
- if (previousResult != setOfFiles) {
- previousResult = setOfFiles
- if (setOfFiles != null) {
- onSelectedFilesChange(object: OnSelectedFilesChange {
- override val selectedFiles: List<VirtualFile>
- get() = listOfFiles
- })
- } else {
- println("No files detected")
- }
- }
- }
- }
- }
- registeredObjectOnce(ActivityListenersByName) {
- mutableMapOf()
- }
- registeredObjectOnce(ActivityListeners) {
- mutableListOf()
- }
- // TODO IDEA I could integrate the mergingTimeSpan into the key name.. then it would work seamlessly
- val mergingTimeSpan = timeUnit.toMillis(delay.toLong()).toInt()
- val queueKey = objectKey<MergingUpdateQueue>("queue", "onFileChange-$mergingTimeSpan")
- registeredObjectOnce(queueKey) {
- val queue = object : MergingUpdateQueue("livePlugin:onFileChange", mergingTimeSpan, true, null) {}
- queue
- }.let { queue ->
- // If queue exists already, we need to change it...
- // TODO This currently applies to all onFileChange listeners, it shouldn't otherwise the API shouldn't expose that parameter
- queue.setMergingTimeSpan(mergingTimeSpan)
- }
- ActivityListenerService.register("onFileChange", Runnable {
- queueKey.fromIde()?.queue(MyUpdate(OnFileChangeId))
- })
- }
- fun main() {
- // ActivityListenerService.reset()
- println("Executing onFileChange")
- onFileChange(1, TimeUnit.SECONDS, project, {
- println("Set of selected files has changed;")
- selectedFiles.forEach {
- println(it)
- }
- })
- println("Executed onFIleChange")
- }
Add Comment
Please, Sign In to add comment