/* * Copyright (C) 2013 FooProject * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ //#define TEST_ASYNC using System; using System.IO; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Slusser.Collections.Generic; namespace FooEditEngine { /// /// ランダムアクセス可能な列挙子を提供するインターフェイス /// /// public interface IRandomEnumrator { /// /// インデクサーを表す /// /// インデックス /// Tを返す T this[int index]{get;} } sealed class StringBuffer : IEnumerable, IRandomEnumrator { GapBuffer buf = new GapBuffer(); const int MaxSemaphoreCount = 1; SemaphoreSlim Semaphore = new SemaphoreSlim(MaxSemaphoreCount); public StringBuffer() { this.Update = (s, e) => { }; } /// /// ロック中なら真を返し、そうでないなら偽を返す /// public bool IsLocked { get { return this.Semaphore.CurrentCount == 0; } } /// /// ロックを解除します /// public void UnLock() { this.Semaphore.Release(); } /// /// ロックします /// public void Lock() { this.Semaphore.Wait(); } /// /// ロックします /// /// Taskオブジェクト public Task LockAsync() { return this.Semaphore.WaitAsync(); } public StringBuffer(StringBuffer buffer) : this() { buf.AddRange(buffer.buf, buffer.Length); } public char this[int index] { get { char c = buf[index]; return c; } } public string ToString(int index, int length) { this.Lock(); StringBuilder temp = new StringBuilder(); temp.Clear(); for (int i = index; i < index + length; i++) temp.Append(buf[i]); this.UnLock(); return temp.ToString(); } public IEnumerable GetLines(int startIndex, int endIndex, int maxCharCount = -1) { foreach (Tuple range in this.ForEachLines(startIndex, endIndex, maxCharCount)) { StringBuilder temp = new StringBuilder(); temp.Clear(); int lineEndIndex = range.Item1; if (range.Item2 > 0) lineEndIndex += range.Item2 - 1; for (int i = range.Item1; i <= lineEndIndex; i++) temp.Append(buf[i]); yield return temp.ToString(); } } public IEnumerable> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1) { int currentLineHeadIndex = startIndex; int currentLineLength = 0; for (int i = startIndex; i <= endIndex; i++) { currentLineLength++; char c = this.buf[i]; if (c == Document.NewLine || (maxCharCount != -1 && currentLineLength >= maxCharCount)) { UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c); if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.SpacingCombiningMark && uc != UnicodeCategory.EnclosingMark && uc != UnicodeCategory.Surrogate) { yield return new Tuple(currentLineHeadIndex, currentLineLength); currentLineHeadIndex += currentLineLength; currentLineLength = 0; } } } if (currentLineLength > 0) yield return new Tuple(currentLineHeadIndex, currentLineLength); } public int Length { get { return this.buf.Count; } } internal DocumentUpdateEventHandler Update; internal void Replace(StringBuffer buf) { this.Replace(buf.buf); } internal void Replace(GapBuffer buf) { this.Lock(); this.Clear(); this.buf = buf; this.UnLock(); this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count)); } internal void Replace(int index, int length, IEnumerable chars,int count) { this.Lock(); try{ if (length > 0) this.buf.RemoveRange(index, length); this.buf.InsertRange(index, chars, count); } finally { this.UnLock(); } this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, index, length, count)); } internal async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null) { char[] str = new char[1024 * 256]; int readCount; do { int index = this.Length; if (index < 0) index = 0; readCount = await fs.ReadAsync(str, 0, str.Length).ConfigureAwait(false); //内部形式に変換する var internal_str = from s in str where s != '\r' && s != '\0' select s; await this.LockAsync().ConfigureAwait(false); //str.lengthは事前に確保しておくために使用するので影響はない this.buf.InsertRange(index, internal_str, str.Length); this.UnLock(); if (tokenSource != null) tokenSource.Token.ThrowIfCancellationRequested(); #if TEST_ASYNC System.Diagnostics.Debug.WriteLine("waiting now"); await Task.Delay(100).ConfigureAwait(false); #endif Array.Clear(str, 0, str.Length); } while (readCount > 0); } internal void ReplaceRegexAll(LineToIndexTable layoutlines, Regex regex, string pattern, bool groupReplace) { for (int i = 0; i < layoutlines.Count; i++) { int lineHeadIndex = layoutlines.GetIndexFromLineNumber(i), lineLength = layoutlines.GetLengthFromLineNumber(i); int left = lineHeadIndex, right = lineHeadIndex; string output = regex.Replace(layoutlines[i], (m) => { if (groupReplace) return m.Result(pattern); else return pattern; }); this.Lock(); try { //空行は削除する必要はない if (lineHeadIndex < this.buf.Count) this.buf.RemoveRange(lineHeadIndex, lineLength); this.buf.InsertRange(lineHeadIndex, output, output.Length); } finally { this.UnLock(); } this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, lineHeadIndex, lineLength, output.Length, i)); } } internal void ReplaceAll(LineToIndexTable layoutlines,string target, string pattern, bool ci = false) { TextSearch ts = new TextSearch(target, ci); char[] pattern_chars = pattern.ToCharArray(); for(int i = 0; i < layoutlines.Count; i++) { int lineHeadIndex = layoutlines.GetIndexFromLineNumber(i), lineLength = layoutlines.GetLengthFromLineNumber(i); int left = lineHeadIndex, right = lineHeadIndex; int newLineLength = lineLength; while ((right = ts.IndexOf(this.buf, left, lineHeadIndex + newLineLength)) != -1) { this.Lock(); try { this.buf.RemoveRange(right, target.Length); this.buf.InsertRange(right, pattern_chars, pattern.Length); } finally { this.UnLock(); } left = right + pattern.Length; newLineLength += pattern.Length - target.Length; } this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, lineHeadIndex, lineLength, newLineLength, i)); } } internal int IndexOf(string target, int start,bool ci = false) { this.Lock(); TextSearch ts = new TextSearch(target,ci); int patternIndex = ts.IndexOf(this.buf, start,this.buf.Count); this.UnLock(); return patternIndex; } /// /// 文字列を削除する /// internal void Clear() { this.buf.Clear(); this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, 0, this.buf.Count,0)); } internal IEnumerable GetEnumerator(int start, int length) { for (int i = start; i < start + length; i++) yield return this.buf[i]; } #region IEnumerable メンバー public IEnumerator GetEnumerator() { for (int i = 0; i < this.Length; i++) yield return this.buf[i]; } #endregion #region IEnumerable メンバー System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { for (int i = 0; i < this.Length; i++) yield return this[i]; } #endregion } sealed class TextSearch { char[] pattern; int patternLength; Dictionary qsTable = new Dictionary(); bool caseInsenstive; public TextSearch(string pattern,bool ci = false) { this.patternLength = pattern.Length; this.caseInsenstive = ci; if (ci) { this.CreateQSTable(pattern.ToLower()); this.CreateQSTable(pattern.ToUpper()); this.pattern = new char[pattern.Length]; for (int i = 0; i < pattern.Length; i++) this.pattern[i] = CharTool.ToUpperFastIf(pattern[i]); } else { this.CreateQSTable(pattern); this.pattern = pattern.ToCharArray(); } } void CreateQSTable(string pattern) { int len = pattern.Length; for (int i = 0; i < len; i++) { if (!this.qsTable.ContainsKey(pattern[i])) this.qsTable.Add(pattern[i], len - i); else this.qsTable[pattern[i]] = len - i; } } public int IndexOf(GapBuffer buf, int start,int end) { //QuickSearch法 int buflen = buf.Count - 1; int plen = this.patternLength; int i = start; int search_end = end - plen; //最適化のためわざとコピペした if (this.caseInsenstive) { while (i <= search_end) { int j = 0; while (j < plen) { if (CharTool.ToUpperFastIf(buf[i + j]) != this.pattern[j]) break; j++; } if (j == plen) { return i; } else { int k = i + plen; if (k <= buflen) //buffer以降にアクセスする可能性がある { int moveDelta; if (this.qsTable.TryGetValue(buf[k], out moveDelta)) i += moveDelta; else i += plen; } else { break; } } } } else { while (i <= search_end) { int j = 0; while (j < plen) { if (buf[i + j] != this.pattern[j]) break; j++; } if (j == plen) { return i; } else { int k = i + plen; if (k <= buflen) //buffer以降にアクセスする可能性がある { int moveDelta; if (this.qsTable.TryGetValue(buf[k], out moveDelta)) i += moveDelta; else i += plen; } else { break; } } } } return -1; } } static class CharTool { /// /// Converts characters to lowercase. /// const string _lookupStringL = "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[-]^_`abcdefghijklmnopqrstuvwxyz{|}~-"; /// /// Converts characters to uppercase. /// const string _lookupStringU = "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[-]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~-"; /// /// Get lowercase version of this ASCII character. /// public static char ToLower(char c) { return _lookupStringL[c]; } /// /// Get uppercase version of this ASCII character. /// public static char ToUpper(char c) { return _lookupStringU[c]; } /// /// Translate uppercase ASCII characters to lowercase. /// public static char ToLowerFastIf(char c) { if (c >= 'A' && c <= 'Z') { return (char)(c + 32); } else { return c; } } /// /// Translate lowercase ASCII characters to uppercase. /// public static char ToUpperFastIf(char c) { if (c >= 'a' && c <= 'z') { return (char)(c - 32); } else { return c; } } } }