diff --git a/plugins/github/resources/messages/GithubBundle.properties b/plugins/github/resources/messages/GithubBundle.properties index 879685cd9f0d..c90a457a0374 100644 --- a/plugins/github/resources/messages/GithubBundle.properties +++ b/plugins/github/resources/messages/GithubBundle.properties @@ -178,6 +178,7 @@ pull.request.create.direction.save=Save pull.request.create.process.title=Creating a pull request... pull.request.create.title=Title pull.request.create.description=Description +pull.request.create.loading.template=Loading template... pull.request.create.collect.diff.data.process.title=Collecting diff data pull.request.create.push.failed=Push failed:
{0} pull.request.create.input.remote.branch.name=Remote branch name: diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRCreateComponentFactory.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRCreateComponentFactory.kt index 9d9ff7737458..1390b7143836 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRCreateComponentFactory.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRCreateComponentFactory.kt @@ -13,6 +13,7 @@ import org.jetbrains.plugins.github.i18n.GithubBundle import org.jetbrains.plugins.github.pullrequest.data.GHPRDataContext import org.jetbrains.plugins.github.pullrequest.ui.GHCompletableFutureLoadingModel import org.jetbrains.plugins.github.pullrequest.ui.toolwindow.GHPRToolWindowTabComponentController +import org.jetbrains.plugins.github.ui.util.DisableableDocument import javax.swing.* import javax.swing.text.PlainDocument @@ -43,7 +44,7 @@ internal class GHPRCreateComponentFactory(private val project: Project, val directionModel = GHPRCreateDirectionModelImpl(repositoryDataService.repositoryMapping) val titleDocument = PlainDocument() - val descriptionDocument = PlainDocument() + val descriptionDocument = DisableableDocument() val metadataModel = GHPRCreateMetadataModel(repositoryDataService, dataContext.securityService.currentUser) val createLoadingModel = GHCompletableFutureLoadingModel(uiDisposable) diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRCreateInfoComponentFactory.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRCreateInfoComponentFactory.kt index 0e3ba0592bba..0610d12f6a3a 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRCreateInfoComponentFactory.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRCreateInfoComponentFactory.kt @@ -52,6 +52,7 @@ import org.jetbrains.plugins.github.pullrequest.ui.GHLoadingModel import org.jetbrains.plugins.github.pullrequest.ui.SimpleEventListener import org.jetbrains.plugins.github.pullrequest.ui.details.GHPRMetadataPanelFactory import org.jetbrains.plugins.github.pullrequest.ui.toolwindow.GHPRToolWindowTabComponentController +import org.jetbrains.plugins.github.ui.util.DisableableDocument import org.jetbrains.plugins.github.util.* import org.jetbrains.plugins.github.util.GithubUtil.runInterruptable import java.awt.event.ActionEvent @@ -67,7 +68,7 @@ internal class GHPRCreateInfoComponentFactory(private val project: Project, fun create(directionModel: GHPRCreateDirectionModel, titleDocument: Document, - descriptionDocument: Document, + descriptionDocument: DisableableDocument, metadataModel: GHPRCreateMetadataModel, createLoadingModel: GHCompletableFutureLoadingModel): JComponent { @@ -87,15 +88,12 @@ internal class GHPRCreateInfoComponentFactory(private val project: Project, progressIndicator.cancel() viewController.viewList() - directionModel.reset() - titleDocument.remove(0, titleDocument.length) - descriptionDocument.remove(0, descriptionDocument.length) - metadataModel.reset() + resetForm(directionModel, titleDocument, descriptionDocument, metadataModel) createLoadingModel.future = null } } InfoController(directionModel, createAction, createDraftAction) - directionModel.reset() + resetForm(directionModel, titleDocument, descriptionDocument, metadataModel) val directionSelector = GHPRCreateDirectionComponentFactory(repositoriesManager, directionModel).create().apply { border = BorderFactory.createCompoundBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM), @@ -118,6 +116,10 @@ internal class GHPRCreateInfoComponentFactory(private val project: Project, emptyText.text = GithubBundle.message("pull.request.create.description") lineWrap = true } + descriptionDocument.addAndInvokeEnabledStateListener { + descriptionField.isEnabled = descriptionDocument.enabled + } + val descriptionPane = JBScrollPane(descriptionField, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER).apply { @@ -203,7 +205,7 @@ internal class GHPRCreateInfoComponentFactory(private val project: Project, } private inner class CreateAction(private val directionModel: GHPRCreateDirectionModel, - private val titleDocument: Document, private val descriptionDocument: Document, + private val titleDocument: Document, private val descriptionDocument: DisableableDocument, private val metadataModel: GHPRCreateMetadataModel, private val draft: Boolean, private val loadingModel: GHCompletableFutureLoadingModel, @@ -240,17 +242,15 @@ internal class GHPRCreateInfoComponentFactory(private val project: Project, CollectionDelta(emptyList(), assignees)) if (labels.isNotEmpty()) detailsData.adjustLabels(ProgressWrapper.wrap(progressIndicator), CollectionDelta(emptyList(), labels)) - } finally { + } + finally { Disposer.dispose(disposable) } pullRequest } }.successOnEdt { viewController.viewPullRequest(it) - directionModel.reset() - titleDocument.remove(0, titleDocument.length) - descriptionDocument.remove(0, descriptionDocument.length) - metadataModel.reset() + resetForm(directionModel, titleDocument, descriptionDocument, metadataModel) it } } @@ -304,7 +304,8 @@ internal class GHPRCreateInfoComponentFactory(private val project: Project, GithubBundle.message("pull.request.create.input.remote.branch.name"), GithubBundle.message("pull.request.create.input.remote.branch.title"), null, localBranch.name, null, null, - GithubBundle.message("pull.request.create.input.remote.branch.comment", localBranch.name, remote.name)) + GithubBundle.message("pull.request.create.input.remote.branch.comment", + localBranch.name, remote.name)) ?: return null //always set tracking return GitPushTarget(GitStandardRemoteBranch(remote, branchName), true) @@ -343,6 +344,26 @@ internal class GHPRCreateInfoComponentFactory(private val project: Project, dialog.show() } + private fun resetForm(directionModel: GHPRCreateDirectionModel, + titleDocument: Document, + descriptionDocument: DisableableDocument, + metadataModel: GHPRCreateMetadataModel) { + directionModel.reset() + titleDocument.remove(0, titleDocument.length) + descriptionDocument.remove(0, descriptionDocument.length) + descriptionDocument.enabled = false + descriptionDocument.insertString(0, GithubBundle.message("pull.request.create.loading.template"), null) + GHPRTemplateLoader.readTemplate(project).successOnEdt { + if (!descriptionDocument.enabled) { + descriptionDocument.remove(0, descriptionDocument.length) + descriptionDocument.insertString(0, it, null) + } + }.completionOnEdt { + if (!descriptionDocument.enabled) descriptionDocument.enabled = true + } + metadataModel.reset() + } + companion object { private val Document.text: String get() = getText(0, length) diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRTemplateLoader.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRTemplateLoader.kt new file mode 100644 index 000000000000..d7b7072382b2 --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/toolwindow/create/GHPRTemplateLoader.kt @@ -0,0 +1,44 @@ +// Copyright 2000-2021 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.ui.toolwindow.create + +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.progress.EmptyProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.project.Project +import com.intellij.util.TimeoutUtil +import com.intellij.util.concurrency.annotations.RequiresBackgroundThread +import org.jetbrains.plugins.github.util.submitIOTask +import java.nio.file.Files +import java.nio.file.Paths +import java.util.concurrent.CompletableFuture + +object GHPRTemplateLoader { + + private val LOG = logger() + + private val paths = listOf( + ".github/pull_request_template.md", + "pull_request_template.md", + "docs/pull_request_template.md" + ) + + fun readTemplate(project: Project): CompletableFuture { + return ProgressManager.getInstance().submitIOTask(EmptyProgressIndicator()) { + doLoad(project) + } + } + + @RequiresBackgroundThread + private fun doLoad(project: Project): String? { + val basePath = project.basePath ?: return null + try { + val files = paths.map { Paths.get(basePath, it) } + val fileContent = files.find(Files::exists)?.let(Files::readString) + if (fileContent != null) return fileContent + } + catch (e: Exception) { + LOG.warn("Failed to read PR template", e) + } + return null + } +} \ No newline at end of file diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/util/DisableableDocument.kt b/plugins/github/src/org/jetbrains/plugins/github/ui/util/DisableableDocument.kt new file mode 100644 index 000000000000..9966ee979de2 --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/util/DisableableDocument.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2021 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.ui.util + +import com.intellij.util.EventDispatcher +import org.jetbrains.plugins.github.pullrequest.ui.SimpleEventListener +import org.jetbrains.plugins.github.util.GithubUtil.Delegates.observableField +import javax.swing.text.PlainDocument + +class DisableableDocument : PlainDocument() { + + private val eventDispatcher = EventDispatcher.create(SimpleEventListener::class.java) + + var enabled by observableField(true, eventDispatcher) + + fun addAndInvokeEnabledStateListener(listener: () -> Unit) = SimpleEventListener.addAndInvokeListener(eventDispatcher, listener) +} \ No newline at end of file