Welcome to mirror list, hosted at ThFree Co, Russian Federation.

BeforeTextBufferChangeUndoPrimitive.cs « EditorOperations « Impl « Text « src - github.com/microsoft/vs-editor-api.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 47abd9b9d9c03ab4c20fa1126fb16ffbd227ec52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
//
//  Copyright (c) Microsoft Corporation. All rights reserved.
//  Licensed under the MIT License. See License.txt in the project root for license information.
//
// This file contain implementations details that are subject to change without notice.
// Use at your own risk.
//
namespace Microsoft.VisualStudio.Text.Operations.Implementation
{
    using System;
    using System.Diagnostics;
    using Microsoft.VisualStudio.Text.Editor;

    /// <summary>
    /// The UndoPrimitive to take place on the Undo stack before a text buffer change. This is the simpler
    /// version of the primitive that handles most common cases.
    /// </summary>
    internal class BeforeTextBufferChangeUndoPrimitive : TextUndoPrimitive
    {
        // Think twice before adding any fields here! These objects are long-lived and consume considerable space.
        // Unusual cases should be handled by the GeneralAfterTextBufferChangedUndoPrimitive class below.
        protected ITextUndoHistory _undoHistory;
        protected int _oldCaretIndex;
        protected byte _oldCaretAffinityByte;
        protected bool _canUndo;

        /// <summary>
        /// Constructs a BeforeTextBufferChangeUndoPrimitive.
        /// </summary>
        /// <param name="textView">
        /// The text view that was responsible for causing this change.
        /// </param>
        /// <param name="undoHistory">
        /// The <see cref="ITextUndoHistory" /> this primitive will be added to.
        /// </param>
        /// <exception cref="ArgumentNullException"><paramref name="textView"/> is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="undoHistory"/> is null.</exception>
        public static BeforeTextBufferChangeUndoPrimitive Create(ITextView textView, ITextUndoHistory undoHistory)
        {
            if (textView == null)
            {
                throw new ArgumentNullException("textView");
            }
            if (undoHistory == null)
            {
                throw new ArgumentNullException("undoHistory");
            }

            // Store the ITextView for these changes in the ITextUndoHistory properties so we can retrieve it later.
            if (!undoHistory.Properties.ContainsProperty(typeof(ITextView)))
            {
                undoHistory.Properties[typeof(ITextView)] = textView;
            }

            CaretPosition caret = textView.Caret.Position;

            IMapEditToData map = BeforeTextBufferChangeUndoPrimitive.GetMap(textView);

            int oldCaretIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, caret.BufferPosition);
            int oldCaretVirtualSpaces = caret.VirtualBufferPosition.VirtualSpaces;

            VirtualSnapshotPoint anchor = textView.Selection.AnchorPoint;
            int oldSelectionAnchorIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, anchor.Position);
            int oldSelectionAnchorVirtualSpaces = anchor.VirtualSpaces;

