Skip to content

Commit

Permalink
Merge branch 'feature/IJMP-1533' into 'release/v1.2.0-221'
Browse files Browse the repository at this point in the history
IJMP-1533: close files in editor when deleting related file

See merge request ijmp/for-mainframe!488
  • Loading branch information
Dzianis Lisiankou committed Mar 5, 2024
2 parents 3203c31 + 8a0d998 commit 981bb9e
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,10 @@ interface ContentSynchronizer {
*/
fun isFileUploadNeeded(syncProvider: SyncProvider): Boolean

/**
* Marks file as not needed for synchronisation until the next time file is modified.
* @param syncProvider instance of [SyncProvider] class that contains file to mark.
*/
fun markAsNotNeededForSync(syncProvider: SyncProvider)

}
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,11 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
override fun isFileUploadNeeded(syncProvider: SyncProvider): Boolean {
return needToUpload.firstOrNull { syncProvider == it } != null
}

/**
* Base implementation of [ContentSynchronizer.markAsNotNeededForSync] method for each content synchronizer.
*/
override fun markAsNotNeededForSync(syncProvider: SyncProvider) {
needToUpload.remove(syncProvider)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import com.intellij.ide.dnd.aware.DnDAwareTree
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.impl.text.EditorHighlighterUpdater
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.BulkFileListener
Expand Down Expand Up @@ -71,7 +73,7 @@ inline fun <reified ExplorerView: ExplorerTreeView<*, *, *>> AnActionEvent.getEx
abstract class ExplorerTreeView<Connection: ConnectionConfigBase, U : WorkingSet<Connection, *>, UnitConfig : EntityWithUuid>
(
val explorer: Explorer<Connection, U>,
project: Project,
private val project: Project,
parentDisposable: Disposable,
private val contextMenu: ActionGroup,
rootNodeProvider: (explorer: Explorer<Connection, U>, project: Project, treeStructure: ExplorerTreeStructureBase) -> ExplorerTreeNode<Connection, *>,
Expand Down Expand Up @@ -227,6 +229,14 @@ abstract class ExplorerTreeView<Connection: ConnectionConfigBase, U : WorkingSet
componentManager = ApplicationManager.getApplication(),
topic = MFVirtualFileSystem.MF_VFS_CHANGES_TOPIC,
handler = object : MFBulkFileListener {
override fun before(events: List<VFileEvent>) {
// listens for virtual file delete events and
// closes files opened in editor if file to be deleted is an ancestor of these files
events.filterIsInstance<VFileDeleteEvent>().forEach {
closeChildrenInEditor(it.file)
}
}

override fun after(events: List<VFileEvent>) {
events
.mapNotNull {
Expand Down Expand Up @@ -374,5 +384,20 @@ abstract class ExplorerTreeView<Connection: ConnectionConfigBase, U : WorkingSet

}

/** Close files opened in editor if selected file is an ancestor of these files */
fun closeChildrenInEditor(selectedFile: VirtualFile) {
val fileEditorManager = FileEditorManager.getInstance(project)
val openFiles = fileEditorManager.openFiles
openFiles.forEach { openFile ->
if (VfsUtilCore.isAncestor(selectedFile, openFile, false)) {
val contentSynchronizer = service<DataOpsManager>().getContentSynchronizer(openFile)
val syncProvider = DocumentedSyncProvider(openFile)
contentSynchronizer?.markAsNotNeededForSync(syncProvider)
runWriteActionInEdtAndWait {
fileEditorManager.closeFile(openFile)
}
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright IBA Group 2020
*/

package eu.ibagroup.formainframe.explorer.ui

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import eu.ibagroup.formainframe.config.connect.ConnectionConfig
import eu.ibagroup.formainframe.dataops.DataOpsManager
import eu.ibagroup.formainframe.dataops.content.synchronizer.ContentSynchronizer
import eu.ibagroup.formainframe.explorer.*
import eu.ibagroup.formainframe.testutils.WithApplicationShouldSpec
import eu.ibagroup.formainframe.testutils.testServiceImpl.TestDataOpsManagerImpl
import eu.ibagroup.formainframe.utils.service
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.shouldBe
import io.mockk.*
import kotlin.reflect.KFunction

class ExplorerTreeViewTestSpec: WithApplicationShouldSpec({
afterSpec {
clearAllMocks()
}
context("Explorer module: ui/ExplorerTreeView") {

lateinit var fileExplorerView: ExplorerTreeView<*, *, *>

val explorerMock = mockk<Explorer<ConnectionConfig, FilesWorkingSet>>()
every { explorerMock.componentManager } returns ApplicationManager.getApplication()

val openFilesMock = arrayOf<VirtualFile>(mockk(), mockk())
var closedFileSize = 0

val dataOpsManagerService =
ApplicationManager.getApplication().service<DataOpsManager>() as TestDataOpsManagerImpl

val contentSynchronizerMock = mockk<ContentSynchronizer>()
every { contentSynchronizerMock.markAsNotNeededForSync(any()) } returns Unit

beforeEach {
mockkConstructor(CommonExplorerTreeStructure::class)
every { anyConstructed<CommonExplorerTreeStructure<*>>().rootElement } returns Unit

fileExplorerView = spyk(
FileExplorerView(
explorerMock,
mockk(),
mockk(),
mockk(),
{ _, _, _ -> mockk() }
) { }
)

closedFileSize = 0
mockkStatic(FileEditorManager::getInstance)
every { FileEditorManager.getInstance(any()) } returns object : TestFileEditorManager() {
override fun getOpenFiles(): Array<VirtualFile> {
return openFilesMock
}

override fun closeFile(file: VirtualFile) {
closedFileSize++
}
}

val isAncestorRef: (VirtualFile, VirtualFile, Boolean) -> Boolean = VfsUtilCore::isAncestor
mockkStatic(isAncestorRef as KFunction<*>)
every { VfsUtilCore.isAncestor(any<VirtualFile>(), any<VirtualFile>(), any<Boolean>()) } returns true

dataOpsManagerService.testInstance = object : TestDataOpsManagerImpl(explorerMock.componentManager) {
override fun getContentSynchronizer(file: VirtualFile): ContentSynchronizer {
return contentSynchronizerMock
}
}
}

afterEach {
unmockkAll()
}

// closeChildrenInEditor
should("close files in editor if selected file is their ancestor") {
fileExplorerView.closeChildrenInEditor(mockk())

assertSoftly { closedFileSize shouldBe openFilesMock.size }
}
should("don't close files in editor if selected file is not their ancestor") {
every { VfsUtilCore.isAncestor(any<VirtualFile>(), any<VirtualFile>(), any<Boolean>()) } returns false

fileExplorerView.closeChildrenInEditor(mockk())

assertSoftly { closedFileSize shouldBe 0 }
}
}
})

0 comments on commit 981bb9e

Please sign in to comment.