//#define debug using System; using System.Collections.Generic; using System.Text; using System.IO; namespace FastColoredTextBoxNS { /// /// This class contains the source text (chars and styles). /// It stores a text lines, the manager of commands, undo/redo stack, styles. /// public class FileTextSource : TextSource, IDisposable { List sourceFileLinePositions = new List(); FileStream fs; Encoding fileEncoding; System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); /// /// Occurs when need to display line in the textbox /// public event EventHandler LineNeeded; /// /// Occurs when need to save line in the file /// public event EventHandler 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; } /// /// End Of Line characters used for saving /// public string SaveEOL { get; set; } public override void SaveToFile(string fileName, Encoding enc) { // var newLinePos = new List(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; } /// /// This text will be displayed in textbox /// 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; } /// /// This property contains only changed text. /// If text of line is not changed, this property contains null. /// public string DisplayedLineText { get; private set; } /// /// This text will be saved in the file /// 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; } } }