mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[git-index] add diff preview
GitOrigin-RevId: 0544268488f3f3aa5539ec7aed09fb7bf7ed372d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f01442dfe6
commit
5cc00a7411
@@ -48,15 +48,18 @@ abstract class BaseSingleTaskController<Request, Result>(name: String, resultCon
|
||||
}
|
||||
|
||||
fun runInEdt(disposable: Disposable, action: () -> Unit) {
|
||||
val app = ApplicationManager.getApplication()
|
||||
if (app.isDispatchThread) {
|
||||
if (ApplicationManager.getApplication().isDispatchThread) {
|
||||
action()
|
||||
}
|
||||
else {
|
||||
app.invokeLater(action, { Disposer.isDisposed(disposable) })
|
||||
runInEdtAsync(disposable, action)
|
||||
}
|
||||
}
|
||||
|
||||
fun runInEdtAsync(disposable: Disposable, action: () -> Unit) {
|
||||
ApplicationManager.getApplication().invokeLater(action, { Disposer.isDisposed(disposable) })
|
||||
}
|
||||
|
||||
fun ExecutorService.submitSafe(log: Logger, task: () -> Unit): Future<*> = this.submit {
|
||||
try {
|
||||
task()
|
||||
|
||||
@@ -563,6 +563,8 @@ stage.tree.node.unstaged=Not staged for commit
|
||||
stage.tree.node.unmerged=Unmerged
|
||||
stage.tree.node.untracked=Untracked
|
||||
stage.tree.node.ignored=Ignored
|
||||
stage.content.staged=Staged
|
||||
stage.content.local=Local
|
||||
stage.commit.process=Committing Staged Changes...
|
||||
stage.loading.status=Loading changes...
|
||||
|
||||
|
||||
@@ -18,8 +18,27 @@ data class GitFileStatus(val index: StatusCode,
|
||||
|
||||
fun isUntracked() = isUntracked(index) || isUntracked(workTree)
|
||||
fun isIgnored() = isIgnored(index) || isIgnored(workTree)
|
||||
fun isTracked() = !isIgnored(index) && !isUntracked(index)
|
||||
|
||||
fun getStagedStatus(): FileStatus? = if (isIgnored(index) || isUntracked(index)) null else getFileStatus(index)
|
||||
fun getUnStagedStatus(): FileStatus? = if (isIgnored(workTree) || isUntracked(workTree)) null
|
||||
else getFileStatus(workTree)
|
||||
}
|
||||
}
|
||||
|
||||
fun GitFileStatus.has(contentVersion: ContentVersion): Boolean {
|
||||
return when (contentVersion) {
|
||||
ContentVersion.HEAD -> isTracked() && !isAdded(index)
|
||||
ContentVersion.STAGED -> isTracked() && !isDeleted(index)
|
||||
ContentVersion.LOCAL -> !isDeleted(workTree)
|
||||
}
|
||||
}
|
||||
|
||||
fun GitFileStatus.path(contentVersion: ContentVersion): FilePath {
|
||||
return when (contentVersion) {
|
||||
ContentVersion.HEAD -> origPath ?: path
|
||||
ContentVersion.STAGED -> if (isRenamed(index)) path else origPath ?: path
|
||||
ContentVersion.LOCAL -> path
|
||||
}
|
||||
}
|
||||
|
||||
enum class ContentVersion { HEAD, STAGED, LOCAL }
|
||||
@@ -168,6 +168,8 @@ typealias StatusCode = Char
|
||||
internal fun isIgnored(status: StatusCode) = status == '!'
|
||||
internal fun isUntracked(status: StatusCode) = status == '?'
|
||||
fun isRenamed(status: StatusCode) = status == 'R' || status == 'C'
|
||||
internal fun isAdded(status: StatusCode) = status == 'A'
|
||||
internal fun isDeleted(status: StatusCode) = status == 'D'
|
||||
internal fun isConflicted(index: StatusCode, workTree: StatusCode): Boolean {
|
||||
return (index == 'D' && workTree == 'D') ||
|
||||
(index == 'A' && workTree == 'A') ||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2000-2020 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 git4idea.index
|
||||
|
||||
import com.intellij.diff.DiffContentFactory
|
||||
import com.intellij.diff.DiffContentFactoryEx
|
||||
import com.intellij.diff.DiffRequestFactoryImpl
|
||||
import com.intellij.diff.chains.DiffRequestProducerException
|
||||
import com.intellij.diff.contents.DiffContent
|
||||
import com.intellij.diff.contents.DocumentContent
|
||||
import com.intellij.diff.requests.DiffRequest
|
||||
import com.intellij.diff.requests.SimpleDiffRequest
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.openapi.vcs.FilePath
|
||||
import com.intellij.openapi.vcs.FileStatus
|
||||
import com.intellij.openapi.vcs.VcsException
|
||||
import com.intellij.openapi.vcs.changes.actions.diff.UnversionedDiffRequestProducer
|
||||
import com.intellij.openapi.vcs.changes.ui.ChangeDiffRequestChain
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.vcsUtil.VcsFileUtil
|
||||
import git4idea.GitUtil
|
||||
import git4idea.i18n.GitBundle
|
||||
import git4idea.index.ui.GitFileStatusNode
|
||||
import git4idea.index.ui.NodeKind
|
||||
import git4idea.index.vfs.GitIndexVirtualFile
|
||||
import git4idea.index.vfs.GitIndexVirtualFileCache
|
||||
import git4idea.util.GitFileUtils
|
||||
import java.io.IOException
|
||||
|
||||
fun createTwoSidesDiffRequestProducer(project: Project, statusNode: GitFileStatusNode): ChangeDiffRequestChain.Producer {
|
||||
return when (statusNode.kind) {
|
||||
NodeKind.STAGED -> StagedProducer(project, statusNode)
|
||||
NodeKind.UNSTAGED -> UnStagedProducer(project, statusNode)
|
||||
NodeKind.CONFLICTED, NodeKind.IGNORED, NodeKind.UNTRACKED -> UnversionedDiffRequestProducer.create(project, statusNode.filePath)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(VcsException::class, IOException::class)
|
||||
private fun headContent(project: Project, statusNode: GitFileStatusNode): DiffContent {
|
||||
if (!statusNode.has(ContentVersion.HEAD)) return DiffContentFactory.getInstance().createEmpty()
|
||||
|
||||
val headContent = GitFileUtils.getFileContent(project, statusNode.root, GitUtil.HEAD,
|
||||
VcsFileUtil.relativePath(statusNode.root, statusNode.path(ContentVersion.HEAD)))
|
||||
return DiffContentFactoryEx.getInstanceEx().createFromBytes(project, headContent, statusNode.filePath)
|
||||
}
|
||||
|
||||
@Throws(VcsException::class)
|
||||
private fun stagedContent(project: Project, statusNode: GitFileStatusNode): DiffContent {
|
||||
if (!statusNode.has(ContentVersion.STAGED)) return DiffContentFactory.getInstance().createEmpty()
|
||||
|
||||
val indexFile = project.service<GitIndexVirtualFileCache>().get(statusNode.root, statusNode.path(ContentVersion.STAGED))
|
||||
return DiffContentFactory.getInstance().create(project, indexFile)
|
||||
}
|
||||
|
||||
@Throws(VcsException::class)
|
||||
private fun localContent(project: Project, statusNode: GitFileStatusNode): DiffContent {
|
||||
if (!statusNode.has(ContentVersion.LOCAL)) return DiffContentFactory.getInstance().createEmpty()
|
||||
|
||||
val localFile: VirtualFile = statusNode.path(ContentVersion.LOCAL).virtualFile ?:
|
||||
throw VcsException("Can't get local file: " + statusNode.filePath)
|
||||
return DiffContentFactory.getInstance().create(project, localFile)
|
||||
}
|
||||
|
||||
private class UnStagedProducer constructor(private val project: Project, file: GitFileStatusNode) : GitFileStatusNodeProducerBase(file) {
|
||||
@Throws(VcsException::class)
|
||||
override fun processImpl(): DiffRequest {
|
||||
return StagedDiffRequest(stagedContent(project, statusNode), localContent(project, statusNode),
|
||||
GitBundle.message("stage.content.staged"), GitBundle.message("stage.content.local"),
|
||||
getTitle(statusNode))
|
||||
}
|
||||
}
|
||||
|
||||
private class StagedProducer constructor(private val project: Project, file: GitFileStatusNode) : GitFileStatusNodeProducerBase(file) {
|
||||
@Throws(VcsException::class, IOException::class)
|
||||
override fun processImpl(): DiffRequest {
|
||||
return StagedDiffRequest(headContent(project, statusNode), stagedContent(project, statusNode),
|
||||
GitUtil.HEAD, GitBundle.message("stage.content.staged"),
|
||||
getTitle(statusNode))
|
||||
}
|
||||
}
|
||||
|
||||
private class StagedDiffRequest(contents: List<DiffContent>, titles: List<String>, title: String? = null) :
|
||||
SimpleDiffRequest(title, contents, titles) {
|
||||
|
||||
constructor(content1: DiffContent, content2: DiffContent, title1: String, title2: String, title: String? = null) :
|
||||
this(listOf(content1, content2), listOf(title1, title2), title)
|
||||
|
||||
override fun onAssigned(isAssigned: Boolean) {
|
||||
super.onAssigned(isAssigned)
|
||||
if (!isAssigned) {
|
||||
for (content in contents) {
|
||||
if (content is DocumentContent) {
|
||||
val file = FileDocumentManager.getInstance().getFile(content.document)
|
||||
if (file is GitIndexVirtualFile) {
|
||||
FileDocumentManager.getInstance().saveDocument(content.document)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class GitFileStatusNodeProducerBase(val statusNode: GitFileStatusNode) : ChangeDiffRequestChain.Producer {
|
||||
@Throws(VcsException::class, IOException::class)
|
||||
abstract fun processImpl(): DiffRequest
|
||||
|
||||
@Throws(DiffRequestProducerException::class)
|
||||
override fun process(context: UserDataHolder, indicator: ProgressIndicator): DiffRequest {
|
||||
try {
|
||||
return processImpl()
|
||||
}
|
||||
catch (e: VcsException) {
|
||||
throw DiffRequestProducerException(e)
|
||||
}
|
||||
catch (e: IOException) {
|
||||
throw DiffRequestProducerException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFilePath(): FilePath {
|
||||
return statusNode.filePath
|
||||
}
|
||||
|
||||
override fun getFileStatus(): FileStatus {
|
||||
return statusNode.fileStatus
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return statusNode.filePath.presentableUrl
|
||||
}
|
||||
|
||||
override fun equals(o: Any?): Boolean {
|
||||
if (this === o) return true
|
||||
if (o == null || javaClass != o.javaClass) return false
|
||||
|
||||
val wrapper = o as GitFileStatusNodeProducerBase
|
||||
return statusNode == wrapper.statusNode
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return statusNode.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun GitFileStatusNode.has(contentVersion: ContentVersion): Boolean {
|
||||
return status.has(contentVersion)
|
||||
}
|
||||
|
||||
private fun GitFileStatusNode.path(contentVersion: ContentVersion): FilePath {
|
||||
return status.path(contentVersion)
|
||||
}
|
||||
|
||||
private fun getTitle(statusNode: GitFileStatusNode): String {
|
||||
return DiffRequestFactoryImpl.getTitle(statusNode.filePath, statusNode.origPath, DiffRequestFactoryImpl.DIFF_TITLE_RENAME_SEPARATOR)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2000-2020 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 git4idea.index.ui
|
||||
|
||||
import com.intellij.diff.FrameDiffTool
|
||||
import com.intellij.diff.chains.DiffRequestProducer
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vcs.changes.ChangeViewDiffRequestProcessor
|
||||
import com.intellij.openapi.vcs.changes.ui.ChangesTree
|
||||
import com.intellij.openapi.vcs.changes.ui.VcsTreeModelData
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.ui.SideBorder
|
||||
import com.intellij.vcs.log.runInEdtAsync
|
||||
import git4idea.index.GitStageTracker
|
||||
import git4idea.index.GitStageTrackerListener
|
||||
import git4idea.index.createTwoSidesDiffRequestProducer
|
||||
import java.util.stream.Stream
|
||||
|
||||
class GitStageDiffPreview(project: Project, private val tree: ChangesTree, tracker: GitStageTracker, parent: Disposable) :
|
||||
ChangeViewDiffRequestProcessor(project, "Stage") {
|
||||
|
||||
init {
|
||||
myContentPanel.border = IdeBorderFactory.createBorder(SideBorder.TOP)
|
||||
tree.addSelectionListener(Runnable {
|
||||
val modelUpdateInProgress = tree.isModelUpdateInProgress
|
||||
runInEdtAsync(this) { updatePreview(component.isShowing, modelUpdateInProgress) }
|
||||
}, this)
|
||||
tracker.addListener(object : GitStageTrackerListener {
|
||||
override fun update() {
|
||||
updatePreview(component.isShowing, true)
|
||||
}
|
||||
}, this)
|
||||
Disposer.register(parent, this)
|
||||
}
|
||||
|
||||
override fun shouldAddToolbarBottomBorder(toolbarComponents: FrameDiffTool.ToolbarComponents): Boolean = false
|
||||
|
||||
fun getToolbarWrapper(): com.intellij.ui.components.panels.Wrapper = myToolbarWrapper
|
||||
|
||||
override fun selectChange(change: Wrapper) {
|
||||
}
|
||||
|
||||
override fun getSelectedChanges(): Stream<Wrapper?> {
|
||||
val hasSelection = tree.selectionModel.selectionCount != 0
|
||||
return wrap(if (hasSelection) VcsTreeModelData.selected(tree) else VcsTreeModelData.all(tree))
|
||||
}
|
||||
|
||||
override fun getAllChanges(): Stream<Wrapper?> {
|
||||
return wrap(VcsTreeModelData.all(tree))
|
||||
}
|
||||
|
||||
private fun wrap(modelData: VcsTreeModelData): Stream<Wrapper?> {
|
||||
return modelData.userObjectsStream(GitFileStatusNode::class.java).map { info -> GitFileStatusNodeWrapper(info) }
|
||||
}
|
||||
|
||||
private class GitFileStatusNodeWrapper(val node: GitFileStatusNode) : Wrapper() {
|
||||
override fun getPresentableName(): String? = node.filePath.name
|
||||
|
||||
override fun getUserObject(): Any = node
|
||||
|
||||
override fun createProducer(project: Project?): DiffRequestProducer? {
|
||||
return createTwoSidesDiffRequestProducer(project!!, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,18 @@ internal class GitStagePanel(private val tracker: GitStageTracker, disposablePar
|
||||
treeMessageSplitter.firstComponent = progressStripe
|
||||
treeMessageSplitter.secondComponent = commitPanel
|
||||
|
||||
add(toolbar.component, BorderLayout.NORTH)
|
||||
add(treeMessageSplitter, BorderLayout.CENTER)
|
||||
val leftPanel = JPanel(BorderLayout())
|
||||
leftPanel.add(toolbar.component, BorderLayout.NORTH)
|
||||
leftPanel.add(treeMessageSplitter, BorderLayout.CENTER)
|
||||
|
||||
val diffPreview = GitStageDiffPreview(project, tree, tracker, this)
|
||||
diffPreview.getToolbarWrapper().setVerticalSizeReferent(toolbar.component)
|
||||
|
||||
val commitDiffSplitter = OnePixelSplitter("git.stage.commit.diff.splitter", 0.5f)
|
||||
commitDiffSplitter.firstComponent = leftPanel
|
||||
commitDiffSplitter.secondComponent = diffPreview.component
|
||||
|
||||
add(commitDiffSplitter, BorderLayout.CENTER)
|
||||
|
||||
tracker.addListener(MyGitStageTrackerListener(), this)
|
||||
if (tracker.isRefreshInProgress) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2020 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 git4idea.index.ui
|
||||
|
||||
import com.google.common.base.Objects
|
||||
import com.intellij.ide.util.treeView.TreeState
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Comparing
|
||||
@@ -37,7 +38,7 @@ abstract class GitStageTree(project: Project) : ChangesTree(project, false, true
|
||||
NodeKind.values().forEach { kind ->
|
||||
if (kind.`is`(status)) {
|
||||
val parentNode = parentNodes.getOrPut(kind) { ChangesBrowserKindNode(kind) }
|
||||
val fileStatusInfo = GitFileStatusNode(root, status.path, kind, kind.status(status), kind.origPath(status))
|
||||
val fileStatusInfo = GitFileStatusNode(root, status, kind)
|
||||
builder.insertPath(fileStatusInfo, parentNode)
|
||||
}
|
||||
}
|
||||
@@ -63,7 +64,7 @@ abstract class GitStageTree(project: Project) : ChangesTree(project, false, true
|
||||
}
|
||||
|
||||
private class ChangesBrowserGitFileStatusNode(node: GitFileStatusNode) :
|
||||
AbstractChangesBrowserFilePathNode<GitFileStatusNode>(node, node.status) {
|
||||
AbstractChangesBrowserFilePathNode<GitFileStatusNode>(node, node.fileStatus) {
|
||||
private val movedRelativePath by lazy { getMovedRelativePath(getUserObject()) }
|
||||
override fun filePath(userObject: GitFileStatusNode): FilePath = userObject.filePath
|
||||
override fun originText(userObject: GitFileStatusNode): String? {
|
||||
@@ -75,8 +76,8 @@ abstract class GitStageTree(project: Project) : ChangesTree(project, false, true
|
||||
}
|
||||
|
||||
private fun getMovedRelativePath(userObject: GitFileStatusNode): String? {
|
||||
if (userObject.origPath == null || userObject.origPath.parentPath == userObject.filePath.parentPath) return null
|
||||
return PlatformVcsPathPresenter.getPresentableRelativePath(userObject.filePath, userObject.origPath)
|
||||
if (userObject.origPath == null || userObject.origPath!!.parentPath == userObject.filePath.parentPath) return null
|
||||
return PlatformVcsPathPresenter.getPresentableRelativePath(userObject.filePath, userObject.origPath!!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +130,29 @@ enum class NodeKind(@PropertyKey(resourceBundle = GitBundle.BUNDLE) @NonNls val
|
||||
open fun origPath(status: GitFileStatus): FilePath? = null
|
||||
}
|
||||
|
||||
data class GitFileStatusNode(val root: VirtualFile,
|
||||
val filePath: FilePath,
|
||||
val kind: NodeKind,
|
||||
val status: FileStatus,
|
||||
val origPath: FilePath? = null)
|
||||
class GitFileStatusNode(val root: VirtualFile, val status: GitFileStatus, val kind: NodeKind) {
|
||||
val filePath: FilePath get() = status.path
|
||||
val origPath: FilePath? get() = kind.origPath(status)
|
||||
val fileStatus: FileStatus get() = kind.status(status)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as GitFileStatusNode
|
||||
|
||||
if (root != other.root) return false
|
||||
if (status != other.status) return false
|
||||
if (kind != other.kind) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hashCode(root, fileStatus, kind)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "GitFileStatusNode.Saved(root=$root, status=$fileStatus, kind=$kind)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user