Show CodeMerger.cs syntax highlighted
/* **********************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This source code is subject to terms and conditions of the Shared Source License
* for IronPython. A copy of the license can be found in the License.html file
* at the root of this distribution. If you can not locate the Shared Source License
* for IronPython, please send an email to ironpy@microsoft.com.
* By using this source code in any fashion, you are agreeing to be bound by
* the terms of the Shared Source License for IronPython.
*
* You must not remove this notice, or any other, from this software.
*
* **********************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.CodeDom;
namespace IronPython.CodeDom {
/// <summary>
/// CodeMeger provides merging services between a cached copy of the code
/// and the pre-existing copy of the code. The parser calls the merger to
/// cache the existing text. When the generator is called the generator
/// will check if a merger exists, and if so, will use the generator for
/// performing updates against the original text. Finally the generator
/// will write the updated text as a string to the output stream.
/// </summary>
class CodeMerger {
private IMergeDestination destination;
private int startLineCount, lineDelta;
#if DEBUG
private int lastLine;
#endif
public static void CacheCode(CodeCompileUnit ccu, string code) {
ccu.UserData["MergerCache"] = new CodeMerger(new SimpleMergeDestination(code));
}
public static void CacheCode(CodeCompileUnit ccu, IMergeDestination mergeDestination) {
ccu.UserData["MergerCache"] = new CodeMerger(mergeDestination);
}
public static CodeMerger GetCachedCode(CodeCompileUnit ccu) {
return ccu.UserData["MergerCache"] as CodeMerger;
}
private CodeMerger(IMergeDestination destination) {
this.destination = destination;
}
/// <summary>
/// Peforms a merge of the code. Line/Columns are always in terms of the original code and
/// index from 1.
///
/// Currently column parameters are ignored.
/// </summary>
public void DoMerge(int startRow, int startCol, int endRow, int endCol, string text) {
// input's index from 1, interally we index from zero.
startRow--; startCol--;
if (endRow != -1) { // end of stream
endRow--; endCol--;
}
#if DEBUG
Debug.Assert(startRow >= lastLine, String.Format("Start line is less than or equal to last line start: {0} last: {1}", startRow, lastLine), text);
lastLine = startRow;
#endif
bool hasMerged = destination.HasMerged;
int destLineCount = destination.LineCount;
List<string> newLines = GetLines(text);
if (!hasMerged) {
startLineCount = destLineCount;
}
// last \r\n should be removed (eg 'foo\r\n' should just be 'foo', but we
// can't ask Split in GetLines to remove blanks because we actually \
// do want those if they're in the middle)
if (newLines[newLines.Count - 1].Length == 0) newLines.RemoveAt(newLines.Count - 1);
int lineRange;
if (endRow != -1) {
lineRange = endRow - startRow;
} else {
// to the end of the file
lineRange = startLineCount - startRow;
}
Debug.Assert(lineRange >= 0, "line range is negative");
Debug.Assert(startRow + lineDelta >= 0, "row & line delta is negative");
Debug.Assert(startRow + lineDelta < startLineCount, "row + lineDelta is > # of lines");
// we expect merges to come in-order (because we are walking
// over the same CodeDOM tree in the same order). Therefore
// we progressively update a change-in-line offsets from the
// original file as we append new data / remove existing data
// from our CodeDOM tree. This data will be reset when we finalize
// the merge and return the resulting text.
destination.RemoveRange(startRow + lineDelta, lineRange);
destination.InsertRange(startRow + lineDelta, newLines);
int newLineDelta = newLines.Count - lineRange;
lineDelta += newLineDelta;
}
public int LineDelta {
get {
return lineDelta;
}
set {
lineDelta = value;
}
}
public string FinalizeMerge() {
string res = destination.FinalText;
// and now we expect any new merges to be based on our
// current offsets.
startLineCount = startLineCount + lineDelta;
lineDelta = 0;
#if DEBUG
lastLine = 0;
#endif
return res;
}
private int GetNewLine(int oldLine) {
return oldLine + lineDelta;
}
private static List<string> GetLines(string text) {
List<string> res;
if (text.IndexOf("\r\n") != -1) {
res = new List<string>(text.Split(new string[] { "\r\n" }, StringSplitOptions.None));
} else if (text.IndexOf('\r') != -1) {
res = new List<string>(text.Split('\r'));
} else {
res = new List<string>(text.Split('\n'));
}
return res;
}
/// <summary>
/// A simple merger that uses strings as it's backing store.
/// </summary>
class SimpleMergeDestination : IMergeDestination {
private string originalText;
private List<string> lines;
public SimpleMergeDestination(string code) {
originalText = code;
}
#region IMergeDestination Members
public void InsertRange(int start, IList<string> newLines) {
TextToLines();
lines.InsertRange(start, newLines);
}
public void RemoveRange(int start, int count) {
TextToLines();
lines.RemoveRange(start, count);
}
public int LineCount {
get {
TextToLines();
return lines.Count;
}
}
/// <summary>
/// True if any updates have been applied to the original text, false other wise.
/// </summary>
public bool HasMerged {
get { return lines != null; }
}
/// <summary>
/// Returns the original text in the buffer. May return null if any merges have been preformed.
/// </summary>
public string FinalText {
get {
if (lines == null) return originalText;
StringBuilder res = new StringBuilder();
for (int i = 0; i < lines.Count; i++) {
res.AppendLine(lines[i]);
}
return res.ToString();
}
}
#endregion
/// <summary>
/// Converts our original text into a line based format
/// that can be more quickly updated.
/// </summary>
private void TextToLines() {
if (lines == null) {
lines = GetLines(originalText);
// no longer needed.
originalText = null;
}
}
}
}
}
See more files for this project here