// // CodeTextBuffer.cs // // Author: // Jean-Philippe Bruyère // // Copyright (c) 2017 jp // // 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 . using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Diagnostics; namespace Crow.Coding { /// /// Code buffer, lines are arranged in a List, new line chars are removed during string.split on '\n...', /// public class CodeBuffer { public object EditMutex = new object(); //those events are handled in SourceEditor to help keeping sync between textbuffer,parser and editor. //modified lines are marked for reparse #region Events public event EventHandler LineUpadateEvent; public event EventHandler LineRemoveEvent; public event EventHandler LineAdditionEvent; public event EventHandler FoldingEvent; public event EventHandler BufferCleared; public event EventHandler SelectionChanged; public event EventHandler PositionChanged; #endregion #region CTOR public CodeBuffer () { } #endregion string lineBreak = Interface.LineBreak; List lines = new List(); public int longestLineIdx = 0; public int longestLineCharCount = 0; /// /// real position in char arrays, tab = 1 char /// int _currentLine = 0; int _currentCol = 0; public int LineCount { get { return lines.Count;}} public int IndexOf (CodeLine cl) { return lines.IndexOf (cl); } public CodeLine this[int i] { get { return lines[i]; } set { if (lines [i] == value) return; lock (EditMutex) { lines [i] = value; LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); } } } public void RemoveAt(int i){ lock (EditMutex) { lines.RemoveAt (i); LineRemoveEvent.Raise (this, new CodeBufferEventArgs (i)); } } public void Insert(int i, string item){ lock (EditMutex) { lines.Insert (i, item); LineAdditionEvent.Raise (this, new CodeBufferEventArgs (i)); } } public void Add(CodeLine item){ lock (EditMutex) { lines.Add (item); LineAdditionEvent.Raise (this, new CodeBufferEventArgs (lines.Count - 1)); } } public void AddRange (string[] items){ int start = lines.Count; lock (EditMutex) { for (int i = 0; i < items.Length; i++) lines.Add (items [i]); LineAdditionEvent.Raise (this, new CodeBufferEventArgs (start, items.Length)); } } public void AddRange (CodeLine[] items){ int start = lines.Count; lock (EditMutex) { lines.AddRange (items); LineAdditionEvent.Raise (this, new CodeBufferEventArgs (start, items.Length)); } } public void Clear () { lock (EditMutex) { longestLineCharCount = 0; lines.Clear (); BufferCleared.Raise (this, null); } } public void UpdateLine(int i, string newContent){ lock (EditMutex) { this [i].Content = newContent; LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); } } public void AppenedLine(int i, string newContent){ lock (EditMutex) { this [i].Content += newContent; LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); } } public void ToogleFolding (int line) { if (!this [line].IsFoldable) return; lock (EditMutex) { this [line].IsFolded = !this [line].IsFolded; FoldingEvent.Raise (this, new CodeBufferEventArgs (line)); } } public void Load(string rawSource) { this.Clear(); if (string.IsNullOrEmpty (rawSource)) return; AddRange (Regex.Split (rawSource, "\r\n|\r|\n|\\\\n")); lineBreak = detectLineBreakKind (rawSource); } /// /// Finds the longest visual line as printed on screen with tabulation replaced with n spaces /// public void FindLongestVisualLine(){ longestLineCharCount = 0; for (int i = 0; i < this.LineCount; i++) { if (lines[i].PrintableLength > longestLineCharCount) { longestLineCharCount = lines[i].PrintableLength; longestLineIdx = i; } } Debug.WriteLine ("Longest line: {0}->{1}", longestLineIdx, longestLineCharCount); } /// line break could be '\r' or '\n' or '\r\n' static string detectLineBreakKind(string buffer){ string strLB = ""; if (string.IsNullOrEmpty(buffer)) return Interface.LineBreak; int i = 0; while ( i < buffer.Length) { if (buffer [i] == '\r') { strLB += '\r'; i++; } if (i < buffer.Length) { if (buffer [i] == '\r') return "\r"; if (buffer[i] == '\n') strLB += '\n'; } if (!string.IsNullOrEmpty (strLB)) return strLB; i++; } return Interface.LineBreak; } /// /// return all lines with linebreaks /// public string FullText{ get { if (lines.Count == 0) return ""; string tmp = ""; for (int i = 0; i < lines.Count -1; i++) tmp += lines [i].Content + this.lineBreak; tmp += lines [lines.Count - 1].Content; return tmp; } } /// /// unfolded and not in folds line count /// public int UnfoldedLines { get { int i = 0, vl = 0; while (i < LineCount) { if (this [i].IsFolded) i = GetEndNodeIndex (i); i++; vl++; } //Debug.WriteLine ("unfolded lines: " + vl); return vl; } } /// /// convert visual position to buffer position /// Point getBuffPos (Point visualPos) { int i = 0; int buffCol = 0; while (i < visualPos.X) { if (this [visualPos.Y] [buffCol] == '\t') i += Interface.TabSize; else i++; buffCol++; } return new Point (buffCol, visualPos.Y); } public int GetEndNodeIndex (int line) { return IndexOf (this [line].SyntacticNode.EndLine); } /// /// Gets visual position computed from actual buffer position /// // public Point TabulatedPosition { // get { return new Point (TabulatedColumn, _currentLine); } // } /// /// set buffer current position from visual position /// // public void SetBufferPos(Point tabulatedPosition) { // CurrentPosition = getBuffPos(tabulatedPosition); // } #region Editing and moving cursor Point selStartPos = -1; //selection start (row,column) Point selEndPos = -1; //selection end (row,column) public bool SelectionInProgress { get { return selStartPos >= 0; }} public void SetSelStartPos () { selStartPos = selEndPos = CurrentPosition; SelectionChanged.Raise (this, null); } public void SetSelEndPos () { selEndPos = CurrentPosition; SelectionChanged.Raise (this, null); } /// /// Set selection in buffer to -1, empty selection /// public void ResetSelection () { selStartPos = selEndPos = -1; SelectionChanged.Raise (this, null); } public string SelectedText { get { if (SelectionIsEmpty) return ""; Point selStart = SelectionStart; Point selEnd = SelectionEnd; if (selStart.Y == selEnd.Y) return this [selStart.Y].Content.Substring (selStart.X, selEnd.X - selStart.X); string tmp = ""; tmp = this [selStart.Y].Content.Substring (selStart.X); for (int l = selStart.Y + 1; l < selEnd.Y; l++) { tmp += Interface.LineBreak + this [l].Content; } tmp += Interface.LineBreak + this [selEnd.Y].Content.Substring (0, selEnd.X); return tmp; } } /// /// ordered selection start and end positions in char units /// public Point SelectionStart { get { return selEndPos < 0 || selStartPos.Y < selEndPos.Y ? selStartPos : selStartPos.Y > selEndPos.Y ? selEndPos : selStartPos.X < selEndPos.X ? selStartPos : selEndPos; } } public Point SelectionEnd { get { return selEndPos < 0 || selStartPos.Y > selEndPos.Y ? selStartPos : selStartPos.Y < selEndPos.Y ? selEndPos : selStartPos.X > selEndPos.X ? selStartPos : selEndPos; } } public bool SelectionIsEmpty { get { return selEndPos == selStartPos; } } /// /// Current column in buffer coordinate, tabulation = 1 char /// public int CurrentColumn{ get { return _currentCol; } set { if (value == _currentCol) return; if (value < 0) _currentCol = 0; else if (value > lines [_currentLine].Length) _currentCol = lines [_currentLine].Length; else _currentCol = value; PositionChanged.Raise (this, null); } } /// /// Current row in buffer coordinate, tabulation = 1 char /// public int CurrentLine{ get { return _currentLine; } set { if (value == _currentLine) return; if (value >= lines.Count) _currentLine = lines.Count-1; else if (value < 0) _currentLine = 0; else _currentLine = value; if (_currentCol > lines [_currentLine].Length) _currentCol = lines [_currentLine].Length; //Debug.WriteLine ("buff cur line: " + _currentLine); PositionChanged.Raise (this, null); } } public CodeLine CurrentCodeLine { get { return this [_currentLine]; } } /// /// Current position in buffer coordinate, tabulation = 1 char /// public Point CurrentPosition { get { return new Point(CurrentColumn, CurrentLine); } // set { // _currentCol = value.X; // _currentLine = value.Y; // } } /// /// get char at current position in buffer /// protected Char CurrentChar { get { return lines [CurrentLine] [CurrentColumn]; } } public void GotoWordStart(){ if (this[CurrentLine].Length == 0) return; CurrentColumn--; //skip white spaces while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) CurrentColumn--; while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) CurrentColumn--; if (!char.IsLetterOrDigit (this.CurrentChar)) CurrentColumn++; } public void GotoWordEnd(){ //skip white spaces if (CurrentColumn >= this [CurrentLine].Length - 1) return; while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < this [CurrentLine].Length-1) CurrentColumn++; while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < this [CurrentLine].Length-1) CurrentColumn++; if (char.IsLetterOrDigit (this.CurrentChar)) CurrentColumn++; } public void DeleteChar() { lock (EditMutex) { if (SelectionIsEmpty) { if (CurrentColumn == 0) { if (CurrentLine == 0) return; CurrentLine--; CurrentColumn = this [CurrentLine].Length; AppenedLine (CurrentLine, this [CurrentLine + 1].Content); RemoveAt (CurrentLine + 1); return; } CurrentColumn--; UpdateLine (CurrentLine, this [CurrentLine].Content.Remove (CurrentColumn, 1)); } else { int linesToRemove = SelectionEnd.Y - SelectionStart.Y + 1; int l = SelectionStart.Y; if (linesToRemove > 0) { UpdateLine (l, this [l].Content.Remove (SelectionStart.X, this [l].Length - SelectionStart.X) + this [SelectionEnd.Y].Content.Substring (SelectionEnd.X, this [SelectionEnd.Y].Length - SelectionEnd.X)); l++; for (int c = 0; c < linesToRemove - 1; c++) RemoveAt (l); CurrentLine = SelectionStart.Y; CurrentColumn = SelectionStart.X; } else UpdateLine (l, this [l].Content.Remove (SelectionStart.X, SelectionEnd.X - SelectionStart.X)); CurrentColumn = SelectionStart.X; ResetSelection (); } } } /// /// Insert new string at caret position, should be sure no line break is inside. /// /// String. public void Insert(string str) { if (!SelectionIsEmpty) this.DeleteChar (); string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray(); UpdateLine (CurrentLine, this [CurrentLine].Content.Insert (CurrentColumn, strLines[0])); CurrentColumn += strLines[0].Length; for (int i = 1; i < strLines.Length; i++) { InsertLineBreak (); UpdateLine (CurrentLine, this [CurrentLine].Content.Insert (CurrentColumn, strLines[i])); CurrentColumn += strLines[i].Length; } } /// /// Insert a line break. /// public void InsertLineBreak() { if (CurrentColumn > 0) { Insert (CurrentLine + 1, this [CurrentLine].Content.Substring (CurrentColumn)); UpdateLine (CurrentLine, this [CurrentLine].Content.Substring (0, CurrentColumn)); } else Insert(CurrentLine, ""); CurrentColumn = 0; CurrentLine++; } #endregion } }