OSDN Git Service

コア部分を共通プロジェクト化した
[fooeditengine/FooEditEngine.git] / Core / StringBuffer.cs
1 /*
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.
5
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.
8
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/>.
10  */
11 using System;
12 using System.Collections.Generic;
13 using System.Globalization;
14 using System.Linq;
15 using System.Text;
16 using System.Text.RegularExpressions;
17 using System.Threading;
18 using System.Threading.Tasks;
19 using Slusser.Collections.Generic;
20
21 namespace FooEditEngine
22 {
23     /// <summary>
24     /// ランダムアクセス可能な列挙子を提供するインターフェイス
25     /// </summary>
26     /// <typeparam name="T"></typeparam>
27     public interface IRandomEnumrator<T>
28     {
29         /// <summary>
30         /// インデクサーを表す
31         /// </summary>
32         /// <param name="index">インデックス</param>
33         /// <returns>Tを返す</returns>
34         T this[int index]{get;}
35     }
36
37     sealed class StringBuffer : IEnumerable<char>, IRandomEnumrator<char>
38     {
39         GapBuffer<char> buf = new GapBuffer<char>();
40
41         public StringBuffer()
42         {
43             this.Update = (s, e) => { };
44         }
45
46         public StringBuffer(StringBuffer buffer)
47             : this()
48         {
49             buf.AddRange(buffer.buf, buffer.Length);
50         }
51
52         public char this[int index]
53         {
54             get
55             {
56                 char c = buf[index];
57                 return c;
58             }
59         }
60
61         public string ToString(int index, int length)
62         {
63             StringBuilder temp = new StringBuilder();
64             temp.Clear();
65             for (int i = index; i < index + length; i++)
66                 temp.Append(buf[i]);
67             return temp.ToString();
68         }
69
70         public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
71         {
72             foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, maxCharCount))
73             {
74                 StringBuilder temp = new StringBuilder();
75                 temp.Clear();
76                 int lineEndIndex = range.Item1;
77                 if (range.Item2 > 0)
78                     lineEndIndex += range.Item2 - 1;
79                 for (int i = range.Item1; i <= lineEndIndex; i++)
80                     temp.Append(buf[i]);
81                 yield return temp.ToString();
82             }
83         }
84
85         public IEnumerable<Tuple<int,int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
86         {
87             int currentLineHeadIndex = startIndex;
88             int currentLineLength = 0;
89             
90             for (int i = startIndex; i <= endIndex; i++)
91             {
92                 currentLineLength++;
93                 char c = this.buf[i];
94                 if (c == Document.NewLine ||
95                     (maxCharCount != -1 && currentLineLength >= maxCharCount))
96                 {
97                     UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
98                     if (uc != UnicodeCategory.NonSpacingMark &&
99                     uc != UnicodeCategory.SpacingCombiningMark &&
100                     uc != UnicodeCategory.EnclosingMark &&
101                     uc != UnicodeCategory.Surrogate)
102                     {
103                         yield return new Tuple<int,int>(currentLineHeadIndex, currentLineLength);
104                         currentLineHeadIndex += currentLineLength;
105                         currentLineLength = 0;
106                     }
107                 }
108             }
109             if (currentLineLength > 0)
110                 yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);
111         }
112         
113         public int Length
114         {
115             get { return this.buf.Count; }
116         }
117
118         internal DocumentUpdateEventHandler Update;
119
120         internal void Replace(StringBuffer buf)
121         {
122             this.Replace(buf.buf);
123         }
124
125         internal void Replace(GapBuffer<char> buf)
126         {
127             this.Clear();
128             this.buf = buf;
129             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));
130         }
131
132         internal void Replace(int index, int length, IEnumerable<char> chars,int count)
133         {
134             if (length > 0)
135                 this.buf.RemoveRange(index, length);
136             this.buf.InsertRange(index, chars,count);
137             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, index, length, count));
138         }
139
140         internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)
141         {
142             char[] str = new char[1024 * 256];
143             int readCount;
144             do
145             {
146                 int index = this.Length;
147                 if (index < 0)
148                     index = 0;
149
150                 readCount = await fs.ReadAsync(str, 0, str.Length).ConfigureAwait(false);
151                 
152                 //内部形式に変換する
153                 var internal_str = from s in str where s != '\r' && s != '\0' select s;
154
155                 //str.lengthは事前に確保しておくために使用するので影響はない
156                 this.buf.InsertRange(index, internal_str, str.Length);
157
158                 if (tokenSource != null)
159                     tokenSource.Token.ThrowIfCancellationRequested();
160 #if TEST_ASYNC
161                     System.Threading.Thread.Sleep(10);
162 #endif
163                 Array.Clear(str, 0, str.Length);
164             } while (readCount > 0);
165             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));
166             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));
167         }
168
169         internal void ReplaceRegexAll(LineToIndexTable layoutlines, Regex regex, string pattern, bool groupReplace)
170         {
171             for (int i = 0; i < layoutlines.Count; i++)
172             {
173                 int lineHeadIndex = layoutlines.GetIndexFromLineNumber(i), lineLength = layoutlines.GetLengthFromLineNumber(i);
174                 int left = lineHeadIndex, right = lineHeadIndex;
175                 string output = regex.Replace(layoutlines[i], (m) => {
176                     if (groupReplace)
177                         return m.Result(pattern);
178                     else
179                         return pattern;
180                 });
181                 this.buf.RemoveRange(lineHeadIndex, lineLength);
182                 this.buf.InsertRange(lineHeadIndex, output, output.Length);
183                 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, lineHeadIndex, lineLength, output.Length, i));
184             }
185         }
186
187         internal void ReplaceAll(LineToIndexTable layoutlines,string target, string pattern, bool ci = false)
188         {
189             TextSearch ts = new TextSearch(target, ci);
190             char[] pattern_chars = pattern.ToCharArray();
191             for(int i = 0; i < layoutlines.Count; i++)
192             {
193                 int lineHeadIndex = layoutlines.GetIndexFromLineNumber(i), lineLength = layoutlines.GetLengthFromLineNumber(i);
194                 int left = lineHeadIndex, right = lineHeadIndex;
195                 int newLineLength = lineLength;
196                 while ((right = ts.IndexOf(this.buf, left, lineHeadIndex + newLineLength)) != -1)
197                 {
198                     this.buf.RemoveRange(right, target.Length);
199                     this.buf.InsertRange(right, pattern_chars, pattern.Length);
200                     left = right + pattern.Length;
201                     newLineLength += pattern.Length - target.Length;
202                 }
203                 this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, lineHeadIndex, lineLength, newLineLength, i));
204             }
205         }
206
207         internal int IndexOf(string target, int start,bool ci = false)
208         {
209             TextSearch ts = new TextSearch(target,ci);
210             return ts.IndexOf(this.buf, start,this.buf.Count);
211         }
212
213         /// <summary>
214         /// 文字列を削除する
215         /// </summary>
216         internal void Clear()
217         {
218             this.buf.Clear();
219             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, 0, this.buf.Count,0));
220         }
221
222         internal IEnumerable<char> GetEnumerator(int start, int length)
223         {
224             for (int i = start; i < start + length; i++)
225                 yield return this.buf[i];
226         }
227
228         #region IEnumerable<char> メンバー
229
230         public IEnumerator<char> GetEnumerator()
231         {
232             for (int i = 0; i < this.Length; i++)
233                 yield return this.buf[i];
234         }
235
236         #endregion
237
238         #region IEnumerable メンバー
239
240         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
241         {
242             for (int i = 0; i < this.Length; i++)
243                 yield return this[i];
244         }
245
246         #endregion
247     }
248
249     sealed class TextSearch
250     {
251         char[] pattern;
252         int patternLength;
253         Dictionary<char, int> qsTable = new Dictionary<char, int>();
254         bool caseInsenstive;
255         public TextSearch(string pattern,bool ci = false)
256         {
257             this.patternLength = pattern.Length;
258             this.caseInsenstive = ci;
259             if (ci)
260             {
261                 this.CreateQSTable(pattern.ToLower());
262                 this.CreateQSTable(pattern.ToUpper());
263                 this.pattern = new char[pattern.Length];
264                 for (int i = 0; i < pattern.Length; i++)
265                     this.pattern[i] = CharTool.ToUpperFastIf(pattern[i]);
266             }
267             else
268             {
269                 this.CreateQSTable(pattern);
270                 this.pattern = pattern.ToCharArray();
271             }
272         }
273         void CreateQSTable(string pattern)
274         {
275             int len = pattern.Length;
276             for (int i = 0; i < len; i++)
277             {
278                 if (!this.qsTable.ContainsKey(pattern[i]))
279                     this.qsTable.Add(pattern[i], len - i);
280                 else
281                     this.qsTable[pattern[i]] = len - i;
282             }
283         }
284         public int IndexOf(GapBuffer<char> buf, int start,int end)
285         {
286             //QuickSearch法
287             int buflen = buf.Count - 1;
288             int plen = this.patternLength;
289             int i = start;
290             int search_end = end - plen;
291             //最適化のためわざとコピペした
292             if (this.caseInsenstive)
293             {
294                 while (i <= search_end)
295                 {
296                     int j = 0;
297                     while (j < plen)
298                     {
299                         if (CharTool.ToUpperFastIf(buf[i + j]) != this.pattern[j])
300                             break;
301                         j++;
302                     }
303                     if (j == plen)
304                     {
305                         return i;
306                     }
307                     else
308                     {
309                         int k = i + plen;
310                         if (k <= buflen)        //buffer以降にアクセスする可能性がある
311                         {
312                             int moveDelta;
313                             if (this.qsTable.TryGetValue(buf[k], out moveDelta))
314                                 i += moveDelta;
315                             else
316                                 i += plen;
317                         }
318                         else
319                         {
320                             break;
321                         }
322                     }
323                 }
324
325             }
326             else
327             {
328                 while (i <= search_end)
329                 {
330                     int j = 0;
331                     while (j < plen)
332                     {
333                         if (buf[i + j] != this.pattern[j])
334                             break;
335                         j++;
336                     }
337                     if (j == plen)
338                     {
339                         return i;
340                     }
341                     else
342                     {
343                         int k = i + plen;
344                         if (k <= buflen)        //buffer以降にアクセスする可能性がある
345                         {
346                             int moveDelta;
347                             if (this.qsTable.TryGetValue(buf[k], out moveDelta))
348                                 i += moveDelta;
349                             else
350                                 i += plen;
351                         }
352                         else
353                         {
354                             break;
355                         }
356                     }
357                 }
358             }
359             return -1;
360         }
361     }
362     static class CharTool
363     {
364         /// <summary>
365         /// Converts characters to lowercase.
366         /// </summary>
367         const string _lookupStringL =
368         "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[-]^_`abcdefghijklmnopqrstuvwxyz{|}~-";
369
370         /// <summary>
371         /// Converts characters to uppercase.
372         /// </summary>
373         const string _lookupStringU =
374         "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[-]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~-";
375
376         /// <summary>
377         /// Get lowercase version of this ASCII character.
378         /// </summary>
379         public static char ToLower(char c)
380         {
381             return _lookupStringL[c];
382         }
383
384         /// <summary>
385         /// Get uppercase version of this ASCII character.
386         /// </summary>
387         public static char ToUpper(char c)
388         {
389             return _lookupStringU[c];
390         }
391
392         /// <summary>
393         /// Translate uppercase ASCII characters to lowercase.
394         /// </summary>
395         public static char ToLowerFastIf(char c)
396         {
397             if (c >= 'A' && c <= 'Z')
398             {
399                 return (char)(c + 32);
400             }
401             else
402             {
403                 return c;
404             }
405         }
406
407         /// <summary>
408         /// Translate lowercase ASCII characters to uppercase.
409         /// </summary>
410         public static char ToUpperFastIf(char c)
411         {
412             if (c >= 'a' && c <= 'z')
413             {
414                 return (char)(c - 32);
415             }
416             else
417             {
418                 return c;
419             }
420         }
421     }
422 }