1 | // |
---|
2 | // Script.cs |
---|
3 | // |
---|
4 | // Author: |
---|
5 | // Mike Krüger <mkrueger@novell.com> |
---|
6 | // |
---|
7 | // Copyright (c) 2011 Mike Krüger <mkrueger@novell.com> |
---|
8 | // |
---|
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
---|
10 | // of this software and associated documentation files (the "Software"), to deal |
---|
11 | // in the Software without restriction, including without limitation the rights |
---|
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
---|
13 | // copies of the Software, and to permit persons to whom the Software is |
---|
14 | // furnished to do so, subject to the following conditions: |
---|
15 | // |
---|
16 | // The above copyright notice and this permission notice shall be included in |
---|
17 | // all copies or substantial portions of the Software. |
---|
18 | // |
---|
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
---|
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
---|
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
---|
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
---|
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
---|
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
---|
25 | // THE SOFTWARE. |
---|
26 | using System; |
---|
27 | using System.Collections.Generic; |
---|
28 | using System.Diagnostics; |
---|
29 | using System.IO; |
---|
30 | using ICSharpCode.NRefactory.Editor; |
---|
31 | using ICSharpCode.NRefactory.TypeSystem; |
---|
32 | using System.Threading.Tasks; |
---|
33 | using System.Linq; |
---|
34 | using System.Text; |
---|
35 | using Mono.CSharp; |
---|
36 | using ITypeDefinition = ICSharpCode.NRefactory.TypeSystem.ITypeDefinition; |
---|
37 | |
---|
38 | namespace ICSharpCode.NRefactory.CSharp.Refactoring |
---|
39 | { |
---|
40 | /// <summary> |
---|
41 | /// Class for creating change scripts. |
---|
42 | /// 'Original document' = document without the change script applied. |
---|
43 | /// 'Current document' = document with the change script (as far as it is already created) applies. |
---|
44 | /// </summary> |
---|
45 | public abstract class Script : IDisposable |
---|
46 | { |
---|
47 | internal struct Segment : ISegment |
---|
48 | { |
---|
49 | readonly int offset; |
---|
50 | readonly int length; |
---|
51 | |
---|
52 | public int Offset { |
---|
53 | get { return offset; } |
---|
54 | } |
---|
55 | |
---|
56 | public int Length { |
---|
57 | get { return length; } |
---|
58 | } |
---|
59 | |
---|
60 | public int EndOffset { |
---|
61 | get { return Offset + Length; } |
---|
62 | } |
---|
63 | |
---|
64 | public Segment (int offset, int length) |
---|
65 | { |
---|
66 | this.offset = offset; |
---|
67 | this.length = length; |
---|
68 | } |
---|
69 | |
---|
70 | public override string ToString () |
---|
71 | { |
---|
72 | return string.Format ("[Script.Segment: Offset={0}, Length={1}, EndOffset={2}]", Offset, Length, EndOffset); |
---|
73 | } |
---|
74 | } |
---|
75 | |
---|
76 | readonly CSharpFormattingOptions formattingOptions; |
---|
77 | readonly TextEditorOptions options; |
---|
78 | readonly Dictionary<AstNode, ISegment> segmentsForInsertedNodes = new Dictionary<AstNode, ISegment>(); |
---|
79 | |
---|
80 | protected Script(CSharpFormattingOptions formattingOptions, TextEditorOptions options) |
---|
81 | { |
---|
82 | if (formattingOptions == null) |
---|
83 | throw new ArgumentNullException("formattingOptions"); |
---|
84 | if (options == null) |
---|
85 | throw new ArgumentNullException("options"); |
---|
86 | this.formattingOptions = formattingOptions; |
---|
87 | this.options = options; |
---|
88 | } |
---|
89 | |
---|
90 | /// <summary> |
---|
91 | /// Given an offset in the original document (at the start of script execution), |
---|
92 | /// returns the offset in the current document. |
---|
93 | /// </summary> |
---|
94 | public abstract int GetCurrentOffset(int originalDocumentOffset); |
---|
95 | |
---|
96 | /// <summary> |
---|
97 | /// Given an offset in the original document (at the start of script execution), |
---|
98 | /// returns the offset in the current document. |
---|
99 | /// </summary> |
---|
100 | public abstract int GetCurrentOffset(TextLocation originalDocumentLocation); |
---|
101 | |
---|
102 | /// <summary> |
---|
103 | /// Creates a tracked segment for the specified (offset,length)-segment. |
---|
104 | /// Offset is interpreted to be an offset in the current document. |
---|
105 | /// </summary> |
---|
106 | /// <returns> |
---|
107 | /// A segment that initially has the specified values, and updates |
---|
108 | /// on every <see cref="Replace(int,int,string)"/> call. |
---|
109 | /// </returns> |
---|
110 | protected abstract ISegment CreateTrackedSegment(int offset, int length); |
---|
111 | |
---|
112 | /// <summary> |
---|
113 | /// Gets the current text segment of the specified AstNode. |
---|
114 | /// </summary> |
---|
115 | /// <param name="node">The node to get the segment for.</param> |
---|
116 | public ISegment GetSegment(AstNode node) |
---|
117 | { |
---|
118 | ISegment segment; |
---|
119 | if (segmentsForInsertedNodes.TryGetValue(node, out segment)) |
---|
120 | return segment; |
---|
121 | if (node.StartLocation.IsEmpty || node.EndLocation.IsEmpty) { |
---|
122 | throw new InvalidOperationException("Trying to get the position of a node that is not part of the original document and was not inserted"); |
---|
123 | } |
---|
124 | int startOffset = GetCurrentOffset(node.StartLocation); |
---|
125 | int endOffset = GetCurrentOffset(node.EndLocation); |
---|
126 | return new Segment(startOffset, endOffset - startOffset); |
---|
127 | } |
---|
128 | |
---|
129 | /// <summary> |
---|
130 | /// Replaces text. |
---|
131 | /// </summary> |
---|
132 | /// <param name="offset">The starting offset of the text to be replaced.</param> |
---|
133 | /// <param name="length">The length of the text to be replaced.</param> |
---|
134 | /// <param name="newText">The new text.</param> |
---|
135 | public abstract void Replace (int offset, int length, string newText); |
---|
136 | |
---|
137 | public void InsertText(int offset, string newText) |
---|
138 | { |
---|
139 | Replace(offset, 0, newText); |
---|
140 | } |
---|
141 | |
---|
142 | public void RemoveText(int offset, int length) |
---|
143 | { |
---|
144 | Replace(offset, length, ""); |
---|
145 | } |
---|
146 | |
---|
147 | public CSharpFormattingOptions FormattingOptions { |
---|
148 | get { return formattingOptions; } |
---|
149 | } |
---|
150 | |
---|
151 | public TextEditorOptions Options { |
---|
152 | get { return options; } |
---|
153 | } |
---|
154 | |
---|
155 | public void InsertBefore(AstNode node, AstNode newNode) |
---|
156 | { |
---|
157 | var startOffset = GetCurrentOffset(new TextLocation(node.StartLocation.Line, 1)); |
---|
158 | var output = OutputNode (GetIndentLevelAt (startOffset), newNode); |
---|
159 | string text = output.Text; |
---|
160 | if (!(newNode is Expression || newNode is AstType)) |
---|
161 | text += Options.EolMarker; |
---|
162 | InsertText(startOffset, text); |
---|
163 | output.RegisterTrackedSegments(this, startOffset); |
---|
164 | CorrectFormatting (node, newNode); |
---|
165 | } |
---|
166 | |
---|
167 | public void InsertAfter(AstNode node, AstNode newNode) |
---|
168 | { |
---|
169 | var indentLevel = IndentLevelFor(node); |
---|
170 | var output = OutputNode(indentLevel, newNode); |
---|
171 | string text = PrefixFor(node, newNode) + output.Text; |
---|
172 | |
---|
173 | var insertOffset = GetCurrentOffset(node.EndLocation); |
---|
174 | InsertText(insertOffset, text); |
---|
175 | output.RegisterTrackedSegments(this, insertOffset); |
---|
176 | CorrectFormatting (node, newNode); |
---|
177 | } |
---|
178 | |
---|
179 | private int IndentLevelFor(AstNode node) |
---|
180 | { |
---|
181 | if (!DoesInsertingAfterRequireNewline(node)) |
---|
182 | return 0; |
---|
183 | |
---|
184 | return GetIndentLevelAt(GetCurrentOffset(new TextLocation(node.StartLocation.Line, 1))); |
---|
185 | } |
---|
186 | |
---|
187 | bool DoesInsertingAfterRequireNewline(AstNode node) |
---|
188 | { |
---|
189 | if (node is Expression) |
---|
190 | return false; |
---|
191 | |
---|
192 | if (node is AstType) |
---|
193 | return false; |
---|
194 | |
---|
195 | if (node is ParameterDeclaration) |
---|
196 | return false; |
---|
197 | |
---|
198 | var token = node as CSharpTokenNode; |
---|
199 | if (token != null && token.Role == Roles.LPar) |
---|
200 | return false; |
---|
201 | |
---|
202 | return true; |
---|
203 | } |
---|
204 | |
---|
205 | private string PrefixFor(AstNode node, AstNode newNode) |
---|
206 | { |
---|
207 | if (DoesInsertingAfterRequireNewline(node)) |
---|
208 | return Options.EolMarker; |
---|
209 | |
---|
210 | if (newNode is ParameterDeclaration && node is ParameterDeclaration) |
---|
211 | //todo: worry about adding characters to the document without matching AstNode's. |
---|
212 | return ", "; |
---|
213 | |
---|
214 | return String.Empty; |
---|
215 | } |
---|
216 | |
---|
217 | public void AddTo(BlockStatement bodyStatement, AstNode newNode) |
---|
218 | { |
---|
219 | var startOffset = GetCurrentOffset(bodyStatement.LBraceToken.EndLocation); |
---|
220 | var output = OutputNode(1 + GetIndentLevelAt(startOffset), newNode, true); |
---|
221 | InsertText(startOffset, output.Text); |
---|
222 | output.RegisterTrackedSegments(this, startOffset); |
---|
223 | CorrectFormatting (null, newNode); |
---|
224 | } |
---|
225 | |
---|
226 | public void AddTo(TypeDeclaration typeDecl, EntityDeclaration entityDecl) |
---|
227 | { |
---|
228 | var startOffset = GetCurrentOffset(typeDecl.LBraceToken.EndLocation); |
---|
229 | var output = OutputNode(1 + GetIndentLevelAt(startOffset), entityDecl, true); |
---|
230 | InsertText(startOffset, output.Text); |
---|
231 | output.RegisterTrackedSegments(this, startOffset); |
---|
232 | CorrectFormatting (null, entityDecl); |
---|
233 | } |
---|
234 | |
---|
235 | /// <summary> |
---|
236 | /// Changes the modifier of a given entity declaration. |
---|
237 | /// </summary> |
---|
238 | /// <param name="entity">The entity.</param> |
---|
239 | /// <param name="modifiers">The new modifiers.</param> |
---|
240 | public void ChangeModifier(EntityDeclaration entity, Modifiers modifiers) |
---|
241 | { |
---|
242 | var dummyEntity = new MethodDeclaration (); |
---|
243 | dummyEntity.Modifiers = modifiers; |
---|
244 | |
---|
245 | int offset; |
---|
246 | int endOffset; |
---|
247 | |
---|
248 | if (entity.ModifierTokens.Any ()) { |
---|
249 | offset = GetCurrentOffset(entity.ModifierTokens.First ().StartLocation); |
---|
250 | endOffset = GetCurrentOffset(entity.ModifierTokens.Last ().GetNextSibling (s => s.Role != Roles.NewLine && s.Role != Roles.Whitespace).StartLocation); |
---|
251 | } else { |
---|
252 | var child = entity.FirstChild; |
---|
253 | while (child.NodeType == NodeType.Whitespace || |
---|
254 | child.Role == EntityDeclaration.AttributeRole || |
---|
255 | child.Role == Roles.NewLine) { |
---|
256 | child = child.NextSibling; |
---|
257 | } |
---|
258 | offset = endOffset = GetCurrentOffset(child.StartLocation); |
---|
259 | } |
---|
260 | |
---|
261 | var sb = new StringBuilder(); |
---|
262 | foreach (var modifier in dummyEntity.ModifierTokens) { |
---|
263 | sb.Append(modifier.ToString()); |
---|
264 | sb.Append(' '); |
---|
265 | } |
---|
266 | |
---|
267 | Replace(offset, endOffset - offset, sb.ToString()); |
---|
268 | } |
---|
269 | |
---|
270 | public void ChangeModifier(ParameterDeclaration param, ParameterModifier modifier) |
---|
271 | { |
---|
272 | var child = param.FirstChild; |
---|
273 | Func<AstNode, bool> pred = s => s.Role == ParameterDeclaration.RefModifierRole || s.Role == ParameterDeclaration.OutModifierRole || s.Role == ParameterDeclaration.ParamsModifierRole || s.Role == ParameterDeclaration.ThisModifierRole; |
---|
274 | if (!pred(child)) |
---|
275 | child = child.GetNextSibling(pred); |
---|
276 | |
---|
277 | int offset; |
---|
278 | int endOffset; |
---|
279 | |
---|
280 | if (child != null) { |
---|
281 | offset = GetCurrentOffset(child.StartLocation); |
---|
282 | endOffset = GetCurrentOffset(child.GetNextSibling (s => s.Role != Roles.NewLine && s.Role != Roles.Whitespace).StartLocation); |
---|
283 | } else { |
---|
284 | offset = endOffset = GetCurrentOffset(param.Type.StartLocation); |
---|
285 | } |
---|
286 | string modString; |
---|
287 | switch (modifier) { |
---|
288 | case ParameterModifier.None: |
---|
289 | modString = ""; |
---|
290 | break; |
---|
291 | case ParameterModifier.Ref: |
---|
292 | modString = "ref "; |
---|
293 | break; |
---|
294 | case ParameterModifier.Out: |
---|
295 | modString = "out "; |
---|
296 | break; |
---|
297 | case ParameterModifier.Params: |
---|
298 | modString = "params "; |
---|
299 | break; |
---|
300 | case ParameterModifier.This: |
---|
301 | modString = "this "; |
---|
302 | break; |
---|
303 | default: |
---|
304 | throw new ArgumentOutOfRangeException(); |
---|
305 | } |
---|
306 | Replace(offset, endOffset - offset, modString); |
---|
307 | } |
---|
308 | |
---|
309 | /// <summary> |
---|
310 | /// Changes the base types of a type declaration. |
---|
311 | /// </summary> |
---|
312 | /// <param name="type">The type declaration to modify.</param> |
---|
313 | /// <param name="baseTypes">The new base types.</param> |
---|
314 | public void ChangeBaseTypes(TypeDeclaration type, IEnumerable<AstType> baseTypes) |
---|
315 | { |
---|
316 | var dummyType = new TypeDeclaration(); |
---|
317 | dummyType.BaseTypes.AddRange(baseTypes); |
---|
318 | |
---|
319 | int offset; |
---|
320 | int endOffset; |
---|
321 | var sb = new StringBuilder(); |
---|
322 | |
---|
323 | if (type.BaseTypes.Any ()) { |
---|
324 | offset = GetCurrentOffset(type.ColonToken.StartLocation); |
---|
325 | endOffset = GetCurrentOffset(type.BaseTypes.Last ().EndLocation); |
---|
326 | } else { |
---|
327 | sb.Append(' '); |
---|
328 | if (type.TypeParameters.Any()) { |
---|
329 | offset = endOffset = GetCurrentOffset(type.RChevronToken.EndLocation); |
---|
330 | } else { |
---|
331 | offset = endOffset = GetCurrentOffset(type.NameToken.EndLocation); |
---|
332 | } |
---|
333 | } |
---|
334 | |
---|
335 | if (dummyType.BaseTypes.Any()) { |
---|
336 | sb.Append(": "); |
---|
337 | sb.Append(string.Join(", ", dummyType.BaseTypes)); |
---|
338 | } |
---|
339 | |
---|
340 | Replace(offset, endOffset - offset, sb.ToString()); |
---|
341 | FormatText(type); |
---|
342 | } |
---|
343 | |
---|
344 | |
---|
345 | /// <summary> |
---|
346 | /// Adds an attribute section to a given entity. |
---|
347 | /// </summary> |
---|
348 | /// <param name="entity">The entity to add the attribute to.</param> |
---|
349 | /// <param name="attr">The attribute to add.</param> |
---|
350 | public void AddAttribute(EntityDeclaration entity, AttributeSection attr) |
---|
351 | { |
---|
352 | var node = entity.FirstChild; |
---|
353 | while (node.NodeType == NodeType.Whitespace || node.Role == Roles.Attribute) { |
---|
354 | node = node.NextSibling; |
---|
355 | } |
---|
356 | InsertBefore(node, attr); |
---|
357 | } |
---|
358 | |
---|
359 | public virtual Task Link (params AstNode[] nodes) |
---|
360 | { |
---|
361 | // Default implementation: do nothing |
---|
362 | // Derived classes are supposed to enter the text editor's linked state. |
---|
363 | |
---|
364 | // Immediately signal the task as completed: |
---|
365 | var tcs = new TaskCompletionSource<object>(); |
---|
366 | tcs.SetResult(null); |
---|
367 | return tcs.Task; |
---|
368 | } |
---|
369 | |
---|
370 | public virtual Task Link (IEnumerable<AstNode> nodes) |
---|
371 | { |
---|
372 | return Link(nodes.ToArray()); |
---|
373 | } |
---|
374 | |
---|
375 | public void Replace (AstNode node, AstNode replaceWith) |
---|
376 | { |
---|
377 | var segment = GetSegment (node); |
---|
378 | int startOffset = segment.Offset; |
---|
379 | int level = 0; |
---|
380 | if (!(replaceWith is Expression) && !(replaceWith is AstType)) |
---|
381 | level = GetIndentLevelAt (startOffset); |
---|
382 | NodeOutput output = OutputNode (level, replaceWith); |
---|
383 | output.TrimStart (); |
---|
384 | Replace (startOffset, segment.Length, output.Text); |
---|
385 | output.RegisterTrackedSegments(this, startOffset); |
---|
386 | CorrectFormatting (node, node); |
---|
387 | } |
---|
388 | |
---|
389 | List<AstNode> nodesToFormat = new List<AstNode> (); |
---|
390 | |
---|
391 | void CorrectFormatting(AstNode node, AstNode newNode) |
---|
392 | { |
---|
393 | if (node is Identifier || node is IdentifierExpression || node is CSharpTokenNode || node is AstType) |
---|
394 | return; |
---|
395 | if (node == null || node.Parent is BlockStatement) { |
---|
396 | nodesToFormat.Add (newNode); |
---|
397 | } else { |
---|
398 | nodesToFormat.Add ((node.Parent != null && (node.Parent is Statement || node.Parent is Expression || node.Parent is VariableInitializer)) ? node.Parent : newNode); |
---|
399 | } |
---|
400 | } |
---|
401 | |
---|
402 | public abstract void Remove (AstNode node, bool removeEmptyLine = true); |
---|
403 | |
---|
404 | /// <summary> |
---|
405 | /// Safely removes an attribue from it's section (removes empty sections). |
---|
406 | /// </summary> |
---|
407 | /// <param name="attr">The attribute to be removed.</param> |
---|
408 | public void RemoveAttribute(Attribute attr) |
---|
409 | { |
---|
410 | AttributeSection section = (AttributeSection)attr.Parent; |
---|
411 | if (section.Attributes.Count == 1) { |
---|
412 | Remove(section); |
---|
413 | return; |
---|
414 | } |
---|
415 | |
---|
416 | var newSection = (AttributeSection)section.Clone(); |
---|
417 | int i = 0; |
---|
418 | foreach (var a in section.Attributes) { |
---|
419 | if (a == attr) |
---|
420 | break; |
---|
421 | i++; |
---|
422 | } |
---|
423 | newSection.Attributes.Remove (newSection.Attributes.ElementAt (i)); |
---|
424 | Replace(section, newSection); |
---|
425 | } |
---|
426 | |
---|
427 | public abstract void FormatText (IEnumerable<AstNode> nodes); |
---|
428 | |
---|
429 | public void FormatText (params AstNode[] nodes) |
---|
430 | { |
---|
431 | FormatText ((IEnumerable<AstNode>)nodes); |
---|
432 | } |
---|
433 | |
---|
434 | public virtual void Select (AstNode node) |
---|
435 | { |
---|
436 | // default implementation: do nothing |
---|
437 | // Derived classes are supposed to set the text editor's selection |
---|
438 | } |
---|
439 | |
---|
440 | public virtual void Select (TextLocation start, TextLocation end) |
---|
441 | { |
---|
442 | // default implementation: do nothing |
---|
443 | // Derived classes are supposed to set the text editor's selection |
---|
444 | } |
---|
445 | |
---|
446 | public virtual void Select (int startOffset, int endOffset) |
---|
447 | { |
---|
448 | // default implementation: do nothing |
---|
449 | // Derived classes are supposed to set the text editor's selection |
---|
450 | } |
---|
451 | |
---|
452 | |
---|
453 | public enum InsertPosition |
---|
454 | { |
---|
455 | Start, |
---|
456 | Before, |
---|
457 | After, |
---|
458 | End |
---|
459 | } |
---|
460 | |
---|
461 | public virtual Task<Script> InsertWithCursor(string operation, InsertPosition defaultPosition, IList<AstNode> nodes) |
---|
462 | { |
---|
463 | throw new NotImplementedException(); |
---|
464 | } |
---|
465 | |
---|
466 | public virtual Task<Script> InsertWithCursor(string operation, ITypeDefinition parentType, Func<Script, RefactoringContext, IList<AstNode>> nodeCallback) |
---|
467 | { |
---|
468 | throw new NotImplementedException(); |
---|
469 | } |
---|
470 | |
---|
471 | public Task<Script> InsertWithCursor(string operation, InsertPosition defaultPosition, params AstNode[] nodes) |
---|
472 | { |
---|
473 | return InsertWithCursor(operation, defaultPosition, (IList<AstNode>)nodes); |
---|
474 | } |
---|
475 | |
---|
476 | public Task<Script> InsertWithCursor(string operation, ITypeDefinition parentType, Func<Script, RefactoringContext, AstNode> nodeCallback) |
---|
477 | { |
---|
478 | return InsertWithCursor(operation, parentType, (Func<Script, RefactoringContext, IList<AstNode>>)delegate (Script s, RefactoringContext ctx) { |
---|
479 | return new AstNode[] { nodeCallback(s, ctx) }; |
---|
480 | }); |
---|
481 | } |
---|
482 | |
---|
483 | protected virtual int GetIndentLevelAt (int offset) |
---|
484 | { |
---|
485 | return 0; |
---|
486 | } |
---|
487 | |
---|
488 | sealed class SegmentTrackingTokenWriter : TextWriterTokenWriter |
---|
489 | { |
---|
490 | internal List<KeyValuePair<AstNode, Segment>> NewSegments = new List<KeyValuePair<AstNode, Segment>>(); |
---|
491 | readonly Stack<int> startOffsets = new Stack<int>(); |
---|
492 | readonly StringWriter stringWriter; |
---|
493 | |
---|
494 | public SegmentTrackingTokenWriter(StringWriter stringWriter) |
---|
495 | : base(stringWriter) |
---|
496 | { |
---|
497 | this.stringWriter = stringWriter; |
---|
498 | } |
---|
499 | |
---|
500 | public override void WriteIdentifier (Identifier identifier) |
---|
501 | { |
---|
502 | int startOffset = stringWriter.GetStringBuilder ().Length; |
---|
503 | int endOffset = startOffset + (identifier.Name ?? "").Length + (identifier.IsVerbatim ? 1 : 0); |
---|
504 | NewSegments.Add(new KeyValuePair<AstNode, Segment>(identifier, new Segment(startOffset, endOffset - startOffset))); |
---|
505 | base.WriteIdentifier (identifier); |
---|
506 | } |
---|
507 | |
---|
508 | public override void StartNode (AstNode node) |
---|
509 | { |
---|
510 | base.StartNode (node); |
---|
511 | startOffsets.Push(stringWriter.GetStringBuilder ().Length); |
---|
512 | } |
---|
513 | |
---|
514 | public override void EndNode (AstNode node) |
---|
515 | { |
---|
516 | int startOffset = startOffsets.Pop(); |
---|
517 | int endOffset = stringWriter.GetStringBuilder ().Length; |
---|
518 | NewSegments.Add(new KeyValuePair<AstNode, Segment>(node, new Segment(startOffset, endOffset - startOffset))); |
---|
519 | base.EndNode (node); |
---|
520 | } |
---|
521 | } |
---|
522 | |
---|
523 | protected NodeOutput OutputNode(int indentLevel, AstNode node, bool startWithNewLine = false) |
---|
524 | { |
---|
525 | var stringWriter = new StringWriter (); |
---|
526 | var formatter = new SegmentTrackingTokenWriter(stringWriter); |
---|
527 | formatter.Indentation = indentLevel; |
---|
528 | formatter.IndentationString = Options.TabsToSpaces ? new string (' ', Options.IndentSize) : "\t"; |
---|
529 | stringWriter.NewLine = Options.EolMarker; |
---|
530 | if (startWithNewLine) |
---|
531 | formatter.NewLine (); |
---|
532 | var visitor = new CSharpOutputVisitor (formatter, formattingOptions); |
---|
533 | node.AcceptVisitor (visitor); |
---|
534 | string text = stringWriter.ToString().TrimEnd(); |
---|
535 | return new NodeOutput(text, formatter.NewSegments); |
---|
536 | } |
---|
537 | |
---|
538 | protected class NodeOutput |
---|
539 | { |
---|
540 | string text; |
---|
541 | readonly List<KeyValuePair<AstNode, Segment>> newSegments; |
---|
542 | int trimmedLength; |
---|
543 | |
---|
544 | internal NodeOutput(string text, List<KeyValuePair<AstNode, Segment>> newSegments) |
---|
545 | { |
---|
546 | this.text = text; |
---|
547 | this.newSegments = newSegments; |
---|
548 | } |
---|
549 | |
---|
550 | public string Text { |
---|
551 | get { return text; } |
---|
552 | } |
---|
553 | |
---|
554 | public void TrimStart() |
---|
555 | { |
---|
556 | for (int i = 0; i < text.Length; i++) { |
---|
557 | char ch = text [i]; |
---|
558 | if (ch != ' ' && ch != '\t') { |
---|
559 | if (i > 0) { |
---|
560 | text = text.Substring (i); |
---|
561 | trimmedLength = i; |
---|
562 | } |
---|
563 | break; |
---|
564 | } |
---|
565 | } |
---|
566 | } |
---|
567 | |
---|
568 | public void RegisterTrackedSegments(Script script, int insertionOffset) |
---|
569 | { |
---|
570 | foreach (var pair in newSegments) { |
---|
571 | int offset = insertionOffset + pair.Value.Offset - trimmedLength; |
---|
572 | ISegment trackedSegment = script.CreateTrackedSegment(offset, pair.Value.Length); |
---|
573 | script.segmentsForInsertedNodes.Add(pair.Key, trackedSegment); |
---|
574 | } |
---|
575 | } |
---|
576 | } |
---|
577 | |
---|
578 | /// <summary> |
---|
579 | /// Renames the specified symbol. |
---|
580 | /// </summary> |
---|
581 | /// <param name='symbol'> |
---|
582 | /// The symbol to rename |
---|
583 | /// </param> |
---|
584 | /// <param name='name'> |
---|
585 | /// The new name, if null the user is prompted for a new name. |
---|
586 | /// </param> |
---|
587 | public virtual void Rename(ISymbol symbol, string name = null) |
---|
588 | { |
---|
589 | } |
---|
590 | |
---|
591 | public virtual void DoGlobalOperationOn(IEnumerable<IEntity> entities, Action<RefactoringContext, Script, IEnumerable<AstNode>> callback, string operationDescription = null) |
---|
592 | { |
---|
593 | } |
---|
594 | |
---|
595 | public virtual void Dispose() |
---|
596 | { |
---|
597 | FormatText (nodesToFormat); |
---|
598 | } |
---|
599 | |
---|
600 | public enum NewTypeContext { |
---|
601 | /// <summary> |
---|
602 | /// The class should be placed in a new file to the current namespace. |
---|
603 | /// </summary> |
---|
604 | CurrentNamespace, |
---|
605 | |
---|
606 | /// <summary> |
---|
607 | /// The class should be placed in the unit tests. (not implemented atm.) |
---|
608 | /// </summary> |
---|
609 | UnitTests |
---|
610 | } |
---|
611 | |
---|
612 | /// <summary> |
---|
613 | /// Creates a new file containing the type, namespace and correct usings. |
---|
614 | /// (Note: Should take care of IDE specific things, file headers, add to project, correct name). |
---|
615 | /// </summary> |
---|
616 | /// <param name='newType'> |
---|
617 | /// New type to be created. |
---|
618 | /// </param> |
---|
619 | /// <param name='context'> |
---|
620 | /// The Context in which the new type should be created. |
---|
621 | /// </param> |
---|
622 | public virtual void CreateNewType(AstNode newType, NewTypeContext context = NewTypeContext.CurrentNamespace) |
---|
623 | { |
---|
624 | } |
---|
625 | } |
---|
626 | |
---|
627 | public static class ExtMethods |
---|
628 | { |
---|
629 | public static void ContinueScript (this Task task, Action act) |
---|
630 | { |
---|
631 | if (task.IsCompleted) { |
---|
632 | act(); |
---|
633 | } else { |
---|
634 | task.ContinueWith(delegate { |
---|
635 | act(); |
---|
636 | }, TaskScheduler.FromCurrentSynchronizationContext()); |
---|
637 | } |
---|
638 | } |
---|
639 | |
---|
640 | public static void ContinueScript (this Task<Script> task, Action<Script> act) |
---|
641 | { |
---|
642 | if (task.IsCompleted) { |
---|
643 | act(task.Result); |
---|
644 | } else { |
---|
645 | task.ContinueWith(delegate { |
---|
646 | act(task.Result); |
---|
647 | }, TaskScheduler.FromCurrentSynchronizationContext()); |
---|
648 | } |
---|
649 | } |
---|
650 | } |
---|
651 | } |
---|