Skip to content

Commit

Permalink
Non-Blocking mark occurences
Browse files Browse the repository at this point in the history
Add delay to runJob method, make mark occurences cancellable
  • Loading branch information
noemus committed Mar 28, 2018
1 parent 726d8be commit 1261e3b
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,22 @@ interface PsiFilesStorage {
fun removeFile(file: IFile)
}

private fun parseFile(file: IFile): KtFile =
KotlinPsiManager.parseFile(file) ?: throw IllegalStateException("Can't parse file $file")

private fun parseIfNotSame(file: IFile, ktFile: KtFile?, expectedSourceCode: String): KtFile =
if (ktFile != null && ktFile.getText().equals(StringUtilRt.convertLineSeparators(expectedSourceCode))) {
ktFile
}
else parseFile(file)

private class ScriptsFilesStorage : PsiFilesStorage {
private val cachedKtFiles = ConcurrentHashMap<IFile, KtFile>()

override fun getPsiFile(eclipseFile: IFile): KtFile {
assert(isApplicable(eclipseFile)) { "$eclipseFile is not applicable for Kotlin scripts storage" }

return cachedKtFiles.getOrPut(eclipseFile) { KotlinPsiManager.parseFile(eclipseFile)!! }
return cachedKtFiles.getOrPut(eclipseFile) { parseFile(eclipseFile) }
}

@Synchronized
Expand Down Expand Up @@ -94,109 +103,113 @@ private class ProjectSourceFiles : PsiFilesStorage {
fun isKotlinFile(file: IFile): Boolean = KotlinFileType.INSTANCE.getDefaultExtension() == file.fileExtension
}

private val projectFiles = hashMapOf<IProject, HashSet<IFile>>()
private val cachedKtFiles = hashMapOf<IFile, KtFile>()
private val mapOperationLock = Any()
private val projectFiles = ConcurrentHashMap<IProject, HashSet<IFile>>()
private val cachedKtFiles = ConcurrentHashMap<IFile, KtFile>()

override fun getPsiFile(eclipseFile: IFile): KtFile {
synchronized (mapOperationLock) {
updateProjectPsiSourcesIfNeeded(eclipseFile.getProject())
updateProjectPsiSourcesIfNeeded(eclipseFile.getProject())

assert(existsInProjectSources(eclipseFile), { "File(" + eclipseFile.getName() + ") does not contain in the psiFiles" })
assert(existsInProjectSources(eclipseFile), { "File(" + eclipseFile.getName() + ") does not contain in the psiFiles" })

return cachedKtFiles.getOrPut(eclipseFile) {
KotlinPsiManager.parseFile(eclipseFile) ?: throw IllegalStateException("Can't parse file $eclipseFile")
}
}
return cachedKtFiles.getOrPut(eclipseFile) { parseFile(eclipseFile) }
}

override fun getPsiFile(file: IFile, expectedSourceCode: String): KtFile {
synchronized (mapOperationLock) {
updatePsiFile(file, expectedSourceCode)
return getPsiFile(file)
}
updateProjectPsiSourcesIfNeeded(file.getProject())

assert(existsInProjectSources(file), { "File(" + file.getName() + ") does not contain in the psiFiles" })

return cachedKtFiles.compute(file) { _,cachedKtFile ->
parseIfNotSame(file, cachedKtFile, expectedSourceCode)
}!!
}

override fun isApplicable(file: IFile): Boolean = existsInProjectSources(file)

fun existsInProjectSources(file: IFile): Boolean {
synchronized (mapOperationLock) {
val project = file.getProject() ?: return false

updateProjectPsiSourcesIfNeeded(project)

val files = projectFiles[project]
return if (files != null) files.contains(file) else false
val project = file.getProject() ?: return false

val exists = projectFiles.compute(project) { _,origFiles ->
origFiles ?: computeProjectPsiSources(project, origFiles)
}
?.contains(file) ?: false

return exists
}

fun containsProject(project: IProject): Boolean {
return synchronized (mapOperationLock) {
projectFiles.containsKey(project)
}
return projectFiles.containsKey(project)
}

fun getFilesByProject(project: IProject): Set<IFile> {
synchronized (mapOperationLock) {
updateProjectPsiSourcesIfNeeded(project)

if (projectFiles.containsKey(project)) {
return Collections.unmodifiableSet(projectFiles[project])
}

return emptySet()
val files = projectFiles.compute(project) { _,origFiles ->
origFiles ?: computeProjectPsiSources(project, origFiles)
}

return if (files != null)
Collections.unmodifiableSet(files)
else
emptySet()
}

fun addFile(file: IFile) {
synchronized (mapOperationLock) {
assert(KotlinNature.hasKotlinNature(file.getProject()),
{ "Project (" + file.getProject().getName() + ") does not have Kotlin nature" })
assert(!existsInProjectSources(file), { "File(" + file.getName() + ") is already added" })
projectFiles
.getOrPut(file.project) { hashSetOf<IFile>() }
.add(file)
assert(KotlinNature.hasKotlinNature(file.getProject()),
{ "Project (" + file.getProject().getName() + ") does not have Kotlin nature" })

assert(!existsInProjectSources(file), { "File(" + file.getName() + ") is already added" })

projectFiles.compute(file.project) { _,origFiles ->
val files = origFiles ?: hashSetOf<IFile>()
files.add(file)
files
}
}

override fun removeFile(file: IFile) {
synchronized (mapOperationLock) {
assert(existsInProjectSources(file), { "File(" + file.getName() + ") does not contain in the psiFiles" })
assert(existsInProjectSources(file), { "File(" + file.getName() + ") does not contain in the psiFiles" })

projectFiles.compute(file.project) { _,origFiles ->
cachedKtFiles.remove(file)
projectFiles.get(file.project)?.remove(file)
}
origFiles?.remove(file)
origFiles
}
}

fun addProject(project: IProject) {
synchronized (mapOperationLock) {
if (ProjectUtils.isAccessibleKotlinProject(project)) {
addFilesToParse(JavaCore.create(project))
}
projectFiles.compute(project) { _,origFiles ->
computeProjectPsiSources(project, origFiles)
}
}

fun computeProjectPsiSources(project: IProject, origFiles: HashSet<IFile>? = null) =
if (ProjectUtils.isAccessibleKotlinProject(project)) {
getFilesToParse(JavaCore.create(project))
}
else origFiles

fun removeProject(project: IProject) {
synchronized (mapOperationLock) {
val files = getFilesByProject(project)

projectFiles.remove(project)
for (file in files) {
projectFiles.computeIfPresent(project) { _,origFiles ->
for (file in origFiles) {
cachedKtFiles.remove(file)
}
null
}
}

fun addFilesToParse(javaProject: IJavaProject) {
projectFiles.compute(javaProject.getProject()) { _,_ ->
getFilesToParse(javaProject)
}
}

fun getFilesToParse(javaProject: IJavaProject): HashSet<IFile> {
val files = hashSetOf<IFile>()

try {
projectFiles.put(javaProject.getProject(), HashSet<IFile>())

for (sourceFolder in javaProject.sourceFolders) {
sourceFolder.getResource().accept { resource ->
if (resource is IFile && isKotlinFile(resource)) {
addFile(resource)
files.add(resource)
}

true
Expand All @@ -205,15 +218,13 @@ private class ProjectSourceFiles : PsiFilesStorage {
} catch (e: CoreException) {
KotlinLogger.logError(e)
}

return files;
}

fun updateProjectPsiSourcesIfNeeded(project: IProject) {
if (projectFiles.containsKey(project)) {
return
}

if (ProjectUtils.isAccessibleKotlinProject(project)) {
updateProjectPsiSources(project, IResourceDelta.ADDED)
projectFiles.computeIfAbsent(project) { _ ->
computeProjectPsiSources(project)!!
}
}

Expand All @@ -227,20 +238,6 @@ private class ProjectSourceFiles : PsiFilesStorage {
fun invalidateProjectSourceFiles() {
cachedKtFiles.clear()
}

private fun updatePsiFile(file: IFile, sourceCode: String) {
val sourceCodeWithouCR = StringUtilRt.convertLineSeparators(sourceCode)

synchronized (mapOperationLock) {
assert(existsInProjectSources(file), { "File(" + file.getName() + ") does not contain in the psiFiles" })

val currentParsedFile = getPsiFile(file)
if (!currentParsedFile.getText().equals(sourceCodeWithouCR)) {
val jetFile = KotlinPsiManager.parseText(sourceCodeWithouCR, file)!!
cachedKtFiles.put(file, jetFile)
}
}
}
}

object KotlinPsiManager {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ fun isConfigurationMissing(project: IProject): Boolean {
}
}

fun runJob(name: String, priority: Int = Job.LONG, action: (IProgressMonitor) -> IStatus) {
runJob(name, priority, null, action, {})
fun runJob(name: String, priority: Int = Job.LONG, delay: Long = 0, action: (IProgressMonitor) -> IStatus) {
runJob(name, priority, delay, null, action, {})
}

fun runJob(
name: String,
priority: Int = Job.LONG,
delay: Long = 0,
jobFamily: Any? = null,
action: (IProgressMonitor) -> IStatus,
postTask: (IJobChangeEvent) -> Unit
Expand All @@ -102,7 +103,7 @@ fun runJob(
}
})

job.schedule()
job.schedule(delay)

return job
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package org.jetbrains.kotlin.ui.editors.occurrences

import org.eclipse.core.resources.IFile
import org.eclipse.core.runtime.NullProgressMonitor
import org.eclipse.core.runtime.IProgressMonitor
import org.eclipse.core.runtime.Status
import org.eclipse.core.runtime.jobs.Job
import org.eclipse.jdt.core.search.IJavaSearchConstants
Expand All @@ -29,59 +29,79 @@ import org.eclipse.search.ui.text.Match
import org.eclipse.ui.ISelectionListener
import org.eclipse.ui.IWorkbenchPart
import org.jetbrains.kotlin.core.builder.KotlinPsiManager
import org.jetbrains.kotlin.core.model.runJob
import org.jetbrains.kotlin.core.references.resolveToSourceDeclaration
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.eclipse.ui.utils.EditorUtil
import org.jetbrains.kotlin.eclipse.ui.utils.getTextDocumentOffset
import org.jetbrains.kotlin.core.model.runJob
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.ui.commands.findReferences.KotlinScopedQuerySpecification
import org.jetbrains.kotlin.ui.editors.KotlinCommonEditor
import org.jetbrains.kotlin.ui.editors.KotlinEditor
import org.jetbrains.kotlin.ui.editors.KotlinFileEditor
import org.jetbrains.kotlin.ui.editors.annotations.AnnotationManager
import org.jetbrains.kotlin.ui.refactorings.rename.getLengthOfIdentifier
import org.jetbrains.kotlin.ui.search.KotlinElementMatch
import org.jetbrains.kotlin.ui.search.KotlinQueryParticipant
import org.jetbrains.kotlin.ui.search.getContainingClassOrObjectForConstructor
import java.util.concurrent.atomic.AtomicReference

public class KotlinMarkOccurrences(val kotlinEditor: KotlinCommonEditor) : ISelectionListener {
companion object {
private val ANNOTATION_TYPE = "org.eclipse.jdt.ui.occurrences"
}

private val currentJob = AtomicReference<Job?>(null)

override fun selectionChanged(part: IWorkbenchPart, selection: ISelection) {
if (!kotlinEditor.isActive()) return

runJob("Update occurrence annotations", Job.DECORATE) {
if (part is KotlinCommonEditor && selection is ITextSelection) {
val file = part.eclipseFile
if (file == null || !file.exists()) return@runJob Status.CANCEL_STATUS

val document = part.getDocumentSafely()
if (document == null) return@runJob Status.CANCEL_STATUS

KotlinPsiManager.getKotlinFileIfExist(file, document.get())
if (part is KotlinCommonEditor && selection is ITextSelection) {
currentJob.updateAndGet {
it?.cancel()
runOccurencesJob(part, selection)
}
}
}

private fun runOccurencesJob(part: KotlinCommonEditor, selection: ITextSelection) =
runJob("Update occurrence annotations", Job.DECORATE, 300) { monitor ->

val file = part.eclipseFile
if (file == null || !file.exists()) {
return@runJob Status.CANCEL_STATUS
}

val document = part.getDocumentSafely()
if (document == null) return@runJob Status.CANCEL_STATUS

KotlinPsiManager.getKotlinFileIfExist(file, document.get())

if (monitor.isCanceled) {
return@runJob Status.CANCEL_STATUS
}

val ktElement = EditorUtil.getJetElement(part, selection.getOffset())
if (ktElement == null) {
return@runJob Status.CANCEL_STATUS
}

val occurrences = findOccurrences(part, ktElement, file, monitor)

val ktElement = EditorUtil.getJetElement(part, selection.getOffset())
if (ktElement == null) {
return@runJob Status.CANCEL_STATUS
}
if (monitor.isCanceled) {
return@runJob Status.CANCEL_STATUS
}

val occurrences = findOccurrences(part, ktElement, file)
updateOccurrences(part, occurrences)
}

Status.OK_STATUS
}
updateOccurrences(part, occurrences)

Status.OK_STATUS
}

private fun updateOccurrences(editor: KotlinEditor, occurrences: List<Position>) {
val annotationMap = occurrences.associateBy { Annotation(ANNOTATION_TYPE, false, null) }
AnnotationManager.updateAnnotations(editor, annotationMap, ANNOTATION_TYPE)
}

private fun findOccurrences(editor: KotlinCommonEditor, jetElement: KtElement, file: IFile): List<Position> {
private fun findOccurrences(editor: KotlinCommonEditor, jetElement: KtElement, file: IFile, monitor: IProgressMonitor?): List<Position> {
val sourceElements = jetElement.resolveToSourceDeclaration()
if (sourceElements.isEmpty()) return emptyList()

Expand All @@ -91,7 +111,7 @@ public class KotlinMarkOccurrences(val kotlinEditor: KotlinCommonEditor) : ISele
IJavaSearchConstants.ALL_OCCURRENCES, "Searching in ${file.getName()}")

val occurrences = arrayListOf<Match>()
KotlinQueryParticipant().search({ occurrences.add(it) }, querySpecification, NullProgressMonitor())
KotlinQueryParticipant().search({ occurrences.add(it) }, querySpecification, monitor)

return occurrences.map {
if (it !is KotlinElementMatch) return@map null
Expand Down

0 comments on commit 1261e3b

Please sign in to comment.