            VirtualSnapshotPoint active = textView.Selection.ActivePoint;
            int oldSelectionActiveIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, active.Position);
            int oldSelectionActiveVirtualSpaces = active.VirtualSpaces;

            TextSelectionMode oldSelectionMode = textView.Selection.Mode;

            if (oldCaretVirtualSpaces != 0 ||
                oldSelectionAnchorIndex != oldCaretIndex ||
                oldSelectionAnchorVirtualSpaces != 0 ||
                oldSelectionActiveIndex != oldCaretIndex ||
                oldSelectionActiveVirtualSpaces != 0 ||
                oldSelectionMode != TextSelectionMode.Stream)
            {
                return new GeneralBeforeTextBufferChangeUndoPrimitive
                            (undoHistory, oldCaretIndex, caret.Affinity, oldCaretVirtualSpaces, oldSelectionAnchorIndex,
                             oldSelectionAnchorVirtualSpaces, oldSelectionActiveIndex, oldSelectionActiveVirtualSpaces, oldSelectionMode);
            }
            else
            {
                return new BeforeTextBufferChangeUndoPrimitive(undoHistory, oldCaretIndex, caret.Affinity);
            }
        }

        //Get the map -- if any -- used to map points in the view's edit buffer to the data buffer. The map is needed because the undo history
        //typically lives on the data buffer, but is used by the view on the edit buffer and a view (if any) on the data buffer. If there isn't
        //a contract that guarantees that the contents of the edit and databuffers are the same, undoing an action on the edit buffer view and
        //then undoing it on the data buffer view will cause cause the undo to try and restore caret/selection (in the data buffer) the coorinates
        //saved in the edit buffer. This isn't good.
        internal static IMapEditToData GetMap(ITextView view)
        {
            IMapEditToData map = null;
            if (view.TextViewModel.EditBuffer != view.TextViewModel.DataBuffer)
            {
                view.Properties.TryGetProperty(typeof(IMapEditToData), out map);
            }

            return map;
        }

        //Map point from a position in the edit buffer to a position in the data buffer (== if there is no map, otherwise ask the map).
        internal static int MapToData(IMapEditToData map, int point)
        {
            return (map != null) ? map.MapEditToData(point) : point;
        }

        //Map point from a position in the data buffer to a position in the edit buffer (== if there is no map, otherwise ask the map).
        internal static int MapToEdit(IMapEditToData map, int point)
        {
            return (map != null) ? map.MapDataToEdit(point) : point;
        }

        protected BeforeTextBufferChangeUndoPrimitive(ITextUndoHistory undoHistory, int caretIndex, PositionAffinity caretAffinity)
        {
            _undoHistory = undoHistory;
            _oldCaretIndex = caretIndex;
            _oldCaretAffinityByte = (byte)caretAffinity;
            _canUndo = true;
        }

        // Internal empty constructor for unit testing.
        internal BeforeTextBufferChangeUndoPrimitive() { }

        /// <summary>
        /// The <see cref="ITextView"/> that this <see cref="BeforeTextBufferChangeUndoPrimitive"/> is bound to.
        /// </summary>
        internal ITextView GetTextView()
        {
            ITextView view = null;
            _undoHistory.Properties.TryGetProperty(typeof(ITextView), out view);
            return view;
        }

        #region UndoPrimitive Members

        /// <summary>
        /// Returns true if operation can be undone, false otherwise.
        /// </summary>
        public override bool CanUndo
        {
            get { return _canUndo; }
        }

        /// <summary>
        /// Returns true if operation can be redone, false otherwise.
        /// </summary>
        public override bool CanRedo
        {
            get { return !_canUndo; }
        }

        /// <summary>
        /// Do the action.
        /// </summary>
        /// <exception cref="InvalidOperationException">Operation cannot be redone.</exception>
        public override void Do()
        {
            // Validate, we shouldn't be allowed to undo
            if (!CanRedo)
            {
                throw new InvalidOperationException(Strings.CannotRedo);
            }

            // Currently, no action is done on the redo.  To set the caret and selection for after a TextBuffer change redo, there is the AfterTextBufferChangeUndoPrimitive.
            // This Redo should not do anything with the caret and selection, because we only want to reset them after the TextBuffer change has occurred.
            // Therefore, we need to add this UndoPrimitive to the undo stack before the UndoPrimitive for the TextBuffer change.  On an undo, the TextBuffer changed UndoPrimitive
            // will fire it's Undo first, and than the Undo for this UndoPrimitive will fire.
            // However, on a redo, the Redo for this UndoPrimitive will be fired, and then the Redo for the TextBuffer change UndoPrimitive.  If we had set any caret placement/selection here (ie the new caret placement/selection), 
            // we may crash because the TextBuffer change has not occurred yet (ie you try to set the caret to be at CharacterIndex 1 when the TextBuffer is still empty).

            _canUndo = true;
        }

        /// <summary>
        /// Undo the action.
        /// </summary>
        /// <exception cref="InvalidOperationException">Operation cannot be undone.</exception>
        public override void Undo()
        {
            // Validate that we can undo this change
            if (!CanUndo)
            {
                throw new InvalidOperationException(Strings.CannotUndo);
            }

            // Restore the old caret position and active selection
            var view = this.GetTextView();
            Debug.Assert(view == null || !view.IsClosed, "Attempt to undo/redo on a closed view?  This shouldn't happen.");
            if (view != null && !view.IsClosed)
            {
                UndoMoveCaretAndSelect(view, BeforeTextBufferChangeUndoPrimitive.GetMap(view));
                view.Caret.EnsureVisible();
            }

            _canUndo = false;
        }

        /// <summary>
        /// Move the caret and restore the selection as part of the Undo operation.
        /// </summary>
        protected virtual void UndoMoveCaretAndSelect(ITextView view, IMapEditToData map)
        {
            SnapshotPoint newCaret = new SnapshotPoint(view.TextSnapshot, MapToEdit(map, _oldCaretIndex));

            view.Caret.MoveTo(new VirtualSnapshotPoint(newCaret), (PositionAffinity)_oldCaretAffinityByte);
            view.Selection.Clear();
        }

        protected virtual int OldCaretVirtualSpaces
        {
            get { return 0; }
        }

        public override bool CanMerge(ITextUndoPrimitive older)
        {
            if (older == null)
            {
                throw new ArgumentNullException("older");
            }

            AfterTextBufferChangeUndoPrimitive olderPrimitive = older as AfterTextBufferChangeUndoPrimitive;
            // We can only merge with IUndoPrimitives of AfterTextBufferChangeUndoPrimitive type
            if (olderPrimitive == null)
            {
                return false;
            }

            return (olderPrimitive.CaretIndex == _oldCaretIndex) && (olderPrimitive.CaretVirtualSpace == OldCaretVirtualSpaces);
        }

        #endregion
    }

    /// <summary>
    /// The UndoPrimitive to take place on the Undo stack before a text buffer change. This is the general
    /// version of the primitive that handles all cases, including those involving selections and virtual space.
    /// </summary>
    internal class GeneralBeforeTextBufferChangeUndoPrimitive : BeforeTextBufferChangeUndoPrimitive
    {
        private int _oldCaretVirtualSpaces;
        private int _oldSelectionAnchorIndex;
        private int _oldSelectionAnchorVirtualSpaces;
        private int _oldSelectionActiveIndex;
        private int _oldSelectionActiveVirtualSpaces;
        private TextSelectionMode _oldSelectionMode;

        public GeneralBeforeTextBufferChangeUndoPrimitive(ITextUndoHistory undoHistory,
                                                          int oldCaretIndex,
                                                          PositionAffinity oldCaretAffinity,
                                                          int oldCaretVirtualSpaces,
                                                          int oldSelectionAnchorIndex,
                                                          int oldSelectionAnchorVirtualSpaces,
                                                          int oldSelectionActiveIndex,
                                                          int oldSelectionActiveVirtualSpaces,
                                                          TextSelectionMode oldSelectionMode)
            : base(undoHistory, oldCaretIndex, oldCaretAffinity)
        {
            _oldCaretVirtualSpaces = oldCaretVirtualSpaces;
            _oldSelectionAnchorIndex = oldSelectionAnchorIndex;
            _oldSelectionAnchorVirtualSpaces = oldSelectionAnchorVirtualSpaces;
            _oldSelectionActiveIndex = oldSelectionActiveIndex;
            _oldSelectionActiveVirtualSpaces = oldSelectionActiveVirtualSpaces;
            _oldSelectionMode = oldSelectionMode;
        }

        /// <summary>
        /// Move the caret and restore the selection as part of the Undo operation.
        /// </summary>
        protected override void UndoMoveCaretAndSelect(ITextView view, IMapEditToData map)
        {
            SnapshotPoint newCaret = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _oldCaretIndex));
            SnapshotPoint newAnchor = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _oldSelectionAnchorIndex));
            SnapshotPoint newActive = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _oldSelectionActiveIndex));

            view.Caret.MoveTo(new VirtualSnapshotPoint(newCaret, _oldCaretVirtualSpaces), (PositionAffinity)_oldCaretAffinityByte);

            view.Selection.Mode = _oldSelectionMode;

            var virtualAnchor = new VirtualSnapshotPoint(newAnchor, _oldSelectionAnchorVirtualSpaces);
            var virtualActive = new VirtualSnapshotPoint(newActive, _oldSelectionActiveVirtualSpaces);

            // Buffer may have been changed by one of the listeners on the caret move event.
            virtualAnchor = virtualAnchor.TranslateTo(view.TextSnapshot);
            virtualActive = virtualActive.TranslateTo(view.TextSnapshot);

            view.Selection.Select(virtualAnchor, virtualActive);
        }

        protected override int OldCaretVirtualSpaces
        {
            get { return _oldCaretVirtualSpaces; }
        }
    }
}