/*
* 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;
}
}
}
}