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;
19 using System.Threading;
20 using System.Threading.Tasks;
21 using Slusser.Collections.Generic;
23 namespace FooEditEngine
26 /// ランダムアクセス可能な列挙子を提供するインターフェイス
28 /// <typeparam name="T"></typeparam>
29 public interface IRandomEnumrator<T>
34 /// <param name="index">インデックス</param>
35 /// <returns>Tを返す</returns>
36 T this[int index]{get;}
39 sealed class StringBuffer : IEnumerable<char>, IRandomEnumrator<char>
41 GapBuffer<char> buf = new GapBuffer<char>();
42 const int MaxSemaphoreCount = 1;
43 SemaphoreSlim Semaphore = new SemaphoreSlim(MaxSemaphoreCount);
47 this.Update = (s, e) => { };
51 /// ロック中なら真を返し、そうでないなら偽を返す
57 return this.Semaphore.CurrentCount == 0;
66 this.Semaphore.Release();
74 this.Semaphore.Wait();
80 /// <returns>Taskオブジェクト</returns>
81 public Task LockAsync()
83 return this.Semaphore.WaitAsync();
86 public StringBuffer(StringBuffer buffer)
89 buf.AddRange(buffer.buf, buffer.Length);
93 public char this[int index]
102 public string ToString(int index, int length)
106 StringBuilder temp = new StringBuilder();
108 for (int i = index; i < index + length; i++)
113 return temp.ToString();
116 public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
118 foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, maxCharCount))
120 StringBuilder temp = new StringBuilder();
122 int lineEndIndex = range.Item1;
124 lineEndIndex += range.Item2 - 1;
125 for (int i = range.Item1; i <= lineEndIndex; i++)
127 yield return temp.ToString();
131 public IEnumerable<Tuple<int,int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
133 int currentLineHeadIndex = startIndex;
134 int currentLineLength = 0;
136 for (int i = startIndex; i <= endIndex; i++)
139 char c = this.buf[i];
140 if (c == Document.NewLine ||
141 (maxCharCount != -1 && currentLineLength >= maxCharCount))
143 UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
144 if (uc != UnicodeCategory.NonSpacingMark &&
145 uc != UnicodeCategory.SpacingCombiningMark &&
146 uc != UnicodeCategory.EnclosingMark &&
147 uc != UnicodeCategory.Surrogate)
149 yield return new Tuple<int,int>(currentLineHeadIndex, currentLineLength);
150 currentLineHeadIndex += currentLineLength;
151 currentLineLength = 0;
155 if (currentLineLength > 0)
156 yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);
161 get { return this.buf.Count; }
164 internal DocumentUpdateEventHandler Update;
166 internal void Replace(StringBuffer buf)
168 this.Replace(buf.buf);
171 internal void Replace(GapBuffer<char> buf)
180 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));
183 internal void Replace(int index, int length, IEnumerable<char> chars,int count)
189 this.buf.RemoveRange(index, length);
190 this.buf.InsertRange(index, chars, count);
197 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, index, length, count));
200 internal async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null)
202 char[] str = new char[1024 * 256];
206 int index = this.Length;
210 readCount = await fs.ReadAsync(str, 0, str.Length).ConfigureAwait(false);
213 var internal_str = from s in str where s != '\r' && s != '\0' select s;
215 await this.LockAsync().ConfigureAwait(false);
216 //str.lengthは事前に確保しておくために使用するので影響はない
217 this.buf.InsertRange(index, internal_str, str.Length);
221 if (tokenSource != null)
222 tokenSource.Token.ThrowIfCancellationRequested();
224 System.Diagnostics.Debug.WriteLine("waiting now");
225 await Task.Delay(100).ConfigureAwait(false);
227 Array.Clear(str, 0, str.Length);
228 } while (readCount > 0);
231 internal async Task SaveAsync(TextWriter fs, CancellationTokenSource tokenSource = null)
235 await this.LockAsync().ConfigureAwait(false);
236 StringBuilder line = new StringBuilder();
237 for (int i = 0; i < this.Length; i++)
241 if (c == Document.NewLine || i == this.Length - 1)
243 string str = line.ToString();
244 str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
245 await fs.WriteAsync(str).ConfigureAwait(false);
247 if (tokenSource != null)
248 tokenSource.Token.ThrowIfCancellationRequested();
250 System.Threading.Thread.Sleep(10);
261 internal void ReplaceRegexAll(LineToIndexTable layoutlines, Regex regex, string pattern, bool groupReplace)
263 for (int i = 0; i < layoutlines.Count; i++)
265 int lineHeadIndex = layoutlines.GetIndexFromLineNumber(i), lineLength = layoutlines.GetLengthFromLineNumber(i);
266 int left = lineHeadIndex, right = lineHeadIndex;
267 string output = regex.Replace(layoutlines[i], (m) => {
269 return m.Result(pattern);
278 if (lineHeadIndex < this.buf.Count)
279 this.buf.RemoveRange(lineHeadIndex, lineLength);
280 this.buf.InsertRange(lineHeadIndex, output, output.Length);
287 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, lineHeadIndex, lineLength, output.Length, i));
291 internal void ReplaceAll(LineToIndexTable layoutlines,string target, string pattern, bool ci = false)
293 TextSearch ts = new TextSearch(target, ci);
294 char[] pattern_chars = pattern.ToCharArray();
295 for(int i = 0; i < layoutlines.Count; i++)
297 int lineHeadIndex = layoutlines.GetIndexFromLineNumber(i), lineLength = layoutlines.GetLengthFromLineNumber(i);
298 int left = lineHeadIndex, right = lineHeadIndex;
299 int newLineLength = lineLength;
300 while ((right = ts.IndexOf(this.buf, left, lineHeadIndex + newLineLength)) != -1)
305 this.buf.RemoveRange(right, target.Length);
306 this.buf.InsertRange(right, pattern_chars, pattern.Length);
313 left = right + pattern.Length;
314 newLineLength += pattern.Length - target.Length;
318 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, lineHeadIndex, lineLength, newLineLength, i));
322 internal int IndexOf(string target, int start,bool ci = false)
326 TextSearch ts = new TextSearch(target,ci);
327 int patternIndex = ts.IndexOf(this.buf, start,this.buf.Count);
337 internal void Clear()
340 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, 0, this.buf.Count,0));
343 internal IEnumerable<char> GetEnumerator(int start, int length)
345 for (int i = start; i < start + length; i++)
346 yield return this.buf[i];
349 #region IEnumerable<char> メンバー
351 public IEnumerator<char> GetEnumerator()
353 for (int i = 0; i < this.Length; i++)
354 yield return this.buf[i];
359 #region IEnumerable メンバー
361 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
363 for (int i = 0; i < this.Length; i++)
364 yield return this[i];
370 sealed class TextSearch
374 Dictionary<char, int> qsTable = new Dictionary<char, int>();
376 public TextSearch(string pattern,bool ci = false)
378 this.patternLength = pattern.Length;
379 this.caseInsenstive = ci;
382 this.CreateQSTable(pattern.ToLower());
383 this.CreateQSTable(pattern.ToUpper());
384 this.pattern = new char[pattern.Length];
385 for (int i = 0; i < pattern.Length; i++)
386 this.pattern[i] = CharTool.ToUpperFastIf(pattern[i]);
390 this.CreateQSTable(pattern);
391 this.pattern = pattern.ToCharArray();
394 void CreateQSTable(string pattern)
396 int len = pattern.Length;
397 for (int i = 0; i < len; i++)
399 if (!this.qsTable.ContainsKey(pattern[i]))
400 this.qsTable.Add(pattern[i], len - i);
402 this.qsTable[pattern[i]] = len - i;
405 public int IndexOf(GapBuffer<char> buf, int start,int end)
408 int buflen = buf.Count - 1;
409 int plen = this.patternLength;
411 int search_end = end - plen;
413 if (this.caseInsenstive)
415 while (i <= search_end)
420 if (CharTool.ToUpperFastIf(buf[i + j]) != this.pattern[j])
431 if (k <= buflen) //buffer以降にアクセスする可能性がある
434 if (this.qsTable.TryGetValue(buf[k], out moveDelta))
449 while (i <= search_end)
454 if (buf[i + j] != this.pattern[j])
465 if (k <= buflen) //buffer以降にアクセスする可能性がある
468 if (this.qsTable.TryGetValue(buf[k], out moveDelta))
483 static class CharTool
486 /// Converts characters to lowercase.
488 const string _lookupStringL =
489 "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[-]^_`abcdefghijklmnopqrstuvwxyz{|}~-";
492 /// Converts characters to uppercase.
494 const string _lookupStringU =
495 "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[-]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~-";
498 /// Get lowercase version of this ASCII character.
500 public static char ToLower(char c)
502 return _lookupStringL[c];
506 /// Get uppercase version of this ASCII character.
508 public static char ToUpper(char c)
510 return _lookupStringU[c];
514 /// Translate uppercase ASCII characters to lowercase.
516 public static char ToLowerFastIf(char c)
518 if (c >= 'A' && c <= 'Z')
520 return (char)(c + 32);
529 /// Translate lowercase ASCII characters to uppercase.
531 public static char ToUpperFastIf(char c)
533 if (c >= 'a' && c <= 'z')
535 return (char)(c - 32);