98b01ec5055ff455896b221f4ce3069f2d3470c8
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / impl / PartialLineStatusTrackerManagerState.kt
1 // 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.
2 package com.intellij.openapi.vcs.impl
3
4 import com.intellij.diff.util.Range
5 import com.intellij.openapi.components.*
6 import com.intellij.openapi.project.Project
7 import com.intellij.openapi.util.registry.Registry
8 import com.intellij.openapi.vcs.changes.ChangeListManager
9 import com.intellij.openapi.vcs.changes.InvokeAfterUpdateMode
10 import com.intellij.openapi.vcs.ex.ChangelistsLocalLineStatusTracker
11 import com.intellij.openapi.vcs.ex.ChangelistsLocalLineStatusTracker.RangeState
12 import com.intellij.openapi.vfs.LocalFileSystem
13 import com.intellij.xml.util.XmlStringUtil
14 import org.jdom.Element
15
16 private typealias TrackerState = ChangelistsLocalLineStatusTracker.State
17 private typealias FullTrackerState = ChangelistsLocalLineStatusTracker.FullState
18
19 @State(name = "LineStatusTrackerManager", storages = [(Storage(value = StoragePathMacros.WORKSPACE_FILE))])
20 class PartialLineStatusTrackerManagerState(
21   private val project: Project,
22   private val lineStatusTracker: LineStatusTrackerManager
23 ) : ProjectComponent, PersistentStateComponent<Element> {
24   private val NODE_PARTIAL_FILE = "file"
25   private val ATT_PATH = "path"
26
27   private val NODE_VCS = "vcs"
28   private val NODE_CURRENT = "current"
29   private val ATT_CONTENT = "content"
30
31   private val NODE_RANGES = "ranges"
32   private val NODE_RANGE = "range"
33   private val ATT_START_1 = "start1"
34   private val ATT_END_1 = "end1"
35   private val ATT_START_2 = "start2"
36   private val ATT_END_2 = "end2"
37   private val ATT_CHANGELIST_ID = "changelist"
38
39
40   override fun getState(): Element {
41     val element = Element("state")
42     val fileStates = lineStatusTracker.collectPartiallyChangedFilesStates()
43     for (state in fileStates) {
44       element.addContent(writePartialFileState(state))
45     }
46
47     return element
48   }
49
50   override fun loadState(element: Element) {
51     val fileStates = mutableListOf<TrackerState>()
52     for (node in element.getChildren(NODE_PARTIAL_FILE)) {
53       val state = readPartialFileState(node)
54       if (state != null) fileStates.add(state)
55     }
56
57     if (fileStates.isNotEmpty()) {
58       ChangeListManager.getInstance(project).invokeAfterUpdate(
59         {
60           lineStatusTracker.restoreTrackersForPartiallyChangedFiles(fileStates)
61         }, InvokeAfterUpdateMode.SILENT, null, null)
62     }
63   }
64
65   private fun writePartialFileState(state: FullTrackerState): Element {
66     val element = Element(NODE_PARTIAL_FILE)
67     element.setAttribute(ATT_PATH, state.virtualFile.path)
68
69     if (Registry.`is`("vcs.enable.partial.changelists.persist.file.contents")) {
70       // TODO: should not be stored in workspace.xml; Project.getProjectCachePath ?
71       element.addContent(Element(NODE_VCS).setAttribute(ATT_CONTENT, XmlStringUtil.escapeIllegalXmlChars(state.vcsContent)))
72       element.addContent(Element(NODE_CURRENT).setAttribute(ATT_CONTENT, XmlStringUtil.escapeIllegalXmlChars(state.currentContent)))
73     }
74
75     val rangesNode = Element(NODE_RANGES)
76     for (it in state.ranges) {
77       rangesNode.addContent(writeRangeState(it))
78     }
79     element.addContent(rangesNode)
80
81     return element
82   }
83
84   private fun readPartialFileState(element: Element): TrackerState? {
85     val path = element.getAttributeValue(ATT_PATH) ?: return null
86     val virtualFile = LocalFileSystem.getInstance().findFileByPath(path) ?: return null
87
88     val vcsContent = element.getChild(NODE_VCS)?.getAttributeValue(ATT_CONTENT)
89     val currentContent = element.getChild(NODE_CURRENT)?.getAttributeValue(ATT_CONTENT)
90
91     val rangeStates = mutableListOf<RangeState>()
92
93     val rangesNode = element.getChild(NODE_RANGES) ?: return null
94     for (node in rangesNode.getChildren(NODE_RANGE)) {
95       val rangeState = readRangeState(node) ?: return null
96       rangeStates.add(rangeState)
97     }
98
99     if (vcsContent != null && currentContent != null) {
100       return FullTrackerState(virtualFile, rangeStates,
101                               XmlStringUtil.unescapeIllegalXmlChars(vcsContent),
102                               XmlStringUtil.unescapeIllegalXmlChars(currentContent))
103     }
104     else {
105       return TrackerState(virtualFile, rangeStates)
106     }
107   }
108
109   private fun writeRangeState(range: RangeState): Element {
110     return Element(NODE_RANGE)
111       .setAttribute(ATT_START_1, range.range.start1.toString())
112       .setAttribute(ATT_END_1, range.range.end1.toString())
113       .setAttribute(ATT_START_2, range.range.start2.toString())
114       .setAttribute(ATT_END_2, range.range.end2.toString())
115       .setAttribute(ATT_CHANGELIST_ID, range.changelistId)
116   }
117
118   private fun readRangeState(node: Element): RangeState? {
119     val start1 = node.getAttributeValue(ATT_START_1)?.toIntOrNull() ?: return null
120     val end1 = node.getAttributeValue(ATT_END_1)?.toIntOrNull() ?: return null
121     val start2 = node.getAttributeValue(ATT_START_2)?.toIntOrNull() ?: return null
122     val end2 = node.getAttributeValue(ATT_END_2)?.toIntOrNull() ?: return null
123     val changelistId = node.getAttributeValue(ATT_CHANGELIST_ID) ?: return null
124     return RangeState(Range(start1, end1, start2, end2), changelistId)
125   }
126 }