2 * Copyright (C) 2013 FooProject
3 * * 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
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
14 using System.Collections.Generic;
15 using System.Globalization;
18 using System.Text.RegularExpressions;
20 using System.Threading;
21 using System.Threading.Tasks;
22 using Slusser.Collections.Generic;
24 namespace FooEditEngine
27 /// ランダムアクセス可能な列挙子を提供するインターフェイス
29 /// <typeparam name="T"></typeparam>
30 public interface IRandomEnumrator<T>
35 /// <param name="index">インデックス</param>
36 /// <returns>Tを返す</returns>
37 T this[int index] { get; }
40 sealed class StringBuffer : IEnumerable<char>, IRandomEnumrator<char>
42 GapBuffer<char> buf = new GapBuffer<char>();
43 const int MaxSemaphoreCount = 1;
44 AsyncReaderWriterLock rwlock = new AsyncReaderWriterLock();
48 this.Update = (s, e) => { };
51 public StringBuffer(StringBuffer buffer)
54 buf.AddRange(buffer.buf, buffer.Length);
58 public char this[int index]
67 public string ToString(int index, int length)
69 StringBuilder temp = new StringBuilder();
71 using (this.rwlock.ReaderLock())
73 for (int i = index; i < index + length; i++)
76 return temp.ToString();
79 public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
81 foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, maxCharCount))
83 StringBuilder temp = new StringBuilder();
85 int lineEndIndex = range.Item1;
87 lineEndIndex += range.Item2 - 1;
88 for (int i = range.Item1; i <= lineEndIndex; i++)
90 yield return temp.ToString();
94 public IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
96 int currentLineHeadIndex = startIndex;
97 int currentLineLength = 0;
99 for (int i = startIndex; i <= endIndex; i++)
102 char c = this.buf[i];
103 if (c == Document.NewLine ||
104 (maxCharCount != -1 && currentLineLength >= maxCharCount))
106 UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
107 if (uc != UnicodeCategory.NonSpacingMark &&
108 uc != UnicodeCategory.SpacingCombiningMark &&
109 uc != UnicodeCategory.EnclosingMark &&
110 uc != UnicodeCategory.Surrogate)
112 yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);
113 currentLineHeadIndex += currentLineLength;
114 currentLineLength = 0;
118 if (currentLineLength > 0)
119 yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);
124 get { return this.buf.Count; }
127 internal DocumentUpdateEventHandler Update;
129 internal void Replace(StringBuffer buf)
131 this.Replace(buf.buf);
134 internal void Replace(GapBuffer<char> buf)
136 using (this.rwlock.WriterLock())
142 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));
145 internal void Replace(int index, int length, IEnumerable<char> chars, int count)
147 using (this.rwlock.WriterLock())
150 this.buf.RemoveRange(index, length);
151 this.buf.InsertRange(index, chars, count);
153 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, index, length, count));
156 internal async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null)
158 char[] str = new char[1024 * 256];
162 int index = this.Length;
166 readCount = await fs.ReadAsync(str, 0, str.Length).ConfigureAwait(false);
169 var internal_str = from s in str where s != '\r' && s != '\0' select s;
171 using (await this.rwlock.WriterLockAsync())
173 //str.lengthは事前に確保しておくために使用するので影響はない
174 this.buf.InsertRange(index, internal_str, str.Length);
177 if (tokenSource != null)
178 tokenSource.Token.ThrowIfCancellationRequested();
180 System.Diagnostics.Debug.WriteLine("waiting now");
181 await Task.Delay(100).ConfigureAwait(false);
183 Array.Clear(str, 0, str.Length);
184 } while (readCount > 0);
187 internal async Task SaveAsync(TextWriter fs, CancellationTokenSource tokenSource = null)
189 using(await this.rwlock.ReaderLockAsync())
191 StringBuilder line = new StringBuilder();
192 for (int i = 0; i < this.Length; i++)
196 if (c == Document.NewLine || i == this.Length - 1)
198 string str = line.ToString();
199 str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
200 await fs.WriteAsync(str).ConfigureAwait(false);
202 if (tokenSource != null)
203 tokenSource.Token.ThrowIfCancellationRequested();
205 System.Threading.Thread.Sleep(10);
212 internal void ReplaceRegexAll(LineToIndexTable layoutlines, Regex regex, string pattern, bool groupReplace)
214 for (int i = 0; i < layoutlines.Count; i++)
216 int lineHeadIndex = layoutlines.GetIndexFromLineNumber(i), lineLength = layoutlines.GetLengthFromLineNumber(i);
217 int left = lineHeadIndex, right = lineHeadIndex;
220 output = regex.Replace(layoutlines[i], (m) => {
222 return m.Result(pattern);
227 using (this.rwlock.WriterLock())
230 if (lineHeadIndex < this.buf.Count)
231 this.buf.RemoveRange(lineHeadIndex, lineLength);
232 this.buf.InsertRange(lineHeadIndex, output, output.Length);
235 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, lineHeadIndex, lineLength, output.Length, i));
239 internal void ReplaceAll(LineToIndexTable layoutlines, string target, string pattern, bool ci = false)
241 TextSearch ts = new TextSearch(target, ci);
242 char[] pattern_chars = pattern.ToCharArray();
243 for (int i = 0; i < layoutlines.Count; i++)
245 int lineHeadIndex = layoutlines.GetIndexFromLineNumber(i), lineLength = layoutlines.GetLengthFromLineNumber(i);
246 int left = lineHeadIndex, right = lineHeadIndex;
247 int newLineLength = lineLength;
248 while ((right = ts.IndexOf(this.buf, left, lineHeadIndex + newLineLength)) != -1)
250 using (this.rwlock.WriterLock())
252 this.buf.RemoveRange(right, target.Length);
253 this.buf.InsertRange(right, pattern_chars, pattern.Length);
255 left = right + pattern.Length;
256 newLineLength += pattern.Length - target.Length;
259 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, lineHeadIndex, lineLength, newLineLength, i));
263 internal int IndexOf(string target, int start, bool ci = false)
265 using (this.rwlock.ReaderLock())
267 TextSearch ts = new TextSearch(target, ci);
268 int patternIndex = ts.IndexOf(this.buf, start, this.buf.Count);
276 internal void Clear()
279 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, 0, this.buf.Count, 0));
282 internal IEnumerable<char> GetEnumerator(int start, int length)
284 for (int i = start; i < start + length; i++)
285 yield return this.buf[i];
288 #region IEnumerable<char> メンバー
290 public IEnumerator<char> GetEnumerator()
292 for (int i = 0; i < this.Length; i++)
293 yield return this.buf[i];
298 #region IEnumerable メンバー
300 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
302 for (int i = 0; i < this.Length; i++)
303 yield return this[i];
309 sealed class TextSearch
313 Dictionary<char, int> qsTable = new Dictionary<char, int>();
315 public TextSearch(string pattern, bool ci = false)
317 this.patternLength = pattern.Length;
318 this.caseInsenstive = ci;
321 this.CreateQSTable(pattern.ToLower());
322 this.CreateQSTable(pattern.ToUpper());
323 this.pattern = new char[pattern.Length];
324 for (int i = 0; i < pattern.Length; i++)
325 this.pattern[i] = CharTool.ToUpperFastIf(pattern[i]);
329 this.CreateQSTable(pattern);
330 this.pattern = pattern.ToCharArray();
333 void CreateQSTable(string pattern)
335 int len = pattern.Length;
336 for (int i = 0; i < len; i++)
338 if (!this.qsTable.ContainsKey(pattern[i]))
339 this.qsTable.Add(pattern[i], len - i);
341 this.qsTable[pattern[i]] = len - i;
344 public int IndexOf(GapBuffer<char> buf, int start, int end)
347 int buflen = buf.Count - 1;
348 int plen = this.patternLength;
350 int search_end = end - plen;
352 if (this.caseInsenstive)
354 while (i <= search_end)
359 if (CharTool.ToUpperFastIf(buf[i + j]) != this.pattern[j])
370 if (k <= buflen) //buffer以降にアクセスする可能性がある
373 if (this.qsTable.TryGetValue(buf[k], out moveDelta))
388 while (i <= search_end)
393 if (buf[i + j] != this.pattern[j])
404 if (k <= buflen) //buffer以降にアクセスする可能性がある
407 if (this.qsTable.TryGetValue(buf[k], out moveDelta))
422 static class CharTool
425 /// Converts characters to lowercase.
427 const string _lookupStringL =
428 "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[-]^_`abcdefghijklmnopqrstuvwxyz{|}~-";
431 /// Converts characters to uppercase.
433 const string _lookupStringU =
434 "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[-]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~-";
437 /// Get lowercase version of this ASCII character.
439 public static char ToLower(char c)
441 return _lookupStringL[c];
445 /// Get uppercase version of this ASCII character.
447 public static char ToUpper(char c)
449 return _lookupStringU[c];
453 /// Translate uppercase ASCII characters to lowercase.
455 public static char ToLowerFastIf(char c)
457 if (c >= 'A' && c <= 'Z')
459 return (char)(c + 32);
468 /// Translate lowercase ASCII characters to uppercase.
470 public static char ToUpperFastIf(char c)
472 if (c >= 'a' && c <= 'z')
474 return (char)(c - 32);