Mesen-X/GUI.NET/Debugger/FastColoredTextBox/FileTextSource.cs

454 lines
14 KiB
C#
Raw Normal View History

2017-08-30 18:31:27 -04:00
//#define debug
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace FastColoredTextBoxNS
{
/// <summary>
/// This class contains the source text (chars and styles).
/// It stores a text lines, the manager of commands, undo/redo stack, styles.
/// </summary>
public class FileTextSource : TextSource, IDisposable
{
List<int> sourceFileLinePositions = new List<int>();
FileStream fs;
Encoding fileEncoding;
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
/// <summary>
/// Occurs when need to display line in the textbox
/// </summary>
public event EventHandler<LineNeededEventArgs> LineNeeded;
/// <summary>
/// Occurs when need to save line in the file
/// </summary>
public event EventHandler<LinePushedEventArgs> LinePushed;
public FileTextSource(FastColoredTextBox currentTB)
: base(currentTB)
{
timer.Interval = 10000;
timer.Tick += new EventHandler(timer_Tick);
timer.Enabled = true;
SaveEOL = Environment.NewLine;
}
void timer_Tick(object sender, EventArgs e)
{
timer.Enabled = false;
try
{
UnloadUnusedLines();
}
finally
{
timer.Enabled = true;
}
}
private void UnloadUnusedLines()
{
const int margin = 2000;
var iStartVisibleLine = CurrentTB.VisibleRange.Start.iLine;
var iFinishVisibleLine = CurrentTB.VisibleRange.End.iLine;
int count = 0;
for (int i = 0; i < Count; i++)
if (base.lines[i] != null && !base.lines[i].IsChanged && Math.Abs(i - iFinishVisibleLine) > margin)
{
base.lines[i] = null;
count++;
}
#if debug
Console.WriteLine("UnloadUnusedLines: " + count);
#endif
}
public void OpenFile(string fileName, Encoding enc)
{
Clear();
if (fs != null)
fs.Dispose();
SaveEOL = Environment.NewLine;
//read lines of file
fs = new FileStream(fileName, FileMode.Open);
var length = fs.Length;
//read signature
enc = DefineEncoding(enc, fs);
int shift = DefineShift(enc);
//first line
sourceFileLinePositions.Add((int)fs.Position);
base.lines.Add(null);
//other lines
sourceFileLinePositions.Capacity = (int)(length/7 + 1000);
int prev = 0;
while(fs.Position < length)
{
var b = fs.ReadByte();
if (b == 10)// \n
{
sourceFileLinePositions.Add((int)(fs.Position) + shift);
base.lines.Add(null);
}else
if (prev == 13)// \r (Mac format)
{
sourceFileLinePositions.Add((int)(fs.Position - 1) + shift);
base.lines.Add(null);
SaveEOL = "\r";
}
prev = b;
}
if (prev == 13)
{
sourceFileLinePositions.Add((int)(fs.Position) + shift);
base.lines.Add(null);
}
if(length > 2000000)
GC.Collect();
Line[] temp = new Line[100];
var c = base.lines.Count;
base.lines.AddRange(temp);
base.lines.TrimExcess();
base.lines.RemoveRange(c, temp.Length);
int[] temp2 = new int[100];
c = base.lines.Count;
sourceFileLinePositions.AddRange(temp2);
sourceFileLinePositions.TrimExcess();
sourceFileLinePositions.RemoveRange(c, temp.Length);
fileEncoding = enc;
OnLineInserted(0, Count);
//load first lines for calc width of the text
var linesCount = Math.Min(lines.Count, CurrentTB.ClientRectangle.Height/CurrentTB.CharHeight);
for (int i = 0; i < linesCount; i++)
LoadLineFromSourceFile(i);
//
NeedRecalc(new TextChangedEventArgs(0, linesCount - 1));
if (CurrentTB.WordWrap)
OnRecalcWordWrap(new TextChangedEventArgs(0, linesCount - 1));
}
private int DefineShift(Encoding enc)
{
if (enc.IsSingleByte)
return 0;
if (enc.HeaderName == "unicodeFFFE")
return 0;//UTF16 BE
if (enc.HeaderName == "utf-16")
return 1;//UTF16 LE
if (enc.HeaderName == "utf-32BE")
return 0;//UTF32 BE
if (enc.HeaderName == "utf-32")
return 3;//UTF32 LE
return 0;
}
private static Encoding DefineEncoding(Encoding enc, FileStream fs)
{
int bytesPerSignature = 0;
byte[] signature = new byte[4];
int c = fs.Read(signature, 0, 4);
if (signature[0] == 0xFF && signature[1] == 0xFE && signature[2] == 0x00 && signature[3] == 0x00 && c >= 4)
{
enc = Encoding.UTF32;//UTF32 LE
bytesPerSignature = 4;
}
else
if (signature[0] == 0x00 && signature[1] == 0x00 && signature[2] == 0xFE && signature[3] == 0xFF)
{
enc = new UTF32Encoding(true, true);//UTF32 BE
bytesPerSignature = 4;
}
else
if (signature[0] == 0xEF && signature[1] == 0xBB && signature[2] == 0xBF)
{
enc = Encoding.UTF8;//UTF8
bytesPerSignature = 3;
}
else
if (signature[0] == 0xFE && signature[1] == 0xFF)
{
enc = Encoding.BigEndianUnicode;//UTF16 BE
bytesPerSignature = 2;
}
else
if (signature[0] == 0xFF && signature[1] == 0xFE)
{
enc = Encoding.Unicode;//UTF16 LE
bytesPerSignature = 2;
}
fs.Seek(bytesPerSignature, SeekOrigin.Begin);
return enc;
}
public void CloseFile()
{
if(fs!=null)
try
{
fs.Dispose();
}
catch
{
;
}
fs = null;
}
/// <summary>
/// End Of Line characters used for saving
/// </summary>
public string SaveEOL { get; set; }
public override void SaveToFile(string fileName, Encoding enc)
{
//
var newLinePos = new List<int>(Count);
//create temp file
var dir = Path.GetDirectoryName(fileName);
var tempFileName = Path.Combine(dir, Path.GetFileNameWithoutExtension(fileName) + ".tmp");
StreamReader sr = new StreamReader(fs, fileEncoding);
using (FileStream tempFs = new FileStream(tempFileName, FileMode.Create))
using (StreamWriter sw = new StreamWriter(tempFs, enc))
{
sw.Flush();
for (int i = 0; i < Count; i++)
{
newLinePos.Add((int)tempFs.Length);
var sourceLine = ReadLine(sr, i);//read line from source file
string line;
bool lineIsChanged = lines[i] != null && lines[i].IsChanged;
if (lineIsChanged)
line = lines[i].Text;
else
line = sourceLine;
//call event handler
if (LinePushed != null)
{
var args = new LinePushedEventArgs(sourceLine, i, lineIsChanged ? line : null);
LinePushed(this, args);
if(args.SavedText != null)
line = args.SavedText;
}
//save line to file
sw.Write(line);
if (i < Count - 1)
sw.Write(SaveEOL);
sw.Flush();
}
}
//clear lines buffer
for (int i = 0; i < Count; i++)
lines[i] = null;
//deattach from source file
sr.Dispose();
fs.Dispose();
//delete target file
if (File.Exists(fileName))
File.Delete(fileName);
//rename temp file
File.Move(tempFileName, fileName);
//binding to new file
sourceFileLinePositions = newLinePos;
fs = new FileStream(fileName, FileMode.Open);
this.fileEncoding = enc;
}
private string ReadLine(StreamReader sr, int i)
{
string line;
var filePos = sourceFileLinePositions[i];
if (filePos < 0)
return "";
fs.Seek(filePos, SeekOrigin.Begin);
sr.DiscardBufferedData();
line = sr.ReadLine();
return line;
}
public override void ClearIsChanged()
{
foreach (var line in lines)
if(line!=null)
line.IsChanged = false;
}
public override Line this[int i]
{
get
{
if (base.lines[i] != null)
return lines[i];
else
LoadLineFromSourceFile(i);
return lines[i];
}
set
{
throw new NotImplementedException();
}
}
private void LoadLineFromSourceFile(int i)
{
var line = CreateLine();
fs.Seek(sourceFileLinePositions[i], SeekOrigin.Begin);
StreamReader sr = new StreamReader(fs, fileEncoding);
var s = sr.ReadLine();
if (s == null)
s = "";
//call event handler
if(LineNeeded!=null)
{
var args = new LineNeededEventArgs(s, i);
LineNeeded(this, args);
s = args.DisplayedLineText;
if (s == null)
return;
}
foreach (var c in s)
line.Add(new Char(c));
base.lines[i] = line;
if (CurrentTB.WordWrap)
OnRecalcWordWrap(new TextChangedEventArgs(i, i));
}
public override void InsertLine(int index, Line line)
{
sourceFileLinePositions.Insert(index, -1);
base.InsertLine(index, line);
}
public override void RemoveLine(int index, int count)
{
sourceFileLinePositions.RemoveRange(index, count);
base.RemoveLine(index, count);
}
public override void Clear()
{
base.Clear();
}
public override int GetLineLength(int i)
{
if (base.lines[i] == null)
return 0;
else
return base.lines[i].Count;
}
public override bool LineHasFoldingStartMarker(int iLine)
{
if (lines[iLine] == null)
return false;
else
return !string.IsNullOrEmpty(lines[iLine].FoldingStartMarker);
}
public override bool LineHasFoldingEndMarker(int iLine)
{
if (lines[iLine] == null)
return false;
else
return !string.IsNullOrEmpty(lines[iLine].FoldingEndMarker);
}
public override void Dispose()
{
if (fs != null)
fs.Dispose();
timer.Dispose();
}
internal void UnloadLine(int iLine)
{
if (lines[iLine] != null && !lines[iLine].IsChanged)
lines[iLine] = null;
}
}
public class LineNeededEventArgs : EventArgs
{
public string SourceLineText { get; private set; }
public int DisplayedLineIndex { get; private set; }
/// <summary>
/// This text will be displayed in textbox
/// </summary>
public string DisplayedLineText { get; set; }
public LineNeededEventArgs(string sourceLineText, int displayedLineIndex)
{
this.SourceLineText = sourceLineText;
this.DisplayedLineIndex = displayedLineIndex;
this.DisplayedLineText = sourceLineText;
}
}
public class LinePushedEventArgs : EventArgs
{
public string SourceLineText { get; private set; }
public int DisplayedLineIndex { get; private set; }
/// <summary>
/// This property contains only changed text.
/// If text of line is not changed, this property contains null.
/// </summary>
public string DisplayedLineText { get; private set; }
/// <summary>
/// This text will be saved in the file
/// </summary>
public string SavedText { get; set; }
public LinePushedEventArgs(string sourceLineText, int displayedLineIndex, string displayedLineText)
{
this.SourceLineText = sourceLineText;
this.DisplayedLineIndex = displayedLineIndex;
this.DisplayedLineText = displayedLineText;
this.SavedText = displayedLineText;
}
}
}