2 * Copyright (C) 2013 FooProject
\r
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
\r
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
\r
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
\r
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/>.
\r
12 using System.Collections.Generic;
\r
13 using System.Globalization;
\r
16 using System.Threading;
\r
17 using System.Threading.Tasks;
\r
18 using Slusser.Collections.Generic;
\r
20 namespace FooEditEngine
\r
23 /// ランダムアクセス可能な列挙子を提供するインターフェイス
\r
25 /// <typeparam name="T"></typeparam>
\r
26 public interface IRandomEnumrator<T>
\r
31 /// <param name="index">インデックス</param>
\r
32 /// <returns>Tを返す</returns>
\r
33 T this[int index]{get;}
\r
36 sealed class StringBuffer : IEnumerable<char>, IRandomEnumrator<char>
\r
38 GapBuffer<char> buf = new GapBuffer<char>();
\r
40 public StringBuffer()
\r
42 this.Update += (s, e) => { };
\r
45 public StringBuffer(StringBuffer buffer)
\r
48 buf.AddRange(buffer.buf, buffer.Length);
\r
51 public char this[int index]
\r
55 char c = buf[index];
\r
60 public string ToString(int index, int length)
\r
62 StringBuilder temp = new StringBuilder();
\r
64 for (int i = index; i < index + length; i++)
\r
65 temp.Append(buf[i]);
\r
66 return temp.ToString();
\r
69 public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
\r
71 foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, maxCharCount))
\r
73 StringBuilder temp = new StringBuilder();
\r
75 int lineEndIndex = range.Item1;
\r
76 if (range.Item2 > 0)
\r
77 lineEndIndex += range.Item2 - 1;
\r
78 for (int i = range.Item1; i <= lineEndIndex; i++)
\r
79 temp.Append(buf[i]);
\r
80 yield return temp.ToString();
\r
84 public IEnumerable<Tuple<int,int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
\r
86 int currentLineHeadIndex = startIndex;
\r
87 int currentLineLength = 0;
\r
89 for (int i = startIndex; i <= endIndex; i++)
\r
91 currentLineLength++;
\r
92 char c = this.buf[i];
\r
93 if (c == Document.NewLine ||
\r
94 (maxCharCount != -1 && currentLineLength >= maxCharCount))
\r
96 UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
\r
97 if (uc != UnicodeCategory.NonSpacingMark &&
\r
98 uc != UnicodeCategory.SpacingCombiningMark &&
\r
99 uc != UnicodeCategory.EnclosingMark &&
\r
100 uc != UnicodeCategory.Surrogate)
\r
102 yield return new Tuple<int,int>(currentLineHeadIndex, currentLineLength);
\r
103 currentLineHeadIndex += currentLineLength;
\r
104 currentLineLength = 0;
\r
108 if (currentLineLength > 0)
\r
109 yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);
\r
114 get { return this.buf.Count; }
\r
117 internal event DocumentUpdateEventHandler Update;
\r
119 internal void Replace(StringBuffer buf)
\r
121 this.Replace(buf.buf);
\r
124 internal void Replace(GapBuffer<char> buf)
\r
128 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));
\r
131 internal void Replace(int index, int length, IEnumerable<char> chars,int count)
\r
134 this.buf.RemoveRange(index, length);
\r
135 this.buf.InsertRange(index, chars,count);
\r
136 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, index, length, count));
\r
139 internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)
\r
142 for (int i = 0; (str = await fs.ReadLineAsync().ConfigureAwait(false)) != null; i++)
\r
144 int index = this.Length;
\r
148 this.buf.InsertRange(index, str + Document.NewLine, str.Length + 1);
\r
150 if (tokenSource != null)
\r
151 tokenSource.Token.ThrowIfCancellationRequested();
\r
153 System.Threading.Thread.Sleep(10);
\r
156 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));
\r
157 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));
\r
160 internal void Replace(string target, string pattern,bool ci = false)
\r
162 TextSearch ts = new TextSearch(target,ci);
\r
163 int left = 0, right = 0;
\r
164 char[] pattern_chars = pattern.ToCharArray();
\r
165 while((right = ts.IndexOf(this.buf,left)) != -1)
\r
167 this.buf.RemoveRange(right, target.Length);
\r
168 this.buf.InsertRange(right, pattern_chars, pattern.Length);
\r
169 left = right + pattern.Length;
\r
171 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));
\r
172 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));
\r
175 internal int IndexOf(string target, int start,bool ci = false)
\r
177 TextSearch ts = new TextSearch(target,ci);
\r
178 return ts.IndexOf(this.buf, start);
\r
184 internal void Clear()
\r
187 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, 0, this.buf.Count,0));
\r
190 internal IEnumerable<char> GetEnumerator(int start, int length)
\r
192 for (int i = start; i < start + length; i++)
\r
193 yield return this.buf[i];
\r
196 #region IEnumerable<char> メンバー
\r
198 public IEnumerator<char> GetEnumerator()
\r
200 for (int i = 0; i < this.Length; i++)
\r
201 yield return this.buf[i];
\r
206 #region IEnumerable メンバー
\r
208 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
\r
210 for (int i = 0; i < this.Length; i++)
\r
211 yield return this[i];
\r
217 sealed class TextSearch
\r
221 Dictionary<char, int> qsTable = new Dictionary<char, int>();
\r
222 bool caseInsenstive;
\r
223 public TextSearch(string pattern,bool ci = false)
\r
225 this.patternLength = pattern.Length;
\r
226 this.caseInsenstive = ci;
\r
229 this.CreateQSTable(pattern.ToLower());
\r
230 this.CreateQSTable(pattern.ToUpper());
\r
231 this.pattern = new char[pattern.Length];
\r
232 for (int i = 0; i < pattern.Length; i++)
\r
233 this.pattern[i] = CharTool.ToUpperFastIf(pattern[i]);
\r
237 this.CreateQSTable(pattern);
\r
238 this.pattern = pattern.ToCharArray();
\r
241 void CreateQSTable(string pattern)
\r
243 int len = pattern.Length;
\r
244 for (int i = 0; i < len; i++)
\r
246 if (!this.qsTable.ContainsKey(pattern[i]))
\r
247 this.qsTable.Add(pattern[i], len - i);
\r
249 this.qsTable[pattern[i]] = len - i;
\r
252 public int IndexOf(GapBuffer<char> buf, int start)
\r
255 int buflen = buf.Count - 1;
\r
256 int plen = this.patternLength;
\r
258 int end = buf.Count - plen;
\r
260 if (this.caseInsenstive)
\r
267 if (CharTool.ToUpperFastIf(buf[i + j]) != this.pattern[j])
\r
278 if (k <= buflen) //buffer以降にアクセスする可能性がある
\r
281 if (this.qsTable.TryGetValue(buf[k], out moveDelta))
\r
301 if (buf[i + j] != this.pattern[j])
\r
312 if (k <= buflen) //buffer以降にアクセスする可能性がある
\r
315 if (this.qsTable.TryGetValue(buf[k], out moveDelta))
\r
330 static class CharTool
\r
333 /// Converts characters to lowercase.
\r
335 const string _lookupStringL =
\r
336 "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[-]^_`abcdefghijklmnopqrstuvwxyz{|}~-";
\r
339 /// Converts characters to uppercase.
\r
341 const string _lookupStringU =
\r
342 "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[-]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~-";
\r
345 /// Get lowercase version of this ASCII character.
\r
347 public static char ToLower(char c)
\r
349 return _lookupStringL[c];
\r
353 /// Get uppercase version of this ASCII character.
\r
355 public static char ToUpper(char c)
\r
357 return _lookupStringU[c];
\r
361 /// Translate uppercase ASCII characters to lowercase.
\r
363 public static char ToLowerFastIf(char c)
\r
365 if (c >= 'A' && c <= 'Z')
\r
367 return (char)(c + 32);
\r
376 /// Translate lowercase ASCII characters to uppercase.
\r
378 public static char ToUpperFastIf(char c)
\r
380 if (c >= 'a' && c <= 'z')
\r
382 return (char)(c - 32);
\r