diff: add option to enable align changes in side by side diff
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / tools / util / base / TextDiffSettingsHolder.kt
1 // Copyright 2000-2021 JetBrains s.r.o. and contributors. 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.diff.tools.util.base
3
4 import com.intellij.diff.tools.util.breadcrumbs.BreadcrumbsPlacement
5 import com.intellij.diff.util.DiffPlaces
6 import com.intellij.diff.util.DiffUtil
7 import com.intellij.openapi.Disposable
8 import com.intellij.openapi.components.*
9 import com.intellij.openapi.diff.DiffBundle
10 import com.intellij.openapi.util.Key
11 import com.intellij.util.EventDispatcher
12 import com.intellij.util.xmlb.annotations.OptionTag
13 import com.intellij.util.xmlb.annotations.Transient
14 import com.intellij.util.xmlb.annotations.XMap
15 import org.jetbrains.annotations.NonNls
16 import java.util.*
17
18 @State(name = "TextDiffSettings", storages = [(Storage(value = DiffUtil.DIFF_CONFIG))], category = SettingsCategory.CODE)
19 class TextDiffSettingsHolder : PersistentStateComponent<TextDiffSettingsHolder.State> {
20   companion object {
21     @JvmField val CONTEXT_RANGE_MODES: IntArray = intArrayOf(1, 2, 4, 8, -1)
22     @JvmField val CONTEXT_RANGE_MODE_LABELS: Array<String> = arrayOf("1", "2", "4", "8", DiffBundle.message("configurable.diff.collapse.unchanged.ranges.disable"))
23   }
24
25   data class SharedSettings(
26     // Fragments settings
27     var CONTEXT_RANGE: Int = 4,
28
29     var MERGE_AUTO_APPLY_NON_CONFLICTED_CHANGES: Boolean = false,
30     var MERGE_LST_GUTTER_MARKERS: Boolean = true,
31     var ENABLE_ALIGNING_CHANGES_MODE: Boolean = false
32   )
33
34   data class PlaceSettings(
35     // Diff settings
36     var HIGHLIGHT_POLICY: HighlightPolicy = HighlightPolicy.BY_WORD,
37     var IGNORE_POLICY: IgnorePolicy = IgnorePolicy.DEFAULT,
38
39     // Editor settings
40     var SHOW_WHITESPACES: Boolean = false,
41     var SHOW_LINE_NUMBERS: Boolean = true,
42     var SHOW_INDENT_LINES: Boolean = false,
43     var USE_SOFT_WRAPS: Boolean = false,
44     var HIGHLIGHTING_LEVEL: HighlightingLevel = HighlightingLevel.INSPECTIONS,
45     var READ_ONLY_LOCK: Boolean = true,
46     var BREADCRUMBS_PLACEMENT: BreadcrumbsPlacement = BreadcrumbsPlacement.HIDDEN,
47
48     // Fragments settings
49     var EXPAND_BY_DEFAULT: Boolean = true
50   ) {
51     @Transient
52     val eventDispatcher: EventDispatcher<TextDiffSettings.Listener> = EventDispatcher.create(TextDiffSettings.Listener::class.java)
53   }
54
55   class TextDiffSettings internal constructor(private val SHARED_SETTINGS: SharedSettings,
56                                               private val PLACE_SETTINGS: PlaceSettings,
57                                               val place: String?) {
58     constructor() : this(SharedSettings(), PlaceSettings(), null)
59
60     fun addListener(listener: Listener, disposable: Disposable) {
61       PLACE_SETTINGS.eventDispatcher.addListener(listener, disposable)
62     }
63
64     // Presentation settings
65
66     var isEnableSyncScroll: Boolean = true
67
68     var isEnableAligningChangesMode: Boolean
69       get() = SHARED_SETTINGS.ENABLE_ALIGNING_CHANGES_MODE
70       set(value) { SHARED_SETTINGS.ENABLE_ALIGNING_CHANGES_MODE = value }
71
72     // Diff settings
73
74     var highlightPolicy: HighlightPolicy = PLACE_SETTINGS.HIGHLIGHT_POLICY
75       set(value) {
76         field = value
77         if (value != HighlightPolicy.DO_NOT_HIGHLIGHT) { // do not persist confusing value as new default
78           PLACE_SETTINGS.HIGHLIGHT_POLICY = value
79         }
80         PLACE_SETTINGS.eventDispatcher.multicaster.highlightPolicyChanged()
81       }
82
83     var ignorePolicy: IgnorePolicy
84       get()      = PLACE_SETTINGS.IGNORE_POLICY
85       set(value) { PLACE_SETTINGS.IGNORE_POLICY = value
86                    PLACE_SETTINGS.eventDispatcher.multicaster.ignorePolicyChanged() }
87
88     //
89     // Merge
90     //
91
92     var isAutoApplyNonConflictedChanges: Boolean
93       get()      = SHARED_SETTINGS.MERGE_AUTO_APPLY_NON_CONFLICTED_CHANGES
94       set(value) { SHARED_SETTINGS.MERGE_AUTO_APPLY_NON_CONFLICTED_CHANGES = value }
95
96     var isEnableLstGutterMarkersInMerge: Boolean
97       get()      = SHARED_SETTINGS.MERGE_LST_GUTTER_MARKERS
98       set(value) { SHARED_SETTINGS.MERGE_LST_GUTTER_MARKERS = value }
99
100     // Editor settings
101
102     var isShowLineNumbers: Boolean
103       get()      = PLACE_SETTINGS.SHOW_LINE_NUMBERS
104       set(value) { PLACE_SETTINGS.SHOW_LINE_NUMBERS = value }
105
106     var isShowWhitespaces: Boolean
107       get()      = PLACE_SETTINGS.SHOW_WHITESPACES
108       set(value) { PLACE_SETTINGS.SHOW_WHITESPACES = value }
109
110     var isShowIndentLines: Boolean
111       get()      = PLACE_SETTINGS.SHOW_INDENT_LINES
112       set(value) { PLACE_SETTINGS.SHOW_INDENT_LINES = value }
113
114     var isUseSoftWraps: Boolean
115       get()      = PLACE_SETTINGS.USE_SOFT_WRAPS
116       set(value) { PLACE_SETTINGS.USE_SOFT_WRAPS = value }
117
118     var highlightingLevel: HighlightingLevel
119       get()      = PLACE_SETTINGS.HIGHLIGHTING_LEVEL
120       set(value) { PLACE_SETTINGS.HIGHLIGHTING_LEVEL = value }
121
122     var contextRange: Int
123       get()      = SHARED_SETTINGS.CONTEXT_RANGE
124       set(value) { SHARED_SETTINGS.CONTEXT_RANGE = value }
125
126     var isExpandByDefault: Boolean
127       get()      = PLACE_SETTINGS.EXPAND_BY_DEFAULT
128       set(value) { PLACE_SETTINGS.EXPAND_BY_DEFAULT = value }
129
130     var isReadOnlyLock: Boolean
131       get()      = PLACE_SETTINGS.READ_ONLY_LOCK
132       set(value) { PLACE_SETTINGS.READ_ONLY_LOCK = value }
133
134     var breadcrumbsPlacement: BreadcrumbsPlacement
135       get()      = PLACE_SETTINGS.BREADCRUMBS_PLACEMENT
136       set(value) { PLACE_SETTINGS.BREADCRUMBS_PLACEMENT = value
137                    PLACE_SETTINGS.eventDispatcher.multicaster.breadcrumbsPlacementChanged() }
138
139     //
140     // Impl
141     //
142
143     companion object {
144       @JvmField val KEY: Key<TextDiffSettings> = Key.create("TextDiffSettings")
145
146       @JvmStatic fun getSettings(): TextDiffSettings = getSettings(null)
147       @JvmStatic fun getSettings(place: String?): TextDiffSettings = service<TextDiffSettingsHolder>().getSettings(place)
148       internal fun getDefaultSettings(place: String): TextDiffSettings =
149         TextDiffSettings(SharedSettings(), service<TextDiffSettingsHolder>().defaultPlaceSettings(place), place)
150     }
151
152     interface Listener : EventListener {
153       fun highlightPolicyChanged() {}
154       fun ignorePolicyChanged() {}
155       fun breadcrumbsPlacementChanged() {}
156
157       abstract class Adapter : Listener
158     }
159   }
160
161   fun getSettings(@NonNls place: String?): TextDiffSettings {
162     val placeKey = place ?: DiffPlaces.DEFAULT
163     val placeSettings = myState.PLACES_MAP.getOrPut(placeKey) { defaultPlaceSettings(placeKey) }
164     return TextDiffSettings(myState.SHARED_SETTINGS, placeSettings, placeKey)
165   }
166
167   private fun copyStateWithoutDefaults(): State {
168     val result = State()
169     result.SHARED_SETTINGS = myState.SHARED_SETTINGS
170     result.PLACES_MAP = DiffUtil.trimDefaultValues(myState.PLACES_MAP) { defaultPlaceSettings(it) }
171     return result
172   }
173
174   private fun defaultPlaceSettings(place: String): PlaceSettings {
175     val settings = PlaceSettings()
176     if (place == DiffPlaces.CHANGES_VIEW) {
177       settings.EXPAND_BY_DEFAULT = false
178       settings.SHOW_LINE_NUMBERS = false
179     }
180     if (place == DiffPlaces.COMMIT_DIALOG) {
181       settings.EXPAND_BY_DEFAULT = false
182     }
183     if (place == DiffPlaces.VCS_LOG_VIEW) {
184       settings.EXPAND_BY_DEFAULT = false
185     }
186     if (place == DiffPlaces.VCS_FILE_HISTORY_VIEW) {
187       settings.EXPAND_BY_DEFAULT = false
188     }
189     return settings
190   }
191
192
193   class State {
194     @OptionTag
195     @XMap
196     @JvmField var PLACES_MAP: TreeMap<String, PlaceSettings> = TreeMap()
197     @JvmField var SHARED_SETTINGS: SharedSettings = SharedSettings()
198   }
199
200   private var myState: State = State()
201
202   override fun getState(): State {
203     return copyStateWithoutDefaults()
204   }
205
206   override fun loadState(state: State) {
207     myState = state
208   }
209 }