mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
632 lines
16 KiB
632 lines
16 KiB
// |
|
// TextPasteIndentEngine.cs |
|
// |
|
// Author: |
|
// Matej Miklečić <matej.miklecic@gmail.com> |
|
// |
|
// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com) |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy |
|
// of this software and associated documentation files (the "Software"), to deal |
|
// in the Software without restriction, including without limitation the rights |
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
// copies of the Software, and to permit persons to whom the Software is |
|
// furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in |
|
// all copies or substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
// THE SOFTWARE. |
|
using ICSharpCode.NRefactory.Editor; |
|
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Reflection; |
|
using System.Text; |
|
|
|
namespace ICSharpCode.NRefactory.CSharp |
|
{ |
|
/// <summary> |
|
/// Represents a decorator of an IStateMachineIndentEngine instance |
|
/// that provides logic for text paste events. |
|
/// </summary> |
|
public class TextPasteIndentEngine : IDocumentIndentEngine, ITextPasteHandler |
|
{ |
|
|
|
#region Properties |
|
|
|
/// <summary> |
|
/// An instance of IStateMachineIndentEngine which handles |
|
/// the indentation logic. |
|
/// </summary> |
|
IStateMachineIndentEngine engine; |
|
/// <summary> |
|
/// Text editor options. |
|
/// </summary> |
|
internal readonly TextEditorOptions textEditorOptions; |
|
internal readonly CSharpFormattingOptions formattingOptions; |
|
#endregion |
|
|
|
#region Constructors |
|
|
|
/// <summary> |
|
/// Creates a new TextPasteIndentEngine instance. |
|
/// </summary> |
|
/// <param name="decoratedEngine"> |
|
/// An instance of <see cref="IStateMachineIndentEngine"/> to which the |
|
/// logic for indentation will be delegated. |
|
/// </param> |
|
/// <param name="textEditorOptions"> |
|
/// Text editor options for indentation. |
|
/// </param> |
|
/// <param name="formattingOptions"> |
|
/// C# formatting options. |
|
/// </param> |
|
public TextPasteIndentEngine(IStateMachineIndentEngine decoratedEngine, TextEditorOptions textEditorOptions, CSharpFormattingOptions formattingOptions) |
|
{ |
|
this.engine = decoratedEngine; |
|
this.textEditorOptions = textEditorOptions; |
|
this.formattingOptions = formattingOptions; |
|
|
|
this.engine.EnableCustomIndentLevels = false; |
|
} |
|
|
|
#endregion |
|
|
|
#region ITextPasteHandler |
|
|
|
/// <inheritdoc /> |
|
string ITextPasteHandler.FormatPlainText(int offset, string text, byte[] copyData) |
|
{ |
|
if (copyData != null && copyData.Length == 1) { |
|
var strategy = TextPasteUtils.Strategies [(PasteStrategy)copyData [0]]; |
|
text = strategy.Decode(text); |
|
} |
|
engine.Update(offset); |
|
if (engine.IsInsideStringLiteral) { |
|
int idx = text.IndexOf('"'); |
|
if (idx > 0) { |
|
var o = offset; |
|
while (o < engine.Document.TextLength) { |
|
char ch = engine.Document.GetCharAt(o); |
|
engine.Push(ch); |
|
if (NewLine.IsNewLine(ch)) |
|
break; |
|
o++; |
|
if (!engine.IsInsideStringLiteral) |
|
return TextPasteUtils.StringLiteralStrategy.Encode(text); |
|
} |
|
return TextPasteUtils.StringLiteralStrategy.Encode(text.Substring(0, idx)) + text.Substring(idx); |
|
} |
|
return TextPasteUtils.StringLiteralStrategy.Encode(text); |
|
|
|
} else if (engine.IsInsideVerbatimString) { |
|
|
|
int idx = text.IndexOf('"'); |
|
if (idx > 0) { |
|
var o = offset; |
|
while (o < engine.Document.TextLength) { |
|
char ch = engine.Document.GetCharAt(o); |
|
engine.Push(ch); |
|
o++; |
|
if (!engine.IsInsideVerbatimString) |
|
return TextPasteUtils.VerbatimStringStrategy.Encode(text); |
|
} |
|
return TextPasteUtils.VerbatimStringStrategy.Encode(text.Substring(0, idx)) + text.Substring(idx); |
|
} |
|
|
|
return TextPasteUtils.VerbatimStringStrategy.Encode(text); |
|
} |
|
var line = engine.Document.GetLineByOffset(offset); |
|
var pasteAtLineStart = line.Offset == offset; |
|
var indentedText = new StringBuilder(); |
|
var curLine = new StringBuilder(); |
|
var clonedEngine = engine.Clone(); |
|
bool isNewLine = false, gotNewLine = false; |
|
for (int i = 0; i < text.Length; i++) { |
|
var ch = text [i]; |
|
if (clonedEngine.IsInsideVerbatimString || clonedEngine.IsInsideMultiLineComment) { |
|
clonedEngine.Push(ch); |
|
curLine.Append(ch); |
|
continue; |
|
} |
|
|
|
var delimiterLength = NewLine.GetDelimiterLength(ch, i + 1 < text.Length ? text[i + 1] : ' '); |
|
if (delimiterLength > 0) { |
|
isNewLine = true; |
|
if (gotNewLine || pasteAtLineStart) { |
|
if (curLine.Length > 0 || formattingOptions.EmptyLineFormatting == EmptyLineFormatting.Indent) |
|
indentedText.Append(clonedEngine.ThisLineIndent); |
|
} |
|
indentedText.Append(curLine); |
|
indentedText.Append(textEditorOptions.EolMarker); |
|
curLine.Length = 0; |
|
gotNewLine = true; |
|
i += delimiterLength - 1; |
|
// textEditorOptions.EolMarker[0] is the newLineChar used by the indentation engine. |
|
clonedEngine.Push(textEditorOptions.EolMarker[0]); |
|
} else { |
|
if (isNewLine) { |
|
if (ch == '\t' || ch == ' ') { |
|
clonedEngine.Push(ch); |
|
continue; |
|
} |
|
isNewLine = false; |
|
} |
|
curLine.Append(ch); |
|
clonedEngine.Push(ch); |
|
} |
|
if (clonedEngine.IsInsideVerbatimString || clonedEngine.IsInsideMultiLineComment && |
|
!(clonedEngine.LineBeganInsideVerbatimString || clonedEngine.LineBeganInsideMultiLineComment)) { |
|
if (gotNewLine) { |
|
if (curLine.Length > 0 || formattingOptions.EmptyLineFormatting == EmptyLineFormatting.Indent) |
|
indentedText.Append(clonedEngine.ThisLineIndent); |
|
} |
|
pasteAtLineStart = false; |
|
indentedText.Append(curLine); |
|
curLine.Length = 0; |
|
gotNewLine = false; |
|
continue; |
|
} |
|
} |
|
if (gotNewLine && (!pasteAtLineStart || curLine.Length > 0)) { |
|
indentedText.Append(clonedEngine.ThisLineIndent); |
|
} |
|
if (curLine.Length > 0) { |
|
indentedText.Append(curLine); |
|
} |
|
return indentedText.ToString(); |
|
} |
|
|
|
/// <inheritdoc /> |
|
byte[] ITextPasteHandler.GetCopyData(ISegment segment) |
|
{ |
|
engine.Update(segment.Offset); |
|
|
|
if (engine.IsInsideStringLiteral) { |
|
return new[] { (byte)PasteStrategy.StringLiteral }; |
|
} else if (engine.IsInsideVerbatimString) { |
|
return new[] { (byte)PasteStrategy.VerbatimString }; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
#endregion |
|
|
|
#region IDocumentIndentEngine |
|
|
|
/// <inheritdoc /> |
|
public IDocument Document { |
|
get { return engine.Document; } |
|
} |
|
|
|
/// <inheritdoc /> |
|
public string ThisLineIndent { |
|
get { return engine.ThisLineIndent; } |
|
} |
|
|
|
/// <inheritdoc /> |
|
public string NextLineIndent { |
|
get { return engine.NextLineIndent; } |
|
} |
|
|
|
/// <inheritdoc /> |
|
public string CurrentIndent { |
|
get { return engine.CurrentIndent; } |
|
} |
|
|
|
/// <inheritdoc /> |
|
public bool NeedsReindent { |
|
get { return engine.NeedsReindent; } |
|
} |
|
|
|
/// <inheritdoc /> |
|
public int Offset { |
|
get { return engine.Offset; } |
|
} |
|
|
|
/// <inheritdoc /> |
|
public TextLocation Location { |
|
get { return engine.Location; } |
|
} |
|
|
|
/// <inheritdoc /> |
|
public bool EnableCustomIndentLevels { |
|
get { return engine.EnableCustomIndentLevels; } |
|
set { engine.EnableCustomIndentLevels = value; } |
|
} |
|
|
|
/// <inheritdoc /> |
|
public void Push(char ch) |
|
{ |
|
engine.Push(ch); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public void Reset() |
|
{ |
|
engine.Reset(); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public void Update(int offset) |
|
{ |
|
engine.Update(offset); |
|
} |
|
|
|
#endregion |
|
|
|
#region IClonable |
|
|
|
public IDocumentIndentEngine Clone() |
|
{ |
|
return new TextPasteIndentEngine(engine, textEditorOptions, formattingOptions); |
|
} |
|
|
|
object ICloneable.Clone() |
|
{ |
|
return Clone(); |
|
} |
|
|
|
#endregion |
|
|
|
} |
|
|
|
/// <summary> |
|
/// Types of text-paste strategies. |
|
/// </summary> |
|
public enum PasteStrategy : byte |
|
{ |
|
PlainText = 0, |
|
StringLiteral = 1, |
|
VerbatimString = 2 |
|
} |
|
|
|
/// <summary> |
|
/// Defines some helper methods for dealing with text-paste events. |
|
/// </summary> |
|
public static class TextPasteUtils |
|
{ |
|
/// <summary> |
|
/// Collection of text-paste strategies. |
|
/// </summary> |
|
public static TextPasteStrategies Strategies = new TextPasteStrategies(); |
|
|
|
/// <summary> |
|
/// The interface for a text-paste strategy. |
|
/// </summary> |
|
public interface IPasteStrategy |
|
{ |
|
/// <summary> |
|
/// Formats the given text according with this strategy rules. |
|
/// </summary> |
|
/// <param name="text"> |
|
/// The text to format. |
|
/// </param> |
|
/// <returns> |
|
/// Formatted text. |
|
/// </returns> |
|
string Encode(string text); |
|
|
|
/// <summary> |
|
/// Converts text formatted according with this strategy rules |
|
/// to its original form. |
|
/// </summary> |
|
/// <param name="text"> |
|
/// Formatted text to convert. |
|
/// </param> |
|
/// <returns> |
|
/// Original form of the given formatted text. |
|
/// </returns> |
|
string Decode(string text); |
|
|
|
/// <summary> |
|
/// Type of this strategy. |
|
/// </summary> |
|
PasteStrategy Type { get; } |
|
} |
|
|
|
/// <summary> |
|
/// Wrapper that discovers all defined text-paste strategies and defines a way |
|
/// to easily access them through their <see cref="PasteStrategy"/> type. |
|
/// </summary> |
|
public sealed class TextPasteStrategies |
|
{ |
|
/// <summary> |
|
/// Collection of discovered text-paste strategies. |
|
/// </summary> |
|
IDictionary<PasteStrategy, IPasteStrategy> strategies; |
|
|
|
/// <summary> |
|
/// Uses reflection to find all types derived from <see cref="IPasteStrategy"/> |
|
/// and adds an instance of each strategy to <see cref="strategies"/>. |
|
/// </summary> |
|
public TextPasteStrategies() |
|
{ |
|
strategies = Assembly.GetExecutingAssembly() |
|
.GetTypes() |
|
.Where(t => typeof(IPasteStrategy).IsAssignableFrom(t) && t.IsClass) |
|
.Select(t => (IPasteStrategy)t.GetProperty("Instance").GetValue(null, null)) |
|
.ToDictionary(s => s.Type); |
|
} |
|
|
|
/// <summary> |
|
/// Checks if there is a strategy of the given type and returns it. |
|
/// </summary> |
|
/// <param name="strategy"> |
|
/// Type of the strategy instance. |
|
/// </param> |
|
/// <returns> |
|
/// A strategy instance of the requested type, |
|
/// or <see cref="DefaultStrategy"/> if it wasn't found. |
|
/// </returns> |
|
public IPasteStrategy this [PasteStrategy strategy] { |
|
get { |
|
if (strategies.ContainsKey(strategy)) { |
|
return strategies [strategy]; |
|
} |
|
|
|
return DefaultStrategy; |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Doesn't do any formatting. Serves as the default strategy. |
|
/// </summary> |
|
public class PlainTextPasteStrategy : IPasteStrategy |
|
{ |
|
|
|
#region Singleton |
|
|
|
public static IPasteStrategy Instance { |
|
get { |
|
return instance ?? (instance = new PlainTextPasteStrategy()); |
|
} |
|
} |
|
|
|
static PlainTextPasteStrategy instance; |
|
|
|
protected PlainTextPasteStrategy() |
|
{ |
|
} |
|
|
|
#endregion |
|
|
|
/// <inheritdoc /> |
|
public string Encode(string text) |
|
{ |
|
return text; |
|
} |
|
|
|
/// <inheritdoc /> |
|
public string Decode(string text) |
|
{ |
|
return text; |
|
} |
|
|
|
/// <inheritdoc /> |
|
public PasteStrategy Type { |
|
get { return PasteStrategy.PlainText; } |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Escapes chars in the given text so that they don't |
|
/// break a valid string literal. |
|
/// </summary> |
|
public class StringLiteralPasteStrategy : IPasteStrategy |
|
{ |
|
|
|
#region Singleton |
|
|
|
public static IPasteStrategy Instance { |
|
get { |
|
return instance ?? (instance = new StringLiteralPasteStrategy()); |
|
} |
|
} |
|
|
|
static StringLiteralPasteStrategy instance; |
|
|
|
protected StringLiteralPasteStrategy() |
|
{ |
|
} |
|
|
|
#endregion |
|
|
|
/// <inheritdoc /> |
|
public string Encode(string text) |
|
{ |
|
return CSharpOutputVisitor.ConvertString(text); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public string Decode(string text) |
|
{ |
|
var result = new StringBuilder(); |
|
bool isEscaped = false; |
|
|
|
for (int i = 0; i < text.Length; i++) { |
|
var ch = text[i]; |
|
if (isEscaped) { |
|
switch (ch) { |
|
case 'a': |
|
result.Append('\a'); |
|
break; |
|
case 'b': |
|
result.Append('\b'); |
|
break; |
|
case 'n': |
|
result.Append('\n'); |
|
break; |
|
case 't': |
|
result.Append('\t'); |
|
break; |
|
case 'v': |
|
result.Append('\v'); |
|
break; |
|
case 'r': |
|
result.Append('\r'); |
|
break; |
|
case '\\': |
|
result.Append('\\'); |
|
break; |
|
case 'f': |
|
result.Append('\f'); |
|
break; |
|
case '0': |
|
result.Append(0); |
|
break; |
|
case '"': |
|
result.Append('"'); |
|
break; |
|
case '\'': |
|
result.Append('\''); |
|
break; |
|
case 'x': |
|
char r; |
|
if (TryGetHex(text, -1, ref i, out r)) { |
|
result.Append(r); |
|
break; |
|
} |
|
goto default; |
|
case 'u': |
|
if (TryGetHex(text, 4, ref i, out r)) { |
|
result.Append(r); |
|
break; |
|
} |
|
goto default; |
|
case 'U': |
|
if (TryGetHex(text, 8, ref i, out r)) { |
|
result.Append(r); |
|
break; |
|
} |
|
goto default; |
|
default: |
|
result.Append('\\'); |
|
result.Append(ch); |
|
break; |
|
} |
|
isEscaped = false; |
|
continue; |
|
} |
|
if (ch != '\\') { |
|
result.Append(ch); |
|
} |
|
else { |
|
isEscaped = true; |
|
} |
|
} |
|
|
|
return result.ToString(); |
|
} |
|
|
|
static bool TryGetHex(string text, int count, ref int idx, out char r) |
|
{ |
|
int i; |
|
int total = 0; |
|
int top = count != -1 ? count : 4; |
|
|
|
for (i = 0; i < top; i++) { |
|
int c = text[idx + 1 + i]; |
|
|
|
if (c >= '0' && c <= '9') |
|
c = (int) c - (int) '0'; |
|
else if (c >= 'A' && c <= 'F') |
|
c = (int) c - (int) 'A' + 10; |
|
else if (c >= 'a' && c <= 'f') |
|
c = (int) c - (int) 'a' + 10; |
|
else { |
|
r = '\0'; |
|
return false; |
|
} |
|
total = (total * 16) + c; |
|
} |
|
|
|
if (top == 8) { |
|
if (total > 0x0010FFFF) { |
|
r = '\0'; |
|
return false; |
|
} |
|
|
|
if (total >= 0x00010000) |
|
total = ((total - 0x00010000) / 0x0400 + 0xD800); |
|
} |
|
r = (char)total; |
|
idx += top; |
|
return true; |
|
} |
|
|
|
/// <inheritdoc /> |
|
public PasteStrategy Type { |
|
get { return PasteStrategy.StringLiteral; } |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Escapes chars in the given text so that they don't |
|
/// break a valid verbatim string. |
|
/// </summary> |
|
public class VerbatimStringPasteStrategy : IPasteStrategy |
|
{ |
|
|
|
#region Singleton |
|
|
|
public static IPasteStrategy Instance { |
|
get { |
|
return instance ?? (instance = new VerbatimStringPasteStrategy()); |
|
} |
|
} |
|
|
|
static VerbatimStringPasteStrategy instance; |
|
|
|
protected VerbatimStringPasteStrategy() |
|
{ |
|
} |
|
|
|
#endregion |
|
|
|
static readonly Dictionary<char, IEnumerable<char>> encodeReplace = new Dictionary<char, IEnumerable<char>> { |
|
{ '\"', "\"\"" }, |
|
}; |
|
|
|
/// <inheritdoc /> |
|
public string Encode(string text) |
|
{ |
|
return string.Concat(text.SelectMany(c => encodeReplace.ContainsKey(c) ? encodeReplace [c] : new[] { c })); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public string Decode(string text) |
|
{ |
|
bool isEscaped = false; |
|
return string.Concat(text.Where(c => !(isEscaped = !isEscaped && c == '"'))); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public PasteStrategy Type { |
|
get { return PasteStrategy.VerbatimString; } |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// The default text-paste strategy. |
|
/// </summary> |
|
public static IPasteStrategy DefaultStrategy = PlainTextPasteStrategy.Instance; |
|
/// <summary> |
|
/// String literal text-paste strategy. |
|
/// </summary> |
|
public static IPasteStrategy StringLiteralStrategy = StringLiteralPasteStrategy.Instance; |
|
/// <summary> |
|
/// Verbatim string text-paste strategy. |
|
/// </summary> |
|
public static IPasteStrategy VerbatimStringStrategy = VerbatimStringPasteStrategy.Instance; |
|
} |
|
}
|
|
|