mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[github] Rework pull request data loaders
Use single thread to load everything and publish data via CompletableFuture's
This commit is contained in:
+10
-17
@@ -49,12 +49,10 @@ class GithubPullRequestsComponentFactory(private val project: Project,
|
||||
CachingGithubAvatarIconsProvider.Factory(
|
||||
avatarLoader, imageResizer, requestExecutor))
|
||||
|
||||
val detailsLoader = GithubPullRequestsDetailsLoader(progressManager, requestExecutor, selectionModel)
|
||||
val branchFetcher = GithubPullRequestsBranchesFetcher(progressManager, git, detailsLoader, repository, remote)
|
||||
val changesLoader = GithubPullRequestsChangesLoader(project, progressManager, branchFetcher, repository)
|
||||
val dataLoader = GithubPullRequestsDataLoader(project, progressManager, git, requestExecutor, repository, remote)
|
||||
|
||||
val changes = GithubPullRequestChangesComponent(project, changesLoader, actionManager)
|
||||
val details = GithubPullRequestDetailsComponent(project, detailsLoader)
|
||||
val changes = GithubPullRequestChangesComponent(project, selectionModel, dataLoader, actionManager)
|
||||
val details = GithubPullRequestDetailsComponent(project, selectionModel, dataLoader)
|
||||
|
||||
val preview = GithubPullRequestPreviewComponent(uiSettings, changes, details)
|
||||
list.setToolbarHeightReferent(preview.toolbarComponent)
|
||||
@@ -65,19 +63,15 @@ class GithubPullRequestsComponentFactory(private val project: Project,
|
||||
|
||||
// disposed by content manager when tab is closed
|
||||
val wrapper = WrappingComponent(splitter,
|
||||
repository,
|
||||
remote, repoPath,
|
||||
account,
|
||||
listLoader,
|
||||
detailsLoader, branchFetcher)
|
||||
repository, remote, repoPath, account,
|
||||
selectionModel, listLoader,
|
||||
dataLoader)
|
||||
Disposer.register(wrapper, Disposable {
|
||||
Disposer.dispose(list)
|
||||
Disposer.dispose(preview)
|
||||
|
||||
Disposer.dispose(listLoader)
|
||||
Disposer.dispose(changesLoader)
|
||||
Disposer.dispose(branchFetcher)
|
||||
Disposer.dispose(detailsLoader)
|
||||
Disposer.dispose(dataLoader)
|
||||
})
|
||||
changes.diffAction.registerCustomShortcutSet(wrapper, wrapper)
|
||||
return wrapper
|
||||
@@ -89,9 +83,9 @@ class GithubPullRequestsComponentFactory(private val project: Project,
|
||||
private val remote: GitRemote,
|
||||
private val repoPath: GithubFullPath,
|
||||
private val account: GithubAccount,
|
||||
private val selectionModel: GithubPullRequestsListSelectionModel,
|
||||
private val listLoader: GithubPullRequestsLoader,
|
||||
private val detailsLoader: GithubPullRequestsDetailsLoader,
|
||||
private val branchesFetcher: GithubPullRequestsBranchesFetcher)
|
||||
private val dataLoader: GithubPullRequestsDataLoader)
|
||||
: Wrapper(wrapped), Disposable, DataProvider {
|
||||
init {
|
||||
isFocusCycleRoot = true
|
||||
@@ -104,8 +98,7 @@ class GithubPullRequestsComponentFactory(private val project: Project,
|
||||
GithubPullRequestKeys.FULL_PATH.`is`(dataId) -> repoPath
|
||||
GithubPullRequestKeys.SERVER_PATH.`is`(dataId) -> account.server
|
||||
GithubPullRequestKeys.PULL_REQUESTS_LOADER.`is`(dataId) -> listLoader
|
||||
GithubPullRequestKeys.PULL_REQUESTS_DETAILS_LOADER.`is`(dataId) -> detailsLoader
|
||||
GithubPullRequestKeys.PULL_REQUESTS_BRANCHES_FETCHER.`is`(dataId) -> branchesFetcher
|
||||
GithubPullRequestKeys.SELECTED_PULL_REQUEST_DATA_PROVIDER.`is`(dataId) -> selectionModel.current?.let(dataLoader::getDataProvider)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -20,17 +20,17 @@ class GithubPullRequestCreateBranchAction : DumbAwareAction("Create New Local Br
|
||||
override fun update(e: AnActionEvent) {
|
||||
val project = e.getData(CommonDataKeys.PROJECT)
|
||||
val pullRequest = e.getData(GithubPullRequestKeys.SELECTED_PULL_REQUEST)
|
||||
e.presentation.isEnabled = project != null && !project.isDefault && pullRequest != null
|
||||
val dataProvider = e.getData(GithubPullRequestKeys.SELECTED_PULL_REQUEST_DATA_PROVIDER)
|
||||
e.presentation.isEnabled = project != null && !project.isDefault && pullRequest != null && dataProvider != null
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.getRequiredData(CommonDataKeys.PROJECT)
|
||||
val pullRequest = e.getRequiredData(GithubPullRequestKeys.SELECTED_PULL_REQUEST)
|
||||
val repository = e.getRequiredData(GithubPullRequestKeys.REPOSITORY)
|
||||
val branchFetcher = e.getRequiredData(GithubPullRequestKeys.PULL_REQUESTS_BRANCHES_FETCHER)
|
||||
val repositoryList = listOf(repository)
|
||||
|
||||
val hashesFuture = branchFetcher.request ?: return
|
||||
val hashesFuture = e.getRequiredData(GithubPullRequestKeys.SELECTED_PULL_REQUEST_DATA_PROVIDER).branchFetchRequest
|
||||
val options = GitBranchUtil.getNewBranchNameFromUser(project, repositoryList,
|
||||
"Checkout New Branch From Pull Request #${pullRequest.number}",
|
||||
"pull/${pullRequest.number}") ?: return
|
||||
|
||||
+4
-8
@@ -7,8 +7,7 @@ import git4idea.repo.GitRepository
|
||||
import org.jetbrains.plugins.github.api.GithubFullPath
|
||||
import org.jetbrains.plugins.github.api.GithubServerPath
|
||||
import org.jetbrains.plugins.github.api.data.GithubSearchedIssue
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsBranchesFetcher
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDetailsLoader
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDataLoader
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsLoader
|
||||
|
||||
object GithubPullRequestKeys {
|
||||
@@ -16,14 +15,11 @@ object GithubPullRequestKeys {
|
||||
val PULL_REQUESTS_LOADER =
|
||||
DataKey.create<GithubPullRequestsLoader>("org.jetbrains.plugins.github.pullrequest.loader")
|
||||
@JvmStatic
|
||||
val PULL_REQUESTS_DETAILS_LOADER =
|
||||
DataKey.create<GithubPullRequestsDetailsLoader>("org.jetbrains.plugins.github.pullrequest.details.loader")
|
||||
@JvmStatic
|
||||
val PULL_REQUESTS_BRANCHES_FETCHER =
|
||||
DataKey.create<GithubPullRequestsBranchesFetcher>("org.jetbrains.plugins.github.pullrequest.branch.fetcher")
|
||||
@JvmStatic
|
||||
val SELECTED_PULL_REQUEST = DataKey.create<GithubSearchedIssue>("org.jetbrains.plugins.github.pullrequest.selected")
|
||||
@JvmStatic
|
||||
val SELECTED_PULL_REQUEST_DATA_PROVIDER =
|
||||
DataKey.create<GithubPullRequestsDataLoader.DataProvider>("org.jetbrains.plugins.github.pullrequest.selected.dataprovider")
|
||||
@JvmStatic
|
||||
val REPOSITORY = DataKey.create<GitRepository>("org.jetbrains.plugins.github.pullrequest.repository")
|
||||
@JvmStatic
|
||||
val REMOTE = DataKey.create<GitRemote>("org.jetbrains.plugins.github.pullrequest.remote")
|
||||
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.github.pullrequest.data
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.util.Couple
|
||||
import com.intellij.util.EventDispatcher
|
||||
import git4idea.commands.Git
|
||||
import git4idea.repo.GitRemote
|
||||
import git4idea.repo.GitRepository
|
||||
import org.jetbrains.annotations.CalledInAwt
|
||||
import org.jetbrains.plugins.github.api.data.GithubPullRequestDetailed
|
||||
import java.util.*
|
||||
import java.util.concurrent.Future
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class GithubPullRequestsBranchesFetcher(progressManager: ProgressManager,
|
||||
private val git: Git,
|
||||
private val detailsLoader: GithubPullRequestsDetailsLoader,
|
||||
private val repository: GitRepository,
|
||||
private val remote: GitRemote)
|
||||
: SingleWorkerProcessExecutor(progressManager, "GitHub PR branch fetching breaker"),
|
||||
GithubPullRequestsDetailsLoader.RequestChangedListener {
|
||||
|
||||
@set:CalledInAwt
|
||||
var request: Future<Couple<String>>? by Delegates.observable<Future<Couple<String>>?>(null) { _, _, _ ->
|
||||
changeEventDispatcher.multicaster.requestChanged()
|
||||
}
|
||||
private set
|
||||
|
||||
private val changeEventDispatcher = EventDispatcher.create(RequestChangedListener::class.java)
|
||||
|
||||
init {
|
||||
detailsLoader.addRequestChangeListener(this, this)
|
||||
}
|
||||
|
||||
override fun requestChanged() {
|
||||
cancelCurrentTasks()
|
||||
request = detailsLoader.request?.let { details ->
|
||||
submit {
|
||||
val pullRequest = details.get()
|
||||
fetchBranch(pullRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchBranch(details: GithubPullRequestDetailed): Couple<String> {
|
||||
if (!isCommitFetched(details.head.sha)) {
|
||||
git.fetch(repository, remote, emptyList(), "refs/pull/${details.number}/head:").throwOnError()
|
||||
}
|
||||
if (!isCommitFetched(details.head.sha)) throw IllegalStateException("Pull request head is not available after fetch")
|
||||
return Couple.of(details.base.sha, details.head.sha)
|
||||
}
|
||||
|
||||
private fun isCommitFetched(commitHash: String): Boolean {
|
||||
val result = git.getObjectType(repository, commitHash)
|
||||
return result.success() && result.outputAsJoinedString == "commit"
|
||||
}
|
||||
|
||||
fun addBranchChangeListener(listener: RequestChangedListener, disposable: Disposable) =
|
||||
changeEventDispatcher.addListener(listener, disposable)
|
||||
|
||||
interface RequestChangedListener : EventListener {
|
||||
fun requestChanged()
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.github.pullrequest.data
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vcs.changes.Change
|
||||
import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser
|
||||
import com.intellij.util.EventDispatcher
|
||||
import git4idea.history.GitLogUtil
|
||||
import git4idea.repo.GitRepository
|
||||
import java.util.*
|
||||
|
||||
class GithubPullRequestsChangesLoader(private val project: Project,
|
||||
progressManager: ProgressManager,
|
||||
private val branchesFetcher: GithubPullRequestsBranchesFetcher,
|
||||
private val repository: GitRepository)
|
||||
: SingleWorkerProcessExecutor(progressManager, "GitHub PR changes loading breaker"),
|
||||
GithubPullRequestsBranchesFetcher.RequestChangedListener {
|
||||
|
||||
private val loadingEventDispatcher = EventDispatcher.create(ChangesLoadingListener::class.java)
|
||||
|
||||
init {
|
||||
branchesFetcher.addBranchChangeListener(this, this)
|
||||
}
|
||||
|
||||
override fun requestChanged() {
|
||||
cancelCurrentTasks()
|
||||
|
||||
val fetchFuture = branchesFetcher.request
|
||||
if (fetchFuture == null) {
|
||||
loadingEventDispatcher.multicaster.loaderCleared()
|
||||
}
|
||||
else submit { indicator ->
|
||||
try {
|
||||
val commits = fetchFuture.get()
|
||||
|
||||
val details = GitLogUtil.collectFullDetails(project, repository.root, "${commits.first}..${commits.second}")
|
||||
val changes = CommittedChangesTreeBrowser.zipChanges(details.reversed().flatMap { it.changes })
|
||||
|
||||
runInEdt { if (!indicator.isCanceled) loadingEventDispatcher.multicaster.changesLoaded(changes) }
|
||||
}
|
||||
catch (pce: ProcessCanceledException) {
|
||||
// ignore
|
||||
}
|
||||
catch (e: Exception) {
|
||||
runInEdt { if (!indicator.isCanceled) loadingEventDispatcher.multicaster.errorOccurred(e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addLoadingListener(listener: ChangesLoadingListener, disposable: Disposable) =
|
||||
loadingEventDispatcher.addListener(listener, disposable)
|
||||
|
||||
interface ChangesLoadingListener : EventListener {
|
||||
fun changesLoaded(changes: List<Change>) {}
|
||||
fun errorOccurred(error: Throwable) {}
|
||||
fun loaderCleared() {}
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.github.pullrequest.data
|
||||
|
||||
import com.google.common.cache.CacheBuilder
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Couple
|
||||
import com.intellij.openapi.util.LowMemoryWatcher
|
||||
import com.intellij.openapi.vcs.changes.Change
|
||||
import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser
|
||||
import git4idea.GitCommit
|
||||
import git4idea.commands.Git
|
||||
import git4idea.history.GitLogUtil
|
||||
import git4idea.repo.GitRemote
|
||||
import git4idea.repo.GitRepository
|
||||
import org.jetbrains.plugins.github.api.GithubApiRequestExecutor
|
||||
import org.jetbrains.plugins.github.api.GithubApiRequests
|
||||
import org.jetbrains.plugins.github.api.data.GithubPullRequestDetailedWithHtml
|
||||
import org.jetbrains.plugins.github.api.data.GithubSearchedIssue
|
||||
import org.jetbrains.plugins.github.util.NonReusableEmptyProgressIndicator
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
//TODO: cancel loading in removed providers
|
||||
class GithubPullRequestsDataLoader(private val project: Project,
|
||||
private val progressManager: ProgressManager,
|
||||
private val git: Git,
|
||||
private val requestExecutor: GithubApiRequestExecutor,
|
||||
private val repository: GitRepository,
|
||||
private val remote: GitRemote) : Disposable {
|
||||
|
||||
private val progressIndicator = NonReusableEmptyProgressIndicator()
|
||||
private val cache = CacheBuilder.newBuilder()
|
||||
.maximumSize(5)
|
||||
.build<Long, DataProvider>()
|
||||
.asMap()
|
||||
|
||||
init {
|
||||
LowMemoryWatcher.register(Runnable { cache.clear() }, this)
|
||||
}
|
||||
|
||||
fun getDataProvider(githubSearchedIssue: GithubSearchedIssue): DataProvider {
|
||||
return cache.getOrPut(githubSearchedIssue.number) {
|
||||
val task = DataTask(githubSearchedIssue.pullRequestLinks!!.url)
|
||||
progressManager.runProcessWithProgressAsynchronously(task, progressIndicator)
|
||||
task
|
||||
}
|
||||
}
|
||||
|
||||
private inner class DataTask(private val url: String)
|
||||
: Task.Backgroundable(project, "Load Pull Request Data", true), DataProvider {
|
||||
|
||||
override val detailsRequest = CompletableFuture<GithubPullRequestDetailedWithHtml>()
|
||||
override val branchFetchRequest = CompletableFuture<Couple<String>>()
|
||||
override val logCommitsRequest = CompletableFuture<List<GitCommit>>()
|
||||
override val changesRequest = CompletableFuture<List<Change>>()
|
||||
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
runPartialTask(detailsRequest, indicator) {
|
||||
requestExecutor.execute(progressIndicator, GithubApiRequests.Repos.PullRequests.getHtml(url))
|
||||
}
|
||||
|
||||
runPartialTask(branchFetchRequest, indicator) {
|
||||
val details = getOrHandle(detailsRequest)
|
||||
if (!isCommitFetched(details.head.sha)) {
|
||||
git.fetch(repository, remote, emptyList(), "refs/pull/${details.number}/head:").throwOnError()
|
||||
}
|
||||
if (!isCommitFetched(details.head.sha)) throw IllegalStateException("Pull request head is not available after fetch")
|
||||
Couple.of(details.base.sha, details.head.sha)
|
||||
}
|
||||
|
||||
runPartialTask(logCommitsRequest, indicator) {
|
||||
val hashes = getOrHandle(branchFetchRequest)
|
||||
GitLogUtil.collectFullDetails(project, repository.root, "${hashes.first}..${hashes.second}")
|
||||
}
|
||||
|
||||
runPartialTask(changesRequest, indicator) {
|
||||
val commits = getOrHandle(logCommitsRequest)
|
||||
CommittedChangesTreeBrowser.zipChanges(commits.reversed().flatMap { it.changes })
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> runPartialTask(resultFuture: CompletableFuture<T>, indicator: ProgressIndicator, crossinline task: () -> T) {
|
||||
try {
|
||||
if (resultFuture.isCancelled) return
|
||||
indicator.checkCanceled()
|
||||
val result = task()
|
||||
resultFuture.complete(result)
|
||||
}
|
||||
catch (pce: ProcessCanceledException) {
|
||||
resultFuture.cancel(true)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
resultFuture.completeExceptionally(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ProcessCanceledException::class)
|
||||
private fun <T> getOrHandle(future: CompletableFuture<T>): T {
|
||||
try {
|
||||
return future.get()
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw ProcessCanceledException(e)
|
||||
}
|
||||
catch (e: InterruptedException) {
|
||||
throw ProcessCanceledException(e)
|
||||
}
|
||||
catch (e: ExecutionException) {
|
||||
throw e.cause ?: e
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCommitFetched(commitHash: String): Boolean {
|
||||
val result = git.getObjectType(repository, commitHash)
|
||||
return result.success() && result.outputAsJoinedString == "commit"
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
progressIndicator.cancel()
|
||||
}
|
||||
|
||||
interface DataProvider {
|
||||
val detailsRequest: CompletableFuture<GithubPullRequestDetailedWithHtml>
|
||||
val branchFetchRequest: CompletableFuture<Couple<String>>
|
||||
val logCommitsRequest: CompletableFuture<List<GitCommit>>
|
||||
val changesRequest: CompletableFuture<List<Change>>
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.github.pullrequest.data
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.util.EventDispatcher
|
||||
import org.jetbrains.annotations.CalledInAwt
|
||||
import org.jetbrains.annotations.CalledInBackground
|
||||
import org.jetbrains.plugins.github.api.GithubApiRequestExecutor
|
||||
import org.jetbrains.plugins.github.api.GithubApiRequests
|
||||
import org.jetbrains.plugins.github.api.data.GithubPullRequestDetailedWithHtml
|
||||
import org.jetbrains.plugins.github.api.data.GithubSearchedIssue
|
||||
import org.jetbrains.plugins.github.pullrequest.ui.GithubPullRequestsListSelectionModel
|
||||
import java.util.*
|
||||
import java.util.concurrent.Future
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class GithubPullRequestsDetailsLoader(progressManager: ProgressManager,
|
||||
private val requestExecutor: GithubApiRequestExecutor,
|
||||
private val selectionModel: GithubPullRequestsListSelectionModel)
|
||||
: SingleWorkerProcessExecutor(progressManager, "GitHub PR info loading breaker"),
|
||||
GithubPullRequestsListSelectionModel.SelectionChangedListener {
|
||||
|
||||
@set:CalledInAwt
|
||||
var request: Future<GithubPullRequestDetailedWithHtml>?
|
||||
by Delegates.observable<Future<GithubPullRequestDetailedWithHtml>?>(null) { _, _, _ ->
|
||||
changeEventDispatcher.multicaster.requestChanged()
|
||||
}
|
||||
private set
|
||||
|
||||
private val changeEventDispatcher = EventDispatcher.create(RequestChangedListener::class.java)
|
||||
private val loadingEventDispatcher = EventDispatcher.create(LoadingListener::class.java)
|
||||
|
||||
init {
|
||||
selectionModel.addChangesListener(this, this)
|
||||
}
|
||||
|
||||
override fun selectionChanged() {
|
||||
cancelCurrentTasks()
|
||||
val selection = selectionModel.current
|
||||
if (selection == null) {
|
||||
request = null
|
||||
loadingEventDispatcher.multicaster.loaderCleared()
|
||||
}
|
||||
else {
|
||||
request = submit { indicator -> loadDetails(indicator, requestExecutor, selection) }
|
||||
}
|
||||
}
|
||||
|
||||
@CalledInBackground
|
||||
private fun loadDetails(indicator: ProgressIndicator, requestExecutor: GithubApiRequestExecutor, searchedIssue: GithubSearchedIssue)
|
||||
: GithubPullRequestDetailedWithHtml {
|
||||
try {
|
||||
val links = searchedIssue.pullRequestLinks ?: throw IllegalStateException("Missing pull request links")
|
||||
val details = requestExecutor.execute(indicator, GithubApiRequests.Repos.PullRequests.getHtml(links.url))
|
||||
loadingEventDispatcher.multicaster.detailsLoaded(details)
|
||||
return details
|
||||
}
|
||||
catch (pce: ProcessCanceledException) {
|
||||
throw pce
|
||||
}
|
||||
catch (e: Exception) {
|
||||
loadingEventDispatcher.multicaster.errorOccurred(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
fun addRequestChangeListener(listener: RequestChangedListener, disposable: Disposable) =
|
||||
changeEventDispatcher.addListener(listener, disposable)
|
||||
|
||||
fun addLoadingListener(listener: LoadingListener, disposable: Disposable) =
|
||||
loadingEventDispatcher.addListener(listener, disposable)
|
||||
|
||||
interface RequestChangedListener : EventListener {
|
||||
fun requestChanged()
|
||||
}
|
||||
|
||||
interface LoadingListener : EventListener {
|
||||
fun detailsLoaded(details: GithubPullRequestDetailedWithHtml)
|
||||
fun errorOccurred(error: Throwable)
|
||||
fun loaderCleared()
|
||||
}
|
||||
}
|
||||
+38
-25
@@ -17,54 +17,67 @@ import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.JBLoadingPanel
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
import com.intellij.util.ui.ComponentWithEmptyText
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsChangesLoader
|
||||
import org.jetbrains.plugins.github.pullrequest.data.SingleWorkerProcessExecutor
|
||||
import org.jetbrains.plugins.github.api.data.GithubSearchedIssue
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDataLoader
|
||||
import org.jetbrains.plugins.github.util.GithubAsyncUtil
|
||||
import org.jetbrains.plugins.github.util.handleOnEdt
|
||||
import java.awt.BorderLayout
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.border.Border
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class GithubPullRequestChangesComponent(project: Project,
|
||||
loader: GithubPullRequestsChangesLoader,
|
||||
private val selectionModel: GithubPullRequestsListSelectionModel,
|
||||
private val dataLoader: GithubPullRequestsDataLoader,
|
||||
actionManager: ActionManager)
|
||||
: Wrapper(), Disposable, GithubPullRequestsChangesLoader.ChangesLoadingListener, SingleWorkerProcessExecutor.ProcessStateListener {
|
||||
|
||||
: Wrapper(), Disposable, GithubPullRequestsListSelectionModel.SelectionChangedListener {
|
||||
private val changesBrowser = PullRequestChangesBrowserWithError(project, actionManager)
|
||||
|
||||
val toolbarComponent: JComponent = changesBrowser.toolbar.component
|
||||
val diffAction = changesBrowser.diffAction
|
||||
private val changesLoadingPanel = JBLoadingPanel(BorderLayout(), this,
|
||||
ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS)
|
||||
|
||||
private var updateFuture: CompletableFuture<Unit>? = null
|
||||
|
||||
init {
|
||||
loader.addProcessListener(this, this)
|
||||
loader.addLoadingListener(this, this)
|
||||
selectionModel.addChangesListener(this, this)
|
||||
|
||||
changesLoadingPanel.add(changesBrowser, BorderLayout.CENTER)
|
||||
setContent(changesLoadingPanel)
|
||||
changesBrowser.emptyText.text = DEFAULT_EMPTY_TEXT
|
||||
}
|
||||
|
||||
override fun processStarted() {
|
||||
changesLoadingPanel.startLoading()
|
||||
changesBrowser.emptyText.clear()
|
||||
changesBrowser.changes = emptyList()
|
||||
override fun selectionChanged() {
|
||||
reset()
|
||||
updateFuture = updateChanges(selectionModel.current)
|
||||
}
|
||||
|
||||
override fun processFinished() {
|
||||
changesLoadingPanel.stopLoading()
|
||||
}
|
||||
private fun updateChanges(item: GithubSearchedIssue?) =
|
||||
item?.let { selection ->
|
||||
changesBrowser.emptyText.clear()
|
||||
changesLoadingPanel.startLoading()
|
||||
|
||||
override fun changesLoaded(changes: List<Change>) {
|
||||
changesBrowser.emptyText.text = "Pull request does not contain any changes"
|
||||
changesBrowser.changes = changes
|
||||
}
|
||||
dataLoader.getDataProvider(selection).changesRequest
|
||||
.handleOnEdt { changes, error ->
|
||||
when {
|
||||
error != null && !GithubAsyncUtil.isCancellation(error) -> {
|
||||
changesBrowser.emptyText
|
||||
.appendText("Cannot load changes", SimpleTextAttributes.ERROR_ATTRIBUTES)
|
||||
.appendSecondaryText(error.message ?: "Unknown error", SimpleTextAttributes.ERROR_ATTRIBUTES, null)
|
||||
}
|
||||
changes != null -> {
|
||||
changesBrowser.emptyText.text = "Pull request does not contain any changes"
|
||||
changesBrowser.changes = changes
|
||||
}
|
||||
}
|
||||
changesLoadingPanel.stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
override fun errorOccurred(error: Throwable) {
|
||||
changesBrowser.emptyText
|
||||
.appendText("Cannot load changes", SimpleTextAttributes.ERROR_ATTRIBUTES)
|
||||
.appendSecondaryText(error.message ?: "Unknown error", SimpleTextAttributes.ERROR_ATTRIBUTES, null)
|
||||
}
|
||||
|
||||
override fun loaderCleared() {
|
||||
private fun reset() {
|
||||
updateFuture?.cancel(true)
|
||||
changesBrowser.emptyText.text = DEFAULT_EMPTY_TEXT
|
||||
changesBrowser.changes = emptyList()
|
||||
}
|
||||
|
||||
+40
-30
@@ -7,50 +7,60 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.JBLoadingPanel
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
import org.jetbrains.plugins.github.api.data.GithubPullRequestDetailedWithHtml
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDetailsLoader
|
||||
import org.jetbrains.plugins.github.pullrequest.data.SingleWorkerProcessExecutor
|
||||
import org.jetbrains.plugins.github.api.data.GithubSearchedIssue
|
||||
import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDataLoader
|
||||
import org.jetbrains.plugins.github.util.GithubAsyncUtil
|
||||
import org.jetbrains.plugins.github.util.handleOnEdt
|
||||
import java.awt.BorderLayout
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class GithubPullRequestDetailsComponent(project: Project,
|
||||
private val selectionModel: GithubPullRequestsListSelectionModel,
|
||||
private val dataLoader: GithubPullRequestsDataLoader)
|
||||
: Wrapper(), Disposable, GithubPullRequestsListSelectionModel.SelectionChangedListener {
|
||||
|
||||
class GithubPullRequestDetailsComponent(project: Project, loader: GithubPullRequestsDetailsLoader)
|
||||
: Wrapper(), Disposable,
|
||||
SingleWorkerProcessExecutor.ProcessStateListener,
|
||||
GithubPullRequestsDetailsLoader.LoadingListener {
|
||||
private val detailsPanel = GithubPullRequestDetailsPanel(project)
|
||||
|
||||
private val loadingPanel = JBLoadingPanel(BorderLayout(), this, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS)
|
||||
|
||||
private var updateFuture: CompletableFuture<Unit>? = null
|
||||
|
||||
init {
|
||||
loader.addProcessListener(this, this)
|
||||
loader.addLoadingListener(this, this)
|
||||
selectionModel.addChangesListener(this, this)
|
||||
|
||||
loadingPanel.add(detailsPanel)
|
||||
setContent(loadingPanel)
|
||||
}
|
||||
|
||||
override fun processStarted() {
|
||||
loadingPanel.startLoading()
|
||||
detailsPanel.details = null
|
||||
override fun selectionChanged() {
|
||||
reset()
|
||||
updateFuture = updateDetails(selectionModel.current)
|
||||
}
|
||||
|
||||
private fun updateDetails(item: GithubSearchedIssue?) =
|
||||
item?.let { selection ->
|
||||
loadingPanel.startLoading()
|
||||
|
||||
dataLoader.getDataProvider(selection).detailsRequest
|
||||
.handleOnEdt { details, error ->
|
||||
when {
|
||||
error != null && !GithubAsyncUtil.isCancellation(error) -> {
|
||||
detailsPanel.emptyText
|
||||
.appendText("Cannot load details", SimpleTextAttributes.ERROR_ATTRIBUTES)
|
||||
.appendSecondaryText(error.message ?: "Unknown error", SimpleTextAttributes.ERROR_ATTRIBUTES, null)
|
||||
}
|
||||
details != null -> {
|
||||
detailsPanel.details = details
|
||||
}
|
||||
}
|
||||
loadingPanel.stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reset() {
|
||||
updateFuture?.cancel(true)
|
||||
detailsPanel.emptyText.clear()
|
||||
}
|
||||
|
||||
override fun processFinished() {
|
||||
loadingPanel.stopLoading()
|
||||
}
|
||||
|
||||
override fun detailsLoaded(details: GithubPullRequestDetailedWithHtml) {
|
||||
detailsPanel.details = details
|
||||
}
|
||||
|
||||
override fun errorOccurred(error: Throwable) {
|
||||
detailsPanel.details = null
|
||||
detailsPanel.emptyText.appendText("Cannot load details", SimpleTextAttributes.ERROR_ATTRIBUTES)
|
||||
.appendSecondaryText(error.message ?: "Unknown error", SimpleTextAttributes.ERROR_ATTRIBUTES, null)
|
||||
}
|
||||
|
||||
override fun loaderCleared() {
|
||||
detailsPanel.details = null
|
||||
detailsPanel.emptyText.clear()
|
||||
}
|
||||
|
||||
override fun dispose() {}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.plugins.github.util
|
||||
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import java.util.concurrent.*
|
||||
import java.util.function.BiFunction
|
||||
|
||||
object GithubAsyncUtil {
|
||||
|
||||
fun isCancellation(error: Throwable): Boolean {
|
||||
return error is ProcessCanceledException
|
||||
|| error is CancellationException
|
||||
|| error is InterruptedException
|
||||
|| error.cause?.let(::isCancellation) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle on EDT if [disposable] is not disposed and [condition] is satisfied
|
||||
*/
|
||||
fun <T> CompletableFuture<T>.handleOnEdt(handler: (T?, Throwable?) -> Unit): CompletableFuture<Unit> =
|
||||
handleAsync(BiFunction<T?, Throwable?, Unit> { result: T?, error: Throwable? ->
|
||||
handler(result, error)
|
||||
}, EDT_EXECUTOR)
|
||||
|
||||
val EDT_EXECUTOR = Executor { runnable -> runInEdt { runnable.run() } }
|
||||
Reference in New Issue
Block a user