Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.NRefactory/5.5.0/NRefactory.CSharp-5.5.0/IndentEngine/TextPasteIndentEngine.cs @ 15724

Last change on this file since 15724 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 16.0 KB
Line 
1//
2// TextPasteIndentEngine.cs
3//
4// Author:
5//       Matej Miklečić <matej.miklecic@gmail.com>
6//
7// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.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.
26using ICSharpCode.NRefactory.Editor;
27using System;
28using System.Collections.Generic;
29using System.Linq;
30using System.Reflection;
31using System.Text;
32
33namespace ICSharpCode.NRefactory.CSharp
34{
35  /// <summary>
36  ///     Represents a decorator of an IStateMachineIndentEngine instance
37  ///     that provides logic for text paste events.
38  /// </summary>
39  public class TextPasteIndentEngine : IDocumentIndentEngine, ITextPasteHandler
40  {
41
42    #region Properties
43
44    /// <summary>
45    ///     An instance of IStateMachineIndentEngine which handles
46    ///     the indentation logic.
47    /// </summary>
48    IStateMachineIndentEngine engine;
49    /// <summary>
50    ///     Text editor options.
51    /// </summary>
52    internal readonly TextEditorOptions textEditorOptions;
53    internal readonly CSharpFormattingOptions formattingOptions;
54    #endregion
55
56    #region Constructors
57
58    /// <summary>
59    ///     Creates a new TextPasteIndentEngine instance.
60    /// </summary>
61    /// <param name="decoratedEngine">
62    ///     An instance of <see cref="IStateMachineIndentEngine"/> to which the
63    ///     logic for indentation will be delegated.
64    /// </param>
65    /// <param name="textEditorOptions">
66    ///    Text editor options for indentation.
67    /// </param>
68    /// <param name="formattingOptions">
69    ///     C# formatting options.
70    /// </param>
71    public TextPasteIndentEngine(IStateMachineIndentEngine decoratedEngine, TextEditorOptions textEditorOptions, CSharpFormattingOptions formattingOptions)
72    {
73      this.engine = decoratedEngine;
74      this.textEditorOptions = textEditorOptions;
75      this.formattingOptions = formattingOptions;
76
77      this.engine.EnableCustomIndentLevels = false;
78    }
79
80    #endregion
81
82    #region ITextPasteHandler
83
84    /// <inheritdoc />
85    string ITextPasteHandler.FormatPlainText(int offset, string text, byte[] copyData)
86    {
87      if (copyData != null && copyData.Length == 1) {
88        var strategy = TextPasteUtils.Strategies [(PasteStrategy)copyData [0]];
89        text = strategy.Decode(text);
90      }
91      engine.Update(offset);
92      if (engine.IsInsideStringLiteral) {
93        int idx = text.IndexOf('"');
94        if (idx > 0) {
95          var o = offset;
96          while (o < engine.Document.TextLength) {
97            char ch = engine.Document.GetCharAt(o);
98            engine.Push(ch);
99            if (NewLine.IsNewLine(ch))
100              break;
101            o++;
102            if (!engine.IsInsideStringLiteral)
103              return TextPasteUtils.StringLiteralStrategy.Encode(text);
104          }
105          return TextPasteUtils.StringLiteralStrategy.Encode(text.Substring(0, idx)) + text.Substring(idx);
106        }
107        return TextPasteUtils.StringLiteralStrategy.Encode(text);
108
109      } else if (engine.IsInsideVerbatimString) {
110
111        int idx = text.IndexOf('"');
112        if (idx > 0) {
113          var o = offset;
114          while (o < engine.Document.TextLength) {
115            char ch = engine.Document.GetCharAt(o);
116            engine.Push(ch);
117            o++;
118            if (!engine.IsInsideVerbatimString)
119              return TextPasteUtils.VerbatimStringStrategy.Encode(text);
120          }
121          return TextPasteUtils.VerbatimStringStrategy.Encode(text.Substring(0, idx)) + text.Substring(idx);
122        }
123
124        return TextPasteUtils.VerbatimStringStrategy.Encode(text);
125      }
126      var line = engine.Document.GetLineByOffset(offset);
127      var pasteAtLineStart = line.Offset == offset;
128      var indentedText = new StringBuilder();
129      var curLine = new StringBuilder();
130      var clonedEngine = engine.Clone();
131      bool isNewLine = false, gotNewLine = false;
132      for (int i = 0; i < text.Length; i++) {
133        var ch = text [i];
134        if (clonedEngine.IsInsideVerbatimString || clonedEngine.IsInsideMultiLineComment) {
135          clonedEngine.Push(ch);
136          curLine.Append(ch);
137          continue;
138        }
139
140        var delimiterLength = NewLine.GetDelimiterLength(ch, i + 1 < text.Length ? text[i + 1] : ' ');
141        if (delimiterLength > 0) {
142          isNewLine = true;
143          if (gotNewLine || pasteAtLineStart) {
144            if (curLine.Length > 0 || formattingOptions.EmptyLineFormatting == EmptyLineFormatting.Indent)
145              indentedText.Append(clonedEngine.ThisLineIndent);
146          }
147          indentedText.Append(curLine);
148          indentedText.Append(textEditorOptions.EolMarker);
149          curLine.Length = 0;
150          gotNewLine = true;
151          i += delimiterLength - 1;
152          // textEditorOptions.EolMarker[0] is the newLineChar used by the indentation engine.
153          clonedEngine.Push(textEditorOptions.EolMarker[0]);
154        } else {
155          if (isNewLine) {
156            if (ch == '\t' || ch == ' ') {
157              clonedEngine.Push(ch);
158              continue;
159            }
160            isNewLine = false;
161          }
162          curLine.Append(ch);
163          clonedEngine.Push(ch);
164        }
165        if (clonedEngine.IsInsideVerbatimString || clonedEngine.IsInsideMultiLineComment &&
166            !(clonedEngine.LineBeganInsideVerbatimString || clonedEngine.LineBeganInsideMultiLineComment)) {
167          if (gotNewLine) {
168            if (curLine.Length > 0 || formattingOptions.EmptyLineFormatting == EmptyLineFormatting.Indent)
169              indentedText.Append(clonedEngine.ThisLineIndent);
170          }
171          pasteAtLineStart = false;
172          indentedText.Append(curLine);
173          curLine.Length = 0;
174          gotNewLine = false;
175          continue;
176        }
177      }
178      if (gotNewLine && (!pasteAtLineStart || curLine.Length > 0)) {
179        indentedText.Append(clonedEngine.ThisLineIndent);
180      }
181      if (curLine.Length > 0) {
182        indentedText.Append(curLine);
183      }
184      return indentedText.ToString();
185    }
186
187    /// <inheritdoc />
188    byte[] ITextPasteHandler.GetCopyData(ISegment segment)
189    {
190      engine.Update(segment.Offset);
191     
192      if (engine.IsInsideStringLiteral) {
193        return new[] { (byte)PasteStrategy.StringLiteral };
194      } else if (engine.IsInsideVerbatimString) {
195        return new[] { (byte)PasteStrategy.VerbatimString };
196      }
197     
198      return null;
199    }
200
201    #endregion
202
203    #region IDocumentIndentEngine
204
205    /// <inheritdoc />
206    public IDocument Document {
207      get { return engine.Document; }
208    }
209
210    /// <inheritdoc />
211    public string ThisLineIndent {
212      get { return engine.ThisLineIndent; }
213    }
214
215    /// <inheritdoc />
216    public string NextLineIndent {
217      get { return engine.NextLineIndent; }
218    }
219
220    /// <inheritdoc />
221    public string CurrentIndent {
222      get { return engine.CurrentIndent; }
223    }
224
225    /// <inheritdoc />
226    public bool NeedsReindent {
227      get { return engine.NeedsReindent; }
228    }
229
230    /// <inheritdoc />
231    public int Offset {
232      get { return engine.Offset; }
233    }
234
235    /// <inheritdoc />
236    public TextLocation Location {
237      get { return engine.Location; }
238    }
239
240    /// <inheritdoc />
241    public bool EnableCustomIndentLevels {
242      get { return engine.EnableCustomIndentLevels; }
243      set { engine.EnableCustomIndentLevels = value; }
244    }
245   
246    /// <inheritdoc />
247    public void Push(char ch)
248    {
249      engine.Push(ch);
250    }
251
252    /// <inheritdoc />
253    public void Reset()
254    {
255      engine.Reset();
256    }
257
258    /// <inheritdoc />
259    public void Update(int offset)
260    {
261      engine.Update(offset);
262    }
263
264    #endregion
265
266    #region IClonable
267
268    public IDocumentIndentEngine Clone()
269    {
270      return new TextPasteIndentEngine(engine, textEditorOptions, formattingOptions);
271    }
272
273    object ICloneable.Clone()
274    {
275      return Clone();
276    }
277
278    #endregion
279
280  }
281
282  /// <summary>
283  ///     Types of text-paste strategies.
284  /// </summary>
285  public enum PasteStrategy : byte
286  {
287    PlainText = 0,
288    StringLiteral = 1,
289    VerbatimString = 2
290  }
291
292  /// <summary>
293  ///     Defines some helper methods for dealing with text-paste events.
294  /// </summary>
295  public static class TextPasteUtils
296  {
297    /// <summary>
298    ///     Collection of text-paste strategies.
299    /// </summary>
300    public static TextPasteStrategies Strategies = new TextPasteStrategies();
301
302    /// <summary>
303    ///     The interface for a text-paste strategy.
304    /// </summary>
305    public interface IPasteStrategy
306    {
307      /// <summary>
308      ///     Formats the given text according with this strategy rules.
309      /// </summary>
310      /// <param name="text">
311      ///    The text to format.
312      /// </param>
313      /// <returns>
314      ///     Formatted text.
315      /// </returns>
316      string Encode(string text);
317
318      /// <summary>
319      ///     Converts text formatted according with this strategy rules
320      ///     to its original form.
321      /// </summary>
322      /// <param name="text">
323      ///     Formatted text to convert.
324      /// </param>
325      /// <returns>
326      ///     Original form of the given formatted text.
327      /// </returns>
328      string Decode(string text);
329
330      /// <summary>
331      ///     Type of this strategy.
332      /// </summary>
333      PasteStrategy Type { get; }
334    }
335
336    /// <summary>
337    ///     Wrapper that discovers all defined text-paste strategies and defines a way
338    ///     to easily access them through their <see cref="PasteStrategy"/> type.
339    /// </summary>
340    public sealed class TextPasteStrategies
341    {
342      /// <summary>
343      ///     Collection of discovered text-paste strategies.
344      /// </summary>
345      IDictionary<PasteStrategy, IPasteStrategy> strategies;
346
347      /// <summary>
348      ///     Uses reflection to find all types derived from <see cref="IPasteStrategy"/>
349      ///     and adds an instance of each strategy to <see cref="strategies"/>.
350      /// </summary>
351      public TextPasteStrategies()
352      {
353        strategies = Assembly.GetExecutingAssembly()
354             .GetTypes()
355        .Where(t => typeof(IPasteStrategy).IsAssignableFrom(t) && t.IsClass)
356        .Select(t => (IPasteStrategy)t.GetProperty("Instance").GetValue(null, null))
357        .ToDictionary(s => s.Type);
358      }
359
360      /// <summary>
361      ///     Checks if there is a strategy of the given type and returns it.
362      /// </summary>
363      /// <param name="strategy">
364      ///     Type of the strategy instance.
365      /// </param>
366      /// <returns>
367      ///     A strategy instance of the requested type,
368      ///     or <see cref="DefaultStrategy"/> if it wasn't found.
369      /// </returns>
370      public IPasteStrategy this [PasteStrategy strategy] {
371        get {
372          if (strategies.ContainsKey(strategy)) {
373            return strategies [strategy];
374          }
375         
376          return DefaultStrategy;
377        }
378      }
379    }
380
381    /// <summary>
382    ///     Doesn't do any formatting. Serves as the default strategy.
383    /// </summary>
384    public class PlainTextPasteStrategy : IPasteStrategy
385    {
386
387      #region Singleton
388
389      public static IPasteStrategy Instance {
390        get {
391          return instance ?? (instance = new PlainTextPasteStrategy());
392        }
393      }
394
395      static PlainTextPasteStrategy instance;
396
397      protected PlainTextPasteStrategy()
398      {
399      }
400
401      #endregion
402
403      /// <inheritdoc />
404      public string Encode(string text)
405      {
406        return text;
407      }
408
409      /// <inheritdoc />
410      public string Decode(string text)
411      {
412        return text;
413      }
414
415      /// <inheritdoc />
416      public PasteStrategy Type {
417        get { return PasteStrategy.PlainText; }
418      }
419    }
420
421    /// <summary>
422    ///     Escapes chars in the given text so that they don't
423    ///     break a valid string literal.
424    /// </summary>
425    public class StringLiteralPasteStrategy : IPasteStrategy
426    {
427
428      #region Singleton
429
430      public static IPasteStrategy Instance {
431        get {
432          return instance ?? (instance = new StringLiteralPasteStrategy());
433        }
434      }
435
436      static StringLiteralPasteStrategy instance;
437
438      protected StringLiteralPasteStrategy()
439      {
440      }
441
442      #endregion
443
444      /// <inheritdoc />
445      public string Encode(string text)
446      {
447        return CSharpOutputVisitor.ConvertString(text);
448      }
449
450      /// <inheritdoc />
451      public string Decode(string text)
452      {
453        var result = new StringBuilder();
454        bool isEscaped = false;
455
456        for (int i = 0; i < text.Length; i++) {
457          var ch = text[i];
458          if (isEscaped) {
459            switch (ch) {
460              case 'a':
461                result.Append('\a');
462                break;
463              case 'b':
464                result.Append('\b');
465                break;
466              case 'n':
467                result.Append('\n');
468                break;
469              case 't':
470                result.Append('\t');
471                break;
472              case 'v':
473                result.Append('\v');
474                break;
475              case 'r':
476                result.Append('\r');
477                break;
478              case '\\':
479                result.Append('\\');
480                break;
481              case 'f':
482                result.Append('\f');
483                break;
484              case '0':
485                result.Append(0);
486                break;
487              case '"':
488                result.Append('"');
489                break;
490              case '\'':
491                result.Append('\'');
492                break;
493              case 'x':
494                char r;
495                if (TryGetHex(text, -1, ref i, out r)) {
496                  result.Append(r);
497                  break;
498                }
499                goto default;
500              case 'u':
501                if (TryGetHex(text, 4, ref i, out r)) {
502                  result.Append(r);
503                  break;
504                }
505                goto default;
506              case 'U':
507                if (TryGetHex(text, 8, ref i, out r)) {
508                  result.Append(r);
509                  break;
510                }
511                goto default;
512              default:
513                result.Append('\\');
514                result.Append(ch);
515                break;
516            }
517            isEscaped = false;
518            continue;
519          }
520          if (ch != '\\') {
521            result.Append(ch);
522          }
523          else {
524            isEscaped = true;
525          }
526        }
527
528        return result.ToString();
529      }
530
531      static bool TryGetHex(string text, int count, ref int idx, out char r)
532      {
533        int i;
534        int total = 0;
535        int top = count != -1 ? count : 4;
536
537        for (i = 0; i < top; i++) {
538          int c = text[idx + 1 + i];
539
540          if (c >= '0' && c <= '9')
541            c = (int) c - (int) '0';
542          else if (c >= 'A' && c <= 'F')
543            c = (int) c - (int) 'A' + 10;
544          else if (c >= 'a' && c <= 'f')
545            c = (int) c - (int) 'a' + 10;
546          else {
547            r = '\0';
548            return false;
549          }
550          total = (total * 16) + c;
551        }
552
553        if (top == 8) {
554          if (total > 0x0010FFFF) {
555            r = '\0';
556            return false;
557          }
558
559          if (total >= 0x00010000)
560            total = ((total - 0x00010000) / 0x0400 + 0xD800);
561        }
562        r = (char)total;
563        idx += top;
564        return true;
565      }
566
567      /// <inheritdoc />
568      public PasteStrategy Type {
569        get { return PasteStrategy.StringLiteral; }
570      }
571    }
572
573    /// <summary>
574    ///     Escapes chars in the given text so that they don't
575    ///     break a valid verbatim string.
576    /// </summary>
577    public class VerbatimStringPasteStrategy : IPasteStrategy
578    {
579
580      #region Singleton
581
582      public static IPasteStrategy Instance {
583        get {
584          return instance ?? (instance = new VerbatimStringPasteStrategy());
585        }
586      }
587
588      static VerbatimStringPasteStrategy instance;
589
590      protected VerbatimStringPasteStrategy()
591      {
592      }
593
594      #endregion
595
596      static readonly Dictionary<char, IEnumerable<char>> encodeReplace = new Dictionary<char, IEnumerable<char>> {
597        { '\"', "\"\"" },
598      };
599
600      /// <inheritdoc />
601      public string Encode(string text)
602      {
603        return string.Concat(text.SelectMany(c => encodeReplace.ContainsKey(c) ? encodeReplace [c] : new[] { c }));
604      }
605
606      /// <inheritdoc />
607      public string Decode(string text)
608      {
609        bool isEscaped = false;
610        return string.Concat(text.Where(c => !(isEscaped = !isEscaped && c == '"')));
611      }
612
613      /// <inheritdoc />
614      public PasteStrategy Type {
615        get { return PasteStrategy.VerbatimString; }
616      }
617    }
618
619    /// <summary>
620    ///     The default text-paste strategy.
621    /// </summary>
622    public static IPasteStrategy DefaultStrategy = PlainTextPasteStrategy.Instance;
623    /// <summary>
624    ///     String literal text-paste strategy.
625    /// </summary>
626    public static IPasteStrategy StringLiteralStrategy = StringLiteralPasteStrategy.Instance;
627    /// <summary>
628    ///     Verbatim string text-paste strategy.
629    /// </summary>
630    public static IPasteStrategy VerbatimStringStrategy = VerbatimStringPasteStrategy.Instance;
631  }
632}
Note: See TracBrowser for help on using the repository browser.