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
{
/// <summary>
+ /// オートインデントを行うためのデリゲートを表す
+ /// </summary>
+ /// <param name="sender">イベント発生元のオブジェクト</param>
+ /// <param name="e">イベントデーター</param>
+ public delegate void AutoIndentHookerHandler(object sender, EventArgs e);
+
+ /// <summary>
/// 進行状況を表す列挙体
/// </summary>
public enum ProgressState
/// ドキュメント全体が削除されたことを表す
/// </summary>
Clear,
+ /// <summary>
+ /// レイアウトが再構築されたことを表す
+ /// </summary>
+ RebuildLayout,
}
/// <summary>
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;
/// ドキュメントの管理を行う
/// </summary>
/// <remarks>この型のすべてのメソッド・プロパティはスレッドセーフです</remarks>
- public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>
+ public sealed class Document : IEnumerable<char>, IRandomEnumrator<char>, IDisposable
{
- const int MaxSemaphoreCount = 1;
Regex regex;
Match match;
StringBuffer buffer;
LineToIndexTable _LayoutLines;
bool _EnableFireUpdateEvent = true,_UrlMark = false, _DrawLineNumber = false, _HideRuler = true, _RightToLeft = false;
- SemaphoreSlim Semaphore = new SemaphoreSlim(MaxSemaphoreCount);
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();
else
this.buffer = new StringBuffer(doc.buffer);
this.buffer.Update = new DocumentUpdateEventHandler(buffer_Update);
- 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)=> {
- return this.CreateLineList(e.index, e.length, MaximumLineLength);
- };
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) => { });
+ this.CaretChanged += (s, e) => { };
+ this.AutoIndentHook += (s, e) => { };
+ this.LineBreakChanged += (s, e) => { };
+ this.Dirty = false;
}
void WacthDogPattern_Updated(object sender, EventArgs e)
}
/// <summary>
- /// HideRulerの値が変わったときに通知します
+ /// ダーティフラグ。保存されていなければ真、そうでなければ偽。
+ /// </summary>
+ public bool Dirty
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// キャレットでの選択の起点となる位置
+ /// </summary>
+ internal int AnchorIndex
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// レタリングの開始位置を表す
+ /// </summary>
+ internal SrcPoint Src
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// ドキュメントのタイトル
+ /// </summary>
+ public string Title
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// 補完候補プロセッサーが切り替わったときに発生するイベント
/// </summary>
- public event EventHandler RightToLeftChanged;
+ public event EventHandler AutoCompleteChanged;
+
+ AutoCompleteBoxBase _AutoComplete;
+ /// <summary>
+ /// 補完候補プロセッサー
+ /// </summary>
+ public AutoCompleteBoxBase AutoComplete
+ {
+ get
+ {
+ return this._AutoComplete;
+ }
+ set
+ {
+ this._AutoComplete = value;
+ if (this.AutoCompleteChanged != null)
+ this.AutoCompleteChanged(this, null);
+ }
+ }
+
+ /// <summary>
+ /// 読み込み中に発生するイベント
+ /// </summary>
+ public event ProgressEventHandler LoadProgress;
+
+ /// <summary>
+ /// ルーラーやキャレット・行番号などの表示すべきものが変化した場合に呼び出される。ドキュメントの内容が変化した通知を受け取り場合はUpdateを使用してください
+ /// </summary>
+ public event EventHandler StatusUpdate;
+
+ /// <summary>
+ /// 全角スペースを表示するかどうか
+ /// </summary>
+ public bool ShowFullSpace
+ {
+ get { return this._ShowFullSpace; }
+ set
+ {
+ if (this._ShowFullSpace == value)
+ return;
+ this._ShowFullSpace = value;
+ this.StatusUpdate(this, null);
+ }
+ }
+
+ /// <summary>
+ /// 半角スペースを表示するかどうか
+ /// </summary>
+ 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;
+ }
/// <summary>
/// 右から左に表示するなら真
get { return this._RightToLeft; }
set
{
+ if (this._RightToLeft == value)
+ return;
this._RightToLeft = value;
- this.RightToLeftChanged(this, null);
+ this.StatusUpdate(this, null);
}
}
/// </summary>
public bool RectSelection
{
- get;
- set;
+ get
+ {
+ return this._RectSelection;
+ }
+ set
+ {
+ this._RectSelection = value;
+ this.StatusUpdate(this, null);
+ }
}
/// <summary>
/// </summary>
public IndentMode IndentMode
{
- get;
- set;
+ get
+ {
+ return this._IndentMode;
+ }
+ set
+ {
+ this._IndentMode = value;
+ this.StatusUpdate(this, null);
+ }
}
/// <summary>
/// </summary>
public bool HideLineMarker
{
- get;
- set;
+ get
+ {
+ return this._HideLineMarker;
+ }
+ set
+ {
+ this._HideLineMarker = value;
+ this.StatusUpdate(this, null);
+ }
}
/// <summary>
/// </summary>
public bool HideCaret
{
- get;
- set;
+ get
+ {
+ return this._HideCaret;
+ }
+ set
+ {
+ this._HideCaret = value;
+ this.StatusUpdate(this, null);
+ }
}
/// <summary>
/// </summary>
public bool InsertMode
{
- get;
- set;
+ get
+ {
+ return this._InsertMode;
+ }
+ set
+ {
+ this._InsertMode = value;
+ this.StatusUpdate(this, null);
+ }
}
/// <summary>
- /// HideRulerの値が変わったときに通知します
- /// </summary>
- public event EventHandler HideRulerChanged;
-
- /// <summary>
/// ルーラーを表示しないなら真、そうでないなら偽
/// </summary>
public bool HideRuler
get { return this._HideRuler; }
set
{
+ if (this._HideRuler == value)
+ return;
this._HideRuler = value;
this.LayoutLines.ClearLayoutCache();
- this.HideRulerChanged(this, null);
+ this.StatusUpdate(this, null);
}
}
+ TextPoint _CaretPostion;
/// <summary>
/// レイアウト行のどこにキャレットがあるかを表す
/// </summary>
- /// <remarks>この値を変更しても反映されないので、EditView側でAdjustCaret()メソッドを呼び出す必要があります</remarks>
public TextPoint CaretPostion
{
- get;
- set;
+ get
+ {
+ return this._CaretPostion;
+ }
+ set
+ {
+ if(this._CaretPostion != value)
+ {
+ this._CaretPostion = value;
+ this.CaretChanged(this, null);
+ }
+ }
+ }
+
+ internal void SetCaretPostionWithoutEvent(TextPoint value)
+ {
+ if (this._CaretPostion != value)
+ this._CaretPostion = value;
}
/// <summary>
}
/// <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);
}
}
}
}
- /// <summary>
- /// レイアウト行を返す
- /// </summary>
- /// <param name="index">開始インデックス</param>
- /// <param name="length">長さ</param>
- /// <param name="lineLimitLength">1行当たりの最大文字数。-1で無制限</param>
- /// <returns>レイアウト行リスト</returns>
- internal IList<LineToIndexTableData> CreateLineList(int index, int length, int lineLimitLength = -1)
- {
- int startIndex = index;
- int endIndex = index + length - 1;
- List<LineToIndexTableData> output = new List<LineToIndexTableData>();
-
- foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, lineLimitLength))
- {
- int lineHeadIndex = range.Item1;
- int lineLength = range.Item2;
- char c = this.buffer[lineHeadIndex + lineLength - 1];
- bool hasNewLine = c == Document.NewLine;
- output.Add(this.LayoutLines.CreateLineToIndexTableData(lineHeadIndex, lineLength, hasNewLine, null));
- }
-
- if (output.Count > 0)
- output.Last().LineEnd = true;
-
- return output;
- }
-
internal void FireUpdate(DocumentUpdateEventArgs e)
{
this.buffer_Update(this.buffer, e);
public event DocumentUpdateEventHandler Update;
/// <summary>
- /// ドキュメントが更新された時に呼びされるイベント
- /// </summary>
- /// <remarks>
- /// FireUpdateEventの値に関わらず常に呼びされます
- /// </remarks>
- internal event DocumentUpdateEventHandler UpdateCalledAlways;
-
- /// <summary>
/// FireUpdateEventの値が変わったときに呼び出されるイベント
/// </summary>
public event EventHandler ChangeFireUpdateEvent;
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 bool IsRequestRedraw { get; internal set; }
+
+ /// <summary>
+ /// 再描写を要求する
+ /// </summary>
+ public void RequestRedraw()
{
- return new DocumentReader(this.buffer);
+ this.IsRequestRedraw = true;
+ }
+
+ /// <summary>
+ /// レイアウト行が再構成されたときに発生するイベント
+ /// </summary>
+ public event EventHandler PerformLayouted;
+ /// <summary>
+ /// レイアウト行をすべて破棄し、再度レイアウトを行う
+ /// </summary>
+ /// <param name="quick">真の場合、レイアウトキャッシュのみ再構築します</param>
+ public void PerformLayout(bool quick = true)
+ {
+ if (quick)
+ {
+ this.LayoutLines.ClearLayoutCache();
+ }
+ else
+ {
+ this.LayoutLines.IsFrozneDirtyFlag = true;
+ this.FireUpdate(new DocumentUpdateEventArgs(UpdateType.RebuildLayout, -1, -1, -1));
+ this.LayoutLines.IsFrozneDirtyFlag = false;
+ }
+ if (this.PerformLayouted != null)
+ this.PerformLayouted(this, null);
}
/// <summary>
- /// ã\83ã\83\83ã\82¯ã\82\92解é\99¤ã\81\97ã\81¾ã\81\99
+ /// ã\82ªã\83¼ã\83\89ã\82¤ã\83³ã\83\87ã\83³ã\83\88ã\81\8cå\8f¯è\83½ã\81«ã\81ªã\81£ã\81\9fæ\99\82ã\81«é\80\9aç\9f¥ã\81\95ã\82\8cã\82\8b
/// </summary>
- public void UnLock()
+ /// <remarks>
+ /// FireUpdateEventの影響を受けます
+ /// </remarks>
+ public event AutoIndentHookerHandler AutoIndentHook;
+
+ /// <summary>
+ /// 選択領域変更時に通知される
+ /// </summary>
+ public event EventHandler SelectionChanged;
+
+ /// <summary>
+ /// キャレット移動時に通知される
+ /// </summary>
+ public event EventHandler CaretChanged;
+
+ /// <summary>
+ /// 指定された範囲を選択する
+ /// </summary>
+ /// <param name="start"></param>
+ /// <param name="length"></param>
+ /// <remarks>RectSelectionの値によって動作が変わります。真の場合は矩形選択モードに、そうでない場合は行ごとに選択されます</remarks>
+ public void Select(int start, int length)
{
- this.Semaphore.Release();
+ if (this.FireUpdateEvent == false)
+ throw new InvalidOperationException("");
+ if (start < 0 || start + length < 0 || start + length > this.Length)
+ throw new ArgumentOutOfRangeException("startかendが指定できる範囲を超えてます");
+ //選択範囲が消されたとき
+ foreach (Selection sel in this.Selections)
+ this.LayoutLines.ClearLayoutCache(sel.start, sel.length);
+ 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));
+ this.LayoutLines.ClearLayoutCache(start, length);
+ }
+ else if (length != 0)
+ {
+ this.Selections.Add(Selection.Create(start, length));
+ this.LayoutLines.ClearLayoutCache(start, length);
+ }
+ this.SelectionChanged(this, null);
}
/// <summary>
- /// ロックします
+ /// 矩形選択を行う
/// </summary>
- public void Lock()
+ /// <param name="tp">開始位置</param>
+ /// <param name="width">桁数</param>
+ /// <param name="height">行数</param>
+ public void Select(TextPoint tp, int width, int height)
{
- this.Semaphore.Wait();
+ 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>
- /// <returns>Taskオブジェクト</returns>
- public Task LockAsync()
+ /// <param name="index">探索を開始するインデックス</param>
+ /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
+ public void SelectWord(int index, bool changeAnchor = false)
+ {
+ this.SelectSepartor(index, (c) => Util.IsWordSeparator(c), changeAnchor);
+ }
+
+ /// <summary>
+ /// 行単位で選択する
+ /// </summary>
+ /// <param name="index">探索を開始するインデックス</param>
+ /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
+ public void SelectLine(int index,bool changeAnchor = false)
{
- return this.Semaphore.WaitAsync();
+ this.SelectSepartor(index, (c) => c == Document.NewLine, changeAnchor);
+ }
+
+ /// <summary>
+ /// セパレーターで区切られた領域を取得する
+ /// </summary>
+ /// <param name="index">探索を開始するインデックス</param>
+ /// <param name="find_sep_func">セパレーターなら真を返し、そうでないなら偽を返す</param>
+ /// <returns>開始インデックス、終了インデックス</returns>
+ public Tuple<int,int> GetSepartor(int index, Func<char, bool> find_sep_func)
+ {
+ if (find_sep_func == null)
+ throw new ArgumentNullException("find_sep_func must not be null");
+
+ if (this.Length <= 0 || index >= this.Length)
+ return null;
+
+ Document str = this;
+
+ int start = index;
+ while (start > 0 && !find_sep_func(str[start]))
+ start--;
+
+ if (find_sep_func(str[start]))
+ {
+ start++;
+ }
+
+ int end = index;
+ while (end < this.Length && !find_sep_func(str[end]))
+ end++;
+
+ return new Tuple<int, int>(start, end);
+ }
+
+ /// <summary>
+ /// セパレーターで囲まれた範囲内を選択する
+ /// </summary>
+ /// <param name="index">探索を開始するインデックス</param>
+ /// <param name="find_sep_func">セパレーターなら真を返し、そうでないなら偽を返す</param>
+ /// <param name="changeAnchor">選択の起点となるとインデックスを変更するなら真。そうでなければ偽</param>
+ public void SelectSepartor(int index,Func<char,bool> find_sep_func, bool changeAnchor = false)
+ {
+ if (this.FireUpdateEvent == false)
+ throw new InvalidOperationException("");
+
+ if (find_sep_func == null)
+ throw new ArgumentNullException("find_sep_func must not be null");
+
+ var t = this.GetSepartor(index, find_sep_func);
+ if (t == null)
+ return;
+
+ int start = t.Item1, end = t.Item2;
+
+ this.Select(start, end - start);
+
+ if (changeAnchor)
+ this.AnchorIndex = start;
+ }
+
+ /// <summary>
+ /// DocumentReaderを作成します
+ /// </summary>
+ /// <returns>DocumentReaderオブジェクト</returns>
+ public DocumentReader CreateReader()
+ {
+ 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>
/// <returns>行イテレーターが返される</returns>
public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)
{
- return this.buffer.GetLines(startIndex, endIndex, maxCharCount);
- }
-
- internal IEnumerable<Tuple<int, int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)
- {
- return this.buffer.ForEachLines(startIndex, endIndex, maxCharCount);
+ foreach (Tuple<int, int> range in this.LayoutLines.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(this.buffer[i]);
+ yield return temp.ToString();
+ }
}
-
/// <summary>
/// 文字列を追加する
/// </summary>
/// <param name="index">開始インデックス</param>
/// <param name="length">長さ</param>
/// <param name="s">文字列</param>
+ /// <param name="UserInput">ユーザーからの入力として扱うなら真</param>
/// <remarks>読み出し操作中はこのメソッドを実行することはできません</remarks>
- public void Replace(int index, int length, string s)
+ public void Replace(int index, int length, string s, bool UserInput = false)
{
if (index < 0 || index > this.buffer.Length || index + length > this.buffer.Length || length < 0)
throw new ArgumentOutOfRangeException();
ReplaceCommand cmd = new ReplaceCommand(this.buffer, index, length, s);
this.UndoManager.push(cmd);
cmd.redo();
+
+ if (this.FireUpdateEvent && UserInput)
+ {
+ var input_str = string.Empty;
+ if (s == Document.NewLine.ToString())
+ input_str = s;
+ else if (s == string.Empty && length > 0)
+ input_str = "\b";
+ //入力は終わっているので空文字を渡すが処理の都合で一部文字だけはそのまま渡す
+ if (this.AutoComplete != null)
+ this.AutoComplete.ParseInput(input_str);
+ if (s == Document.NewLine.ToString())
+ this.AutoIndentHook(this, null);
+ }
}
/// <summary>
public void Clear()
{
this.buffer.Clear();
+ System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
+ GC.Collect();
+ this.Dirty = false;
}
/// <summary>
/// </summary>
/// <param name="fs">IStreamReaderオブジェクト</param>
/// <param name="tokenSource">キャンセルトークン</param>
+ /// <param name="file_size">ファイルサイズ。-1を指定しても動作しますが、読み取りが遅くなります</param>
/// <returns>Taskオブジェクト</returns>
/// <remarks>
/// 読み取り操作は別スレッドで行われます。
/// また、非同期操作中はこのメソッドを実行することはできません。
/// </remarks>
- internal async Task LoadAsync(IStreamReader fs, CancellationTokenSource tokenSource = null)
+ public async Task LoadAsync(TextReader fs, CancellationTokenSource tokenSource = null, int file_size = -1)
{
- 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;
+ if (file_size > 0)
+ this.buffer.Allocate(file_size);
await this.buffer.LoadAsync(fs, tokenSource);
}
finally
{
- this.FireUpdateEvent = true;
- this.UnLock();
+ this.PerformLayout(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);
- StringBuilder line = new StringBuilder();
- for (int i = 0; i < this.Length; i++)
- {
- char c = this[i];
- line.Append(c);
- if (c == Document.NewLine || i == this.Length - 1)
- {
- string str = line.ToString();
- str = str.Replace(Document.NewLine.ToString(), fs.NewLine);
- await fs.WriteAsync(str).ConfigureAwait(false);
- line.Clear();
- if (tokenSource != null)
- tokenSource.Token.ThrowIfCancellationRequested();
-#if TEST_ASYNC
- System.Threading.Thread.Sleep(10);
-#endif
- }
- }
- }
- finally
- {
- this.UnLock();
- }
+ await this.buffer.SaveAsync(fs, tokenSource);
}
/// <summary>
{
switch (e.type)
{
+ case UpdateType.RebuildLayout:
+ this._LayoutLines.Clear();
+ this._LayoutLines.UpdateAsReplace(0, 0, this.Length);
+ break;
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);
+ }
+ this.Dirty = true;
break;
case UpdateType.Clear:
this._LayoutLines.Clear();
+ this.Dirty = true;
break;
}
- this.UpdateCalledAlways(this, e);
if(this.FireUpdateEvent)
this.Update(this, e);
}
- }
- public interface IStreamReader
- {
- /// <summary>
- /// ストリームが空かどうかを返す
- /// </summary>
- bool IsEnd();
+ #region IDisposable Support
+ private bool disposedValue = false; // 重複する呼び出しを検出するには
- /// <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);
- }
+ void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ this.buffer.Clear();
+ this.LayoutLines.Clear();
+ }
- public interface IStreamWriter
- {
- /// <summary>
- /// ストリームに書き込む。SaveAsyncを呼び出す場合は必ず実装してください
- /// </summary>
- Task WriteAsync(string str);
+ disposedValue = true;
+ }
+ }
/// <summary>
- /// 書き込む際に使用する改行コード
+ /// ドキュメントを破棄する
/// </summary>
- string NewLine
+ public void Dispose()
{
- get;
- set;
+ Dispose(true);
}
+ #endregion
}
/// <summary>
{
this.document = null;
}
+
}
}