You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-//#define TEST_ASYNC
-
using System;
using System.IO;
+using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
+using System.Runtime.CompilerServices;
namespace FooEditEngine
{
public sealed class DocumentUpdateEventArgs : EventArgs
{
/// <summary>
+ /// 値が指定されていないことを示す
+ /// </summary>
+ public const int EmptyValue = -1;
+ /// <summary>
/// 更新タイプ
/// </summary>
public UpdateType type;
/// <param name="removeLength">削除された長さ</param>
/// <param name="insertLength">追加された長さ</param>
/// <param name="row">開始行。nullを指定することができる</param>
- public DocumentUpdateEventArgs(UpdateType type, int startIndex, int removeLength, int insertLength, int? row = null)
+ public DocumentUpdateEventArgs(UpdateType type, int startIndex = EmptyValue, int removeLength = EmptyValue, int insertLength = EmptyValue, int? row = null)
{
this.type = type;
this.startIndex = startIndex;
/// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>
public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>
{
- const int MaxSemaphoreCount = 1;
Regex regex;
Match match;
StringBuffer buffer;
LineToIndexTable _LayoutLines;
- bool _EnableFireUpdateEvent = true,_UrlMark = false, _DrawLineNumber = false, _HideRuler = true;
- SemaphoreSlim Semaphore = new SemaphoreSlim(MaxSemaphoreCount);
+ bool _EnableFireUpdateEvent = true,_UrlMark = false, _DrawLineNumber = false, _HideRuler = true, _RightToLeft = false;
LineBreakMethod _LineBreak;
int _TabStops, _LineBreakCharCount = 80;
+ bool _ShowFullSpace, _ShowHalfSpace, _ShowTab, _ShowLineBreak,_InsertMode, _HideCaret, _HideLineMarker, _RectSelection;
+ IndentMode _IndentMode;
/// <summary>
/// 一行当たりの最大文字数
/// <summary>
/// コンストラクター
/// </summary>
- internal Document()
+ public Document()
: this(null)
{
}
- internal Document(Document doc)
+ /// <summary>
+ /// コンストラクター
+ /// </summary>
+ /// <param name="doc">ドキュメントオブジェクト</param>
+ /// <remarks>docが複製されますが、プロパティは引き継がれません</remarks>
+ public Document(Document doc)
{
if (doc == null)
this.buffer = new StringBuffer();
this.UpdateCalledAlways += (s, e) => { };
this.Update += new DocumentUpdateEventHandler((s, e) => { });
this.ChangeFireUpdateEvent += new EventHandler((s, e) => { });
- this.Markers = new MarkerCollection(this);
+ this.StatusUpdate += new EventHandler((s, e) => { });
+ this.Markers = new MarkerCollection();
this.UndoManager = new UndoManager();
this._LayoutLines = new LineToIndexTable(this);
this._LayoutLines.SpilitString = (s,e)=> {
this._LayoutLines.Clear();
this.MarkerPatternSet = new MarkerPatternSet(this._LayoutLines, this.Markers);
this.MarkerPatternSet.Updated += WacthDogPattern_Updated;
- this.LineBreakChanged += (s, e) => { };
- this.TabStopsChanged += (s, e) => { };
- this.DrawLineNumberChanged += (s, e) => { };
this.Selections = new SelectCollection();
+ this.CaretPostion = new TextPoint();
this.HideLineMarker = true;
- this.HideRulerChanged += (s, e) => { };
- this.CaretPostion = TextPoint.Null;
+ this.SelectGrippers = new GripperRectangle(new Gripper(), new Gripper());
+ this.SelectionChanged += new EventHandler((s, e) => { });
}
void WacthDogPattern_Updated(object sender, EventArgs e)
}
/// <summary>
- /// 矩形選択モードなら真を返し、そうでない場合は偽を返す
+ /// キャレットでの選択の起点となる位置
/// </summary>
- public bool RectSelection
+ internal int AnchorIndex
{
get;
set;
}
/// <summary>
- /// ã\82¤ã\83³ã\83\87ã\83³ã\83\88ã\81®æ\96¹æ³\95を表す
+ /// ã\83¬ã\82¿ã\83ªã\83³ã\82°ã\81®é\96\8bå§\8bä½\8dç½®を表す
/// </summary>
- public IndentMode IndentMode
+ internal SrcPoint Src
{
get;
set;
}
/// <summary>
- /// ã\83©ã\82¤ã\83³ã\83\9eã\83¼ã\82«ã\83¼ã\82\92æ\8f\8fã\81\8fã\81ªã\82\89å\81½ã\80\82ã\81\9dã\81\86ã\81§ã\81ªã\81\91ã\82\8cã\81°ç\9c\9f
+ /// ã\83\89ã\82ã\83¥ã\83¡ã\83³ã\83\88ã\81®ã\82¿ã\82¤ã\83\88ã\83«
/// </summary>
- public bool HideLineMarker
+ public string Title
{
get;
set;
}
+ public event ProgressEventHandler LoadProgress;
+
/// <summary>
- /// ã\82ã\83£ã\83¬ã\83\83ã\83\88ã\82\92æ\8f\8fã\81\8fã\81ªã\82\89å\81½ã\80\82ã\81\9dã\81\86ã\81§ã\81ªã\81\91ã\82\8cã\81°ç\9c\9f
+ /// ã\83«ã\83¼ã\83©ã\83¼ã\82\84ã\82ã\83£ã\83¬ã\83\83ã\83\88ã\83»è¡\8cç\95ªå\8f·ã\81ªã\81©ã\81®è¡¨ç¤ºã\81\99ã\81¹ã\81\8dã\82\82ã\81®ã\81\8cå¤\89å\8c\96ã\81\97ã\81\9få ´å\90\88ã\81«å\91¼ã\81³å\87ºã\81\95ã\82\8cã\82\8bã\80\82ã\83\89ã\82ã\83¥ã\83¡ã\83³ã\83\88ã\81®å\86\85容ã\81\8cå¤\89å\8c\96ã\81\97ã\81\9fé\80\9aç\9f¥ã\82\92å\8f\97ã\81\91å\8f\96ã\82\8aå ´å\90\88ã\81¯Updateã\82\92使ç\94¨ã\81\97ã\81¦ã\81\8fã\81 ã\81\95ã\81\84
/// </summary>
- public bool HideCaret
+ public event EventHandler StatusUpdate;
+
+ /// <summary>
+ /// 全角スペースを表示するかどうか
+ /// </summary>
+ public bool ShowFullSpace
{
- get;
- set;
+ get { return this._ShowFullSpace; }
+ set
+ {
+ if (this._ShowFullSpace == value)
+ return;
+ this._ShowFullSpace = value;
+ this.StatusUpdate(this, null);
+ }
}
/// <summary>
- /// 挿入モードなら真を返し、上書きモードなら偽を返す
+ /// 半角スペースを表示するかどうか
/// </summary>
- public bool InsertMode
+ public bool ShowHalfSpace
+ {
+ get { return this._ShowHalfSpace; }
+ set
+ {
+ if (this._ShowHalfSpace == value)
+ return;
+ this._ShowHalfSpace = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// TABを表示するかどうか
+ /// </summary>
+ public bool ShowTab
+ {
+ get { return this._ShowTab; }
+ set
+ {
+ if (this._ShowTab == value)
+ return;
+ this._ShowTab = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// 改行を表示するかどうか
+ /// </summary>
+ public bool ShowLineBreak
{
+ get { return this._ShowLineBreak; }
+ set
+ {
+ if (this._ShowLineBreak == value)
+ return;
+ this._ShowLineBreak = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// 選択範囲にあるグリッパーのリスト
+ /// </summary>
+ internal GripperRectangle SelectGrippers
+ {
+ private set;
get;
- set;
}
/// <summary>
- /// HideRulerの値が変わったときに通知します
+ /// 右から左に表示するなら真
+ /// </summary>
+ public bool RightToLeft {
+ get { return this._RightToLeft; }
+ set
+ {
+ if (this._RightToLeft == value)
+ return;
+ this._RightToLeft = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// 矩形選択モードなら真を返し、そうでない場合は偽を返す
+ /// </summary>
+ public bool RectSelection
+ {
+ get
+ {
+ return this._RectSelection;
+ }
+ set
+ {
+ this._RectSelection = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// インデントの方法を表す
+ /// </summary>
+ public IndentMode IndentMode
+ {
+ get
+ {
+ return this._IndentMode;
+ }
+ set
+ {
+ this._IndentMode = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// ラインマーカーを描くなら偽。そうでなければ真
+ /// </summary>
+ public bool HideLineMarker
+ {
+ get
+ {
+ return this._HideLineMarker;
+ }
+ set
+ {
+ this._HideLineMarker = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// キャレットを描くなら偽。そうでなければ真
+ /// </summary>
+ public bool HideCaret
+ {
+ get
+ {
+ return this._HideCaret;
+ }
+ set
+ {
+ this._HideCaret = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// 挿入モードなら真を返し、上書きモードなら偽を返す
/// </summary>
- public event EventHandler HideRulerChanged;
+ public bool InsertMode
+ {
+ get
+ {
+ return this._InsertMode;
+ }
+ set
+ {
+ this._InsertMode = value;
+ this.StatusUpdate(this, null);
+ }
+ }
/// <summary>
/// ルーラーを表示しないなら真、そうでないなら偽
get { return this._HideRuler; }
set
{
+ if (this._HideRuler == value)
+ return;
this._HideRuler = value;
this.LayoutLines.ClearLayoutCache();
- this.HideRulerChanged(this, null);
+ this.StatusUpdate(this, null);
}
}
}
/// <summary>
- /// DrawLineNumberの値が変わったときに通知される
- /// </summary>
- public event EventHandler DrawLineNumberChanged;
-
- /// <summary>
/// 行番号を表示するかどうか
/// </summary>
public bool DrawLineNumber
get { return this._DrawLineNumber; }
set
{
+ if (this._DrawLineNumber == value)
+ return;
this._DrawLineNumber = value;
this._LayoutLines.ClearLayoutCache();
- this.DrawLineNumberChanged(this, null);
+ this.StatusUpdate(this, null);
}
}
get { return this._UrlMark; }
set
{
+ if (this._UrlMark == value)
+ return;
this._UrlMark = value;
if (value)
{
{
this.MarkerPatternSet.Remove(MarkerIDs.URL);
}
+ this.StatusUpdate(this, null);
}
}
/// <summary>
- /// LineBreakが変更されたときに通知される
+ /// 桁折りの方法が変わったことを表す
/// </summary>
- public EventHandler LineBreakChanged;
+ public event EventHandler LineBreakChanged;
/// <summary>
/// 桁折り処理の方法を指定する
/// </summary>
/// <remarks>
/// 変更した場合、呼び出し側で再描写とレイアウトの再構築を行う必要があります
+ /// また、StatusUpdatedではなく、LineBreakChangedイベントが発生します
/// </remarks>
public LineBreakMethod LineBreak
{
}
set
{
+ if (this._LineBreak == value)
+ return;
this._LineBreak = value;
this.LineBreakChanged(this, null);
}
}
set
{
+ if (this._LineBreakCharCount == value)
+ return;
this._LineBreakCharCount = value;
this.LineBreakChanged(this, null);
}
}
/// <summary>
- /// TabStopsの値が変わったことを通知する
- /// </summary>
- public event EventHandler TabStopsChanged;
-
- /// <summary>
/// タブの幅
/// </summary>
/// <remarks>変更した場合、呼び出し側で再描写する必要があります</remarks>
{
get { return this._TabStops; }
set {
+ if (this._TabStops == value)
+ return;
this._TabStops = value;
- this.TabStopsChanged(this, null);
+ this.StatusUpdate(this, null);
}
}
public const char EndOfFile = '\u001a';
/// <summary>
- /// ロック中なら真を返し、そうでないなら偽を返す
- /// </summary>
- public bool IsLocked
- {
- get
- {
- return this.Semaphore.CurrentCount == 0;
- }
- }
-
- /// <summary>
/// アンドゥ管理クラスを表す
/// </summary>
public UndoManager UndoManager
}
/// <summary>
- /// DocumentReaderを作成します
+ /// 選択領域変更時に通知される
/// </summary>
- /// <returns>DocumentReaderオブジェクト</returns>
- public DocumentReader CreateReader()
+ public event EventHandler SelectionChanged;
+
+ /// <summary>
+ /// 指定された範囲を選択する
+ /// </summary>
+ /// <param name="start"></param>
+ /// <param name="length"></param>
+ /// <remarks>RectSelectionの値によって動作が変わります。真の場合は矩形選択モードに、そうでない場合は行ごとに選択されます</remarks>
+ public void Select(int start, int length)
{
- return new DocumentReader(this.buffer);
+ if (this.FireUpdateEvent == false)
+ throw new InvalidOperationException("");
+ if (start < 0 || start + length < 0 || start + length > this.Length)
+ throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます");
+ this.Selections.Clear();
+ if (length < 0)
+ {
+ int oldStart = start;
+ start += length;
+ length = oldStart - start;
+ }
+ if (this.RectSelection && length != 0)
+ {
+ TextPoint startTextPoint = this.LayoutLines.GetTextPointFromIndex(start);
+ TextPoint endTextPoint = this.LayoutLines.GetTextPointFromIndex(start + length);
+ this.SelectByRectangle(new TextRectangle(startTextPoint, endTextPoint));
+ }
+ else if (length != 0)
+ {
+ this.Selections.Add(Selection.Create(start, length));
+ }
+ this.SelectionChanged(this, null);
}
/// <summary>
- /// ロックを解除します
+ /// 矩形選択を行う
/// </summary>
- public void UnLock()
+ /// <param name="tp">開始位置</param>
+ /// <param name="width">桁数</param>
+ /// <param name="height">行数</param>
+ public void Select(TextPoint tp, int width, int height)
{
- this.Semaphore.Release();
+ if (this.FireUpdateEvent == false || !this.RectSelection)
+ throw new InvalidOperationException("");
+ TextPoint end = tp;
+
+ end.row = tp.row + height;
+ end.col = tp.col + width;
+
+ if (end.row > this.LayoutLines.Count - 1)
+ throw new ArgumentOutOfRangeException("");
+
+ this.Selections.Clear();
+
+ this.SelectByRectangle(new TextRectangle(tp, end));
+
+ this.SelectionChanged(this, null);
+ }
+
+ private void SelectByRectangle(TextRectangle rect)
+ {
+ if (this.FireUpdateEvent == false)
+ throw new InvalidOperationException("");
+ if (rect.TopLeft <= rect.BottomRight)
+ {
+ for (int i = rect.TopLeft.row; i <= rect.BottomLeft.row; i++)
+ {
+ int length = this.LayoutLines.GetLengthFromLineNumber(i);
+ int leftCol = rect.TopLeft.col, rightCol = rect.TopRight.col, lastCol = length;
+ if (length > 0 && this.LayoutLines[i][length - 1] == Document.NewLine)
+ lastCol = length - 1;
+ if (lastCol < 0)
+ lastCol = 0;
+ if (rect.TopLeft.col > lastCol)
+ leftCol = lastCol;
+ if (rect.TopRight.col > lastCol)
+ rightCol = lastCol;
+
+ int StartIndex = this.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, leftCol));
+ int EndIndex = this.LayoutLines.GetIndexFromTextPoint(new TextPoint(i, rightCol));
+
+ Selection sel;
+ sel = Selection.Create(StartIndex, EndIndex - StartIndex);
+
+ this.Selections.Add(sel);
+ }
+ }
}
/// <summary>
- /// ロックします
+ /// 単語単位で選択する
/// </summary>
- public void Lock()
+ /// <param name="index">探索を開始するインデックス</param>
+ /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
+ public void SelectWord(int index, bool changeAnchor = false)
{
- this.Semaphore.Wait();
+ if (this.FireUpdateEvent == false)
+ throw new InvalidOperationException("");
+
+ if (this.Length <= 0 || index >= this.Length)
+ return;
+
+ Document str = this;
+
+ int start = index;
+ while (start > 0 && !Util.IsWordSeparator(str[start]))
+ start--;
+
+ if (Util.IsWordSeparator(str[start]))
+ start++;
+
+ int end = index;
+ while (end < this.Length && !Util.IsWordSeparator(str[end]))
+ end++;
+
+ this.Select(start, end - start);
+
+ if (changeAnchor)
+ this.AnchorIndex = start;
}
/// <summary>
- /// ロックします
+ /// DocumentReaderを作成します
/// </summary>
- /// <returns>Taskオブジェクト</returns>
- public Task LockAsync()
+ /// <returns>DocumentReaderオブジェクト</returns>
+ public DocumentReader CreateReader()
{
- return this.Semaphore.WaitAsync();
+ return new DocumentReader(this.buffer);
}
/// <summary>
}
/// <summary>
+ /// すべてのマーカーを削除する
+ /// </summary>
+ /// <param name="id">マーカーID</param>
+ public void RemoveAllMarker(int id)
+ {
+ this.Markers.RemoveAll(id);
+ }
+
+ /// <summary>
/// インデックスに対応するマーカーを得る
/// </summary>
/// <param name="id">マーカーID</param>
/// 読み取り操作は別スレッドで行われます。
/// また、非同期操作中はこのメソッドを実行することはできません。
/// </remarks>
- internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)
+ public async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null)
{
- if (fs.IsEnd())
+ if (fs.Peek() == -1)
return;
+ if (this.LoadProgress != null)
+ this.LoadProgress(this, new ProgressEventArgs(ProgressState.Start));
+
try
{
- await this.LockAsync().ConfigureAwait(false);
this.Clear();
this.FireUpdateEvent = false;
+ this.LayoutLines.IsFrozneDirtyFlag = true;
await this.buffer.LoadAsync(fs, tokenSource);
}
finally
{
this.FireUpdateEvent = true;
- this.UnLock();
+ //これ以降の操作にだけダーティフラグを適用しないとおかしなことになる
+ this.LayoutLines.IsFrozneDirtyFlag = false;
+ if (this.LoadProgress != null)
+ this.LoadProgress(this, new ProgressEventArgs(ProgressState.Complete));
}
}
/// <param name="tokenSource">キャンセルトークン</param>
/// <returns>Taskオブジェクト</returns>
/// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
- internal async Task SaveAsync(IStreamWriter fs, CancellationTokenSource tokenSource = null)
+ public async Task SaveAsync(TextWriter fs, CancellationTokenSource tokenSource = null)
{
try
{
- await this.LockAsync().ConfigureAwait(false);
+ await this.buffer.LockAsync().ConfigureAwait(false);
StringBuilder line = new StringBuilder();
for (int i = 0; i < this.Length; i++)
{
}
finally
{
- this.UnLock();
+ this.buffer.UnLock();
}
}
{
case UpdateType.Replace:
if (e.row == null)
+ {
this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);
+ this.Markers.UpdateMarkers(e.startIndex, e.insertLength, e.removeLength);
+ }
else
+ {
this._LayoutLines.UpdateLineAsReplace(e.row.Value, e.removeLength, e.insertLength);
+ this.Markers.UpdateMarkers(this.LayoutLines.GetIndexFromLineNumber(e.row.Value), e.insertLength, e.removeLength);
+ }
break;
case UpdateType.Clear:
this._LayoutLines.Clear();
}
}
- public interface IStreamReader
- {
- /// <summary>
- /// ストリームが空かどうかを返す
- /// </summary>
- bool IsEnd();
-
- /// <summary>
- /// ストリームから行を読み取った物を返す。LoadAsyncを呼び出す場合は必ず実装してください
- /// </summary>
- Task<string> ReadLineAsync();
- /// <summary>
- /// ストリームから指定した文字数だけ読み取る
- /// </summary>
- /// <param name="buffer">書き込み先バッファー</param>
- /// <param name="index">書き込み先バッファーのインデックス</param>
- /// <param name="count">読み取る文字数</param>
- /// <returns>読み取った文字数</returns>
- Task<int> ReadAsync(char[] buffer, int index, int count);
- }
-
- public interface IStreamWriter
- {
- /// <summary>
- /// ストリームに書き込む。SaveAsyncを呼び出す場合は必ず実装してください
- /// </summary>
- Task WriteAsync(string str);
-
- /// <summary>
- /// 書き込む際に使用する改行コード
- /// </summary>
- string NewLine
- {
- get;
- set;
- }
- }
-
/// <summary>
/// 検索結果を表す
/// </summary>
{
this.document = null;
}
+
}
}