1 | // Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team |
---|
2 | // |
---|
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this |
---|
4 | // software and associated documentation files (the "Software"), to deal in the Software |
---|
5 | // without restriction, including without limitation the rights to use, copy, modify, merge, |
---|
6 | // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
---|
7 | // to whom the Software is furnished to do so, subject to the following conditions: |
---|
8 | // |
---|
9 | // The above copyright notice and this permission notice shall be included in all copies or |
---|
10 | // substantial portions of the Software. |
---|
11 | // |
---|
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
---|
13 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
---|
14 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
---|
15 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
---|
16 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
---|
17 | // DEALINGS IN THE SOFTWARE. |
---|
18 | |
---|
19 | using System; |
---|
20 | using System.Collections.Generic; |
---|
21 | using System.Text; |
---|
22 | using System.Windows; |
---|
23 | |
---|
24 | using ICSharpCode.AvalonEdit.Document; |
---|
25 | using ICSharpCode.AvalonEdit.Highlighting; |
---|
26 | using ICSharpCode.AvalonEdit.Utils; |
---|
27 | #if NREFACTORY |
---|
28 | using ICSharpCode.NRefactory.Editor; |
---|
29 | #endif |
---|
30 | |
---|
31 | namespace ICSharpCode.AvalonEdit.Editing |
---|
32 | { |
---|
33 | /// <summary> |
---|
34 | /// Base class for selections. |
---|
35 | /// </summary> |
---|
36 | public abstract class Selection |
---|
37 | { |
---|
38 | /// <summary> |
---|
39 | /// Creates a new simple selection that selects the text from startOffset to endOffset. |
---|
40 | /// </summary> |
---|
41 | public static Selection Create(TextArea textArea, int startOffset, int endOffset) |
---|
42 | { |
---|
43 | if (textArea == null) |
---|
44 | throw new ArgumentNullException("textArea"); |
---|
45 | if (startOffset == endOffset) |
---|
46 | return textArea.emptySelection; |
---|
47 | else |
---|
48 | return new SimpleSelection(textArea, |
---|
49 | new TextViewPosition(textArea.Document.GetLocation(startOffset)), |
---|
50 | new TextViewPosition(textArea.Document.GetLocation(endOffset))); |
---|
51 | } |
---|
52 | |
---|
53 | internal static Selection Create(TextArea textArea, TextViewPosition start, TextViewPosition end) |
---|
54 | { |
---|
55 | if (textArea == null) |
---|
56 | throw new ArgumentNullException("textArea"); |
---|
57 | if (textArea.Document.GetOffset(start.Location) == textArea.Document.GetOffset(end.Location) && start.VisualColumn == end.VisualColumn) |
---|
58 | return textArea.emptySelection; |
---|
59 | else |
---|
60 | return new SimpleSelection(textArea, start, end); |
---|
61 | } |
---|
62 | |
---|
63 | /// <summary> |
---|
64 | /// Creates a new simple selection that selects the text in the specified segment. |
---|
65 | /// </summary> |
---|
66 | public static Selection Create(TextArea textArea, ISegment segment) |
---|
67 | { |
---|
68 | if (segment == null) |
---|
69 | throw new ArgumentNullException("segment"); |
---|
70 | return Create(textArea, segment.Offset, segment.EndOffset); |
---|
71 | } |
---|
72 | |
---|
73 | internal readonly TextArea textArea; |
---|
74 | |
---|
75 | /// <summary> |
---|
76 | /// Constructor for Selection. |
---|
77 | /// </summary> |
---|
78 | protected Selection(TextArea textArea) |
---|
79 | { |
---|
80 | if (textArea == null) |
---|
81 | throw new ArgumentNullException("textArea"); |
---|
82 | this.textArea = textArea; |
---|
83 | } |
---|
84 | |
---|
85 | /// <summary> |
---|
86 | /// Gets the start position of the selection. |
---|
87 | /// </summary> |
---|
88 | public abstract TextViewPosition StartPosition { get; } |
---|
89 | |
---|
90 | /// <summary> |
---|
91 | /// Gets the end position of the selection. |
---|
92 | /// </summary> |
---|
93 | public abstract TextViewPosition EndPosition { get; } |
---|
94 | |
---|
95 | /// <summary> |
---|
96 | /// Gets the selected text segments. |
---|
97 | /// </summary> |
---|
98 | public abstract IEnumerable<SelectionSegment> Segments { get; } |
---|
99 | |
---|
100 | /// <summary> |
---|
101 | /// Gets the smallest segment that contains all segments in this selection. |
---|
102 | /// May return null if the selection is empty. |
---|
103 | /// </summary> |
---|
104 | public abstract ISegment SurroundingSegment { get; } |
---|
105 | |
---|
106 | /// <summary> |
---|
107 | /// Replaces the selection with the specified text. |
---|
108 | /// </summary> |
---|
109 | public abstract void ReplaceSelectionWithText(string newText); |
---|
110 | |
---|
111 | internal string AddSpacesIfRequired(string newText, TextViewPosition start, TextViewPosition end) |
---|
112 | { |
---|
113 | if (EnableVirtualSpace && InsertVirtualSpaces(newText, start, end)) { |
---|
114 | var line = textArea.Document.GetLineByNumber(start.Line); |
---|
115 | string lineText = textArea.Document.GetText(line); |
---|
116 | var vLine = textArea.TextView.GetOrConstructVisualLine(line); |
---|
117 | int colDiff = start.VisualColumn - vLine.VisualLengthWithEndOfLineMarker; |
---|
118 | if (colDiff > 0) { |
---|
119 | string additionalSpaces = ""; |
---|
120 | if (!textArea.Options.ConvertTabsToSpaces && lineText.Trim('\t').Length == 0) { |
---|
121 | int tabCount = (int)(colDiff / textArea.Options.IndentationSize); |
---|
122 | additionalSpaces = new string('\t', tabCount); |
---|
123 | colDiff -= tabCount * textArea.Options.IndentationSize; |
---|
124 | } |
---|
125 | additionalSpaces += new string(' ', colDiff); |
---|
126 | return additionalSpaces + newText; |
---|
127 | } |
---|
128 | } |
---|
129 | return newText; |
---|
130 | } |
---|
131 | |
---|
132 | bool InsertVirtualSpaces(string newText, TextViewPosition start, TextViewPosition end) |
---|
133 | { |
---|
134 | return (!string.IsNullOrEmpty(newText) || !(IsInVirtualSpace(start) && IsInVirtualSpace(end))) |
---|
135 | && newText != "\r\n" |
---|
136 | && newText != "\n" |
---|
137 | && newText != "\r"; |
---|
138 | } |
---|
139 | |
---|
140 | bool IsInVirtualSpace(TextViewPosition pos) |
---|
141 | { |
---|
142 | return pos.VisualColumn > textArea.TextView.GetOrConstructVisualLine(textArea.Document.GetLineByNumber(pos.Line)).VisualLength; |
---|
143 | } |
---|
144 | |
---|
145 | /// <summary> |
---|
146 | /// Updates the selection when the document changes. |
---|
147 | /// </summary> |
---|
148 | public abstract Selection UpdateOnDocumentChange(DocumentChangeEventArgs e); |
---|
149 | |
---|
150 | /// <summary> |
---|
151 | /// Gets whether the selection is empty. |
---|
152 | /// </summary> |
---|
153 | public virtual bool IsEmpty { |
---|
154 | get { return Length == 0; } |
---|
155 | } |
---|
156 | |
---|
157 | /// <summary> |
---|
158 | /// Gets whether virtual space is enabled for this selection. |
---|
159 | /// </summary> |
---|
160 | public virtual bool EnableVirtualSpace { |
---|
161 | get { return textArea.Options.EnableVirtualSpace; } |
---|
162 | } |
---|
163 | |
---|
164 | /// <summary> |
---|
165 | /// Gets the selection length. |
---|
166 | /// </summary> |
---|
167 | public abstract int Length { get; } |
---|
168 | |
---|
169 | /// <summary> |
---|
170 | /// Returns a new selection with the changed end point. |
---|
171 | /// </summary> |
---|
172 | /// <exception cref="NotSupportedException">Cannot set endpoint for empty selection</exception> |
---|
173 | public abstract Selection SetEndpoint(TextViewPosition endPosition); |
---|
174 | |
---|
175 | /// <summary> |
---|
176 | /// If this selection is empty, starts a new selection from <paramref name="startPosition"/> to |
---|
177 | /// <paramref name="endPosition"/>, otherwise, changes the endpoint of this selection. |
---|
178 | /// </summary> |
---|
179 | public abstract Selection StartSelectionOrSetEndpoint(TextViewPosition startPosition, TextViewPosition endPosition); |
---|
180 | |
---|
181 | /// <summary> |
---|
182 | /// Gets whether the selection is multi-line. |
---|
183 | /// </summary> |
---|
184 | public virtual bool IsMultiline { |
---|
185 | get { |
---|
186 | ISegment surroundingSegment = this.SurroundingSegment; |
---|
187 | if (surroundingSegment == null) |
---|
188 | return false; |
---|
189 | int start = surroundingSegment.Offset; |
---|
190 | int end = start + surroundingSegment.Length; |
---|
191 | var document = textArea.Document; |
---|
192 | if (document == null) |
---|
193 | throw ThrowUtil.NoDocumentAssigned(); |
---|
194 | return document.GetLineByOffset(start) != document.GetLineByOffset(end); |
---|
195 | } |
---|
196 | } |
---|
197 | |
---|
198 | /// <summary> |
---|
199 | /// Gets the selected text. |
---|
200 | /// </summary> |
---|
201 | public virtual string GetText() |
---|
202 | { |
---|
203 | var document = textArea.Document; |
---|
204 | if (document == null) |
---|
205 | throw ThrowUtil.NoDocumentAssigned(); |
---|
206 | StringBuilder b = null; |
---|
207 | string text = null; |
---|
208 | foreach (ISegment s in Segments) { |
---|
209 | if (text != null) { |
---|
210 | if (b == null) |
---|
211 | b = new StringBuilder(text); |
---|
212 | else |
---|
213 | b.Append(text); |
---|
214 | } |
---|
215 | text = document.GetText(s); |
---|
216 | } |
---|
217 | if (b != null) { |
---|
218 | if (text != null) b.Append(text); |
---|
219 | return b.ToString(); |
---|
220 | } else { |
---|
221 | return text ?? string.Empty; |
---|
222 | } |
---|
223 | } |
---|
224 | |
---|
225 | /// <summary> |
---|
226 | /// Creates a HTML fragment for the selected text. |
---|
227 | /// </summary> |
---|
228 | public string CreateHtmlFragment(HtmlOptions options) |
---|
229 | { |
---|
230 | if (options == null) |
---|
231 | throw new ArgumentNullException("options"); |
---|
232 | IHighlighter highlighter = textArea.GetService(typeof(IHighlighter)) as IHighlighter; |
---|
233 | StringBuilder html = new StringBuilder(); |
---|
234 | bool first = true; |
---|
235 | foreach (ISegment selectedSegment in this.Segments) { |
---|
236 | if (first) |
---|
237 | first = false; |
---|
238 | else |
---|
239 | html.AppendLine("<br>"); |
---|
240 | html.Append(HtmlClipboard.CreateHtmlFragment(textArea.Document, highlighter, selectedSegment, options)); |
---|
241 | } |
---|
242 | return html.ToString(); |
---|
243 | } |
---|
244 | |
---|
245 | /// <inheritdoc/> |
---|
246 | public abstract override bool Equals(object obj); |
---|
247 | |
---|
248 | /// <inheritdoc/> |
---|
249 | public abstract override int GetHashCode(); |
---|
250 | |
---|
251 | /// <summary> |
---|
252 | /// Gets whether the specified offset is included in the selection. |
---|
253 | /// </summary> |
---|
254 | /// <returns>True, if the selection contains the offset (selection borders inclusive); |
---|
255 | /// otherwise, false.</returns> |
---|
256 | public virtual bool Contains(int offset) |
---|
257 | { |
---|
258 | if (this.IsEmpty) |
---|
259 | return false; |
---|
260 | if (this.SurroundingSegment.Contains(offset, 0)) { |
---|
261 | foreach (ISegment s in this.Segments) { |
---|
262 | if (s.Contains(offset, 0)) { |
---|
263 | return true; |
---|
264 | } |
---|
265 | } |
---|
266 | } |
---|
267 | return false; |
---|
268 | } |
---|
269 | |
---|
270 | /// <summary> |
---|
271 | /// Creates a data object containing the selection's text. |
---|
272 | /// </summary> |
---|
273 | public virtual DataObject CreateDataObject(TextArea textArea) |
---|
274 | { |
---|
275 | DataObject data = new DataObject(); |
---|
276 | |
---|
277 | // Ensure we use the appropriate newline sequence for the OS |
---|
278 | string text = TextUtilities.NormalizeNewLines(GetText(), Environment.NewLine); |
---|
279 | |
---|
280 | // Enable drag/drop to Word, Notepad++ and others |
---|
281 | if (EditingCommandHandler.ConfirmDataFormat(textArea, data, DataFormats.UnicodeText)) { |
---|
282 | data.SetText(text); |
---|
283 | } |
---|
284 | |
---|
285 | // Enable drag/drop to SciTe: |
---|
286 | // We cannot use SetText, thus we need to use typeof(string).FullName as data format. |
---|
287 | // new DataObject(object) calls SetData(object), which in turn calls SetData(Type, data), |
---|
288 | // which then uses Type.FullName as format. |
---|
289 | // We immitate that behavior here as well: |
---|
290 | if (EditingCommandHandler.ConfirmDataFormat(textArea, data, typeof(string).FullName)) { |
---|
291 | data.SetData(typeof(string).FullName, text); |
---|
292 | } |
---|
293 | |
---|
294 | // Also copy text in HTML format to clipboard - good for pasting text into Word |
---|
295 | // or to the SharpDevelop forums. |
---|
296 | if (EditingCommandHandler.ConfirmDataFormat(textArea, data, DataFormats.Html)) { |
---|
297 | HtmlClipboard.SetHtml(data, CreateHtmlFragment(new HtmlOptions(textArea.Options))); |
---|
298 | } |
---|
299 | return data; |
---|
300 | } |
---|
301 | } |
---|
302 | } |
---|