From 09abcce174f8dc4e612f5cd98b07dffced5b38cd Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bruyère Date: Mon, 6 May 2019 00:37:48 +0200 Subject: wip --- .../CrowIDE/src/Editors/CodeBuffer/CodeBuffer.cs | 530 ++++++++++++++++ .../src/Editors/CodeBuffer/CodeBufferEventArgs.cs | 20 + Samples/CrowIDE/src/Editors/CodeBuffer/CodeLine.cs | 82 +++ Samples/CrowIDE/src/Editors/CodeBuffer/Node.cs | 36 ++ .../CrowIDE/src/Editors/CodeBuffer/TextBuffer.cs | 527 ++++++++++++++++ .../src/Editors/CodeBuffer/TextBufferEventArgs.cs | 20 + .../CrowIDE/src/Editors/CodeBuffer/TextEditor.cs | 701 +++++++++++++++++++++ .../src/Editors/CodeBuffer/TextFormatting.cs | 19 + Samples/CrowIDE/src/Editors/CodeBuffer/Token.cs | 71 +++ 9 files changed, 2006 insertions(+) create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/CodeBuffer.cs create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/CodeBufferEventArgs.cs create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/CodeLine.cs create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/Node.cs create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/TextBuffer.cs create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/TextBufferEventArgs.cs create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/TextEditor.cs create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/TextFormatting.cs create mode 100644 Samples/CrowIDE/src/Editors/CodeBuffer/Token.cs (limited to 'Samples/CrowIDE/src/Editors/CodeBuffer') diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBuffer.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBuffer.cs new file mode 100644 index 00000000..7d5efec9 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBuffer.cs @@ -0,0 +1,530 @@ +// +// 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; +using System.Threading; + +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 ReaderWriterLockSlim editMutex = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + + //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 + + 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 i < LineCount ? lines[i] : null; } + set { + if (lines [i] == value) + return; + editMutex.EnterWriteLock (); + lines [i] = value; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); + } + } + + public void RemoveAt(int i){ + editMutex.EnterWriteLock (); + lines.RemoveAt (i); + editMutex.ExitWriteLock (); + LineRemoveEvent.Raise (this, new CodeBufferEventArgs (i)); + } + public void Insert(int i, string item){ + editMutex.EnterWriteLock (); + lines.Insert (i, item); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new CodeBufferEventArgs (i)); + } + public void Add(CodeLine item){ + editMutex.EnterWriteLock (); + lines.Add (item); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new CodeBufferEventArgs (lines.Count - 1)); + } + public void AddRange (string[] items){ + int start = lines.Count; + editMutex.EnterWriteLock (); + for (int i = 0; i < items.Length; i++) + lines.Add (items [i]); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new CodeBufferEventArgs (start, items.Length)); + } + public void AddRange (CodeLine[] items){ + int start = lines.Count; + editMutex.EnterWriteLock (); + lines.AddRange (items); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new CodeBufferEventArgs (start, items.Length)); + } + public void Clear () { + editMutex.EnterWriteLock (); + longestLineCharCount = 0; + lines.Clear (); + editMutex.ExitWriteLock (); + BufferCleared.Raise (this, null); + } + public void UpdateLine(int i, string newContent){ + editMutex.EnterWriteLock (); + this [i].Content = newContent; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); + } + public void AppenedLine(int i, string newContent){ + editMutex.EnterWriteLock (); + this [i].Content += newContent; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); + } + public void RemoveLeadingTab (int l) { + if (this [l] [0] == '\t') { + UpdateLine (l, this [l].Content.Substring (1)); + return; + } + int i = 0; + while (i < Interface.TabSize) { + if (this [l] [i] != ' ') + break; + i++; + } + if (i > 0) + UpdateLine (l, this [l].Content.Substring (i)); + } + public void ToogleFolding (int line) { + if (!this [line].IsFoldable) + return; + editMutex.EnterWriteLock (); + this [line].IsFolded = !this [line].IsFolded; + editMutex.ExitWriteLock (); + FoldingEvent.Raise (this, new CodeBufferEventArgs (line)); + } + public void Load(string rawSource, string lineBrkRegex = @"\r\n|\r|\n|\\\n") { + this.Clear(); + + if (string.IsNullOrEmpty (rawSource)) + return; + + AddRange (Regex.Split (rawSource, lineBrkRegex)); + + lineBreak = detectLineBreakKind (rawSource); + } + + /// + /// Finds the longest visual line as printed on screen with tabulation replaced with n spaces + /// + public void FindLongestVisualLine(){ + longestLineCharCount = 0; + editMutex.EnterReadLock (); + for (int i = 0; i < this.LineCount; i++) { + if (lines[i].PrintableLength > longestLineCharCount) { + longestLineCharCount = lines[i].PrintableLength; + longestLineIdx = i; + } + } + editMutex.ExitReadLock (); + //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 = ""; + editMutex.EnterReadLock (); + for (int i = 0; i < lines.Count -1; i++) + tmp += lines [i].Content + this.lineBreak; + tmp += lines [lines.Count - 1].Content; + editMutex.ExitReadLock (); + return tmp; + } + } + + /// + /// unfolded and not in folds line count + /// + public int UnfoldedLines { + get { + int i = 0, vl = 0; + editMutex.EnterReadLock (); + while (i < LineCount) { + if (this [i].IsFolded) { + i = GetEndNodeIndex (i); + if (i < 0) { + Console.WriteLine ("error folding"); + break; + } + } + i++; + vl++; + } + editMutex.ExitReadLock (); + //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); + } + + int ConverteTabulatedPosOfCurLine (int column) { + int tmp = 0; + int i = 0; + while (i < lines [_currentLine].Content.Length){ + if (lines [_currentLine].Content [i] == '\t') + tmp += 4; + else + tmp++; + if (tmp > column) + break; + i++; + } + return i; + } + + public int CurrentTabulatedColumn { + get { + return lines [_currentLine].Content.Substring (0, _currentCol). + Replace ("\t", new String (' ', Interface.TabSize)).Length; + } + } + /// + /// 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); + } + public void SetSelectionOnFullLines () { + if (!SelectionInProgress) + return; + Point s = new Point (0, SelectionStart.Y); + Point e = new Point (this [SelectionEnd.Y].Length, SelectionEnd.Y); + selStartPos = s; + selEndPos = e; + 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; } } + int requestedColumn = -1; + /// + /// Current column in buffer coordinate, tabulation = 1 char + /// + public int CurrentColumn{ + get { return _currentCol; } + set { + if (value == _currentCol) + return; + + editMutex.EnterReadLock (); + + if (value < 0) + _currentCol = 0; + else if (value > lines [_currentLine].Length) + _currentCol = lines [_currentLine].Length; + else + _currentCol = value; + + requestedColumn = CurrentTabulatedColumn; + + editMutex.ExitReadLock (); + + PositionChanged.Raise (this, null); + } + } + /// + /// Current row in buffer coordinate, tabulation = 1 char + /// + public int CurrentLine{ + get { return _currentLine; } + set { + if (value == _currentLine) + return; + + editMutex.EnterReadLock (); + + if (LineCount == 0) + _currentLine = 0; + else if (value >= lines.Count) + _currentLine = lines.Count-1; + else if (value < 0) + _currentLine = 0; + else + _currentLine = value; +// if (_currentCol < 0) +// requestedColumn = tabu _currentCol; + int tabulatedRequestedCol = ConverteTabulatedPosOfCurLine(requestedColumn); + if (requestedColumn > lines [_currentLine].PrintableLength) + _currentCol = lines [_currentLine].Length; + else + //_currentCol = requestedColumn; + _currentCol = tabulatedRequestedCol; + //Debug.WriteLine ("buff cur line: " + _currentLine); + + editMutex.ExitReadLock(); + + 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() + { + editMutex.EnterWriteLock (); + if (SelectionIsEmpty) { + if (CurrentColumn == 0) { + if (CurrentLine == 0) { + editMutex.ExitWriteLock (); + return; + } + CurrentLine--; + CurrentColumn = this [CurrentLine].Length; + AppenedLine (CurrentLine, this [CurrentLine + 1].Content); + RemoveAt (CurrentLine + 1); + editMutex.ExitWriteLock (); + 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 (); + } + editMutex.ExitWriteLock (); + } + /// + /// 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 + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBufferEventArgs.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBufferEventArgs.cs new file mode 100644 index 00000000..07dd25b6 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeBufferEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Crow.Coding +{ + public class CodeBufferEventArgs : EventArgs { + public int LineStart; + public int LineCount; + + public CodeBufferEventArgs(int lineNumber) { + LineStart = lineNumber; + LineCount = 1; + } + public CodeBufferEventArgs(int lineStart, int lineCount) { + LineStart = lineStart; + LineCount = lineCount; + } + } + +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/CodeLine.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeLine.cs new file mode 100644 index 00000000..23f43fba --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/CodeLine.cs @@ -0,0 +1,82 @@ +using System; +using System.Text; +using System.Collections.Generic; +using System.Linq; + +namespace Crow.Coding +{ + public class CodeLine + { + public string Content; + public List Tokens; + public int EndingState = 0; + public Node SyntacticNode; + public ParserException exception; + + public CodeLine (string _content){ + Content = _content; + Tokens = null; + exception = null; + } + + public char this[int i] + { + get { return Content[i]; } + set { + if (Content [i] == value) + return; + StringBuilder sb = new StringBuilder(Content); + sb[i] = value; + Content = sb.ToString(); + Tokens = null; + //LineUpadateEvent.Raise (this, new CodeBufferEventArgs (i)); + } + } + public bool IsFoldable { get { return SyntacticNode == null ? false : + SyntacticNode.EndLine != SyntacticNode.StartLine && SyntacticNode.EndLine != null; } } + public int FoldingLevel { get { return IsFoldable ? SyntacticNode.Level : 0; } } + public bool IsFolded = false; + public bool IsParsed { + get { return Tokens != null; } + } + public string PrintableContent { + get { + return string.IsNullOrEmpty (Content) ? "" : Content.Replace ("\t", new String (' ', Interface.TabSize)); + } + } + public int PrintableLength { + get { + return PrintableContent.Length; + } + } + public int Length { + get { + return string.IsNullOrEmpty (Content) ? 0 : Content.Length; + } + } + public int FirstNonBlankTokIndex { + get { return Tokens == null ? -1 : Tokens.FindIndex (tk=>tk.Type != BufferParser.TokenType.WhiteSpace); } + } + + public void SetLineInError (ParserException ex) { + Tokens = null; + exception = ex; + } + +// public static implicit operator string(CodeLine sl) { +// return sl == null ? "" : sl.Content; +// } + public static implicit operator CodeLine(string s) { + return new CodeLine(s); + } + public static bool operator ==(string s1, CodeLine s2) + { + return string.Equals (s1, s2.Content); + } + public static bool operator !=(string s1, CodeLine s2) + { + return !string.Equals (s1, s2.Content); + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/Node.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/Node.cs new file mode 100644 index 00000000..9db55428 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/Node.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Crow.Coding +{ + public class Node + { + public Node Parent; + public string Name; + public string Type; + public CodeLine StartLine; + public CodeLine EndLine; + public Dictionary Attributes = new Dictionary (); + + public List Children = new List(); + + public Node () + { + } + + public void AddChild (Node child) { + child.Parent = this; + Children.Add (child); + } + + public int Level { + get { return Parent == null ? 1 : Parent.Level + 1; } + } + + public override string ToString () + { + return string.Format ("Name:{0}, Type:{1}\n\tparent:{2}", Name, Type, Parent); + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/TextBuffer.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/TextBuffer.cs new file mode 100644 index 00000000..e8c87182 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/TextBuffer.cs @@ -0,0 +1,527 @@ +// +// 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; +using System.Threading; +using System.Text; + +namespace Crow.Text +{ + /// + /// Code buffer, lines are arranged in a List, new line chars are removed during string.split on '\n...', + /// + public class TextBuffer + { + public ReaderWriterLockSlim editMutex = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + static Regex slb = new Regex ("\\n");//single char line break used internaly + Regex reghexLineBrk = new Regex(@"\r\n|\r|\n|\\\n");//original text line break regex + + #region Events + public event EventHandler LineUpadateEvent; + public event EventHandler LineRemoveEvent; + public event EventHandler LineAdditionEvent; + public event EventHandler BufferCleared; + public event EventHandler SelectionChanged; + public event EventHandler PositionChanged; + #endregion + + StringBuilder buffer = new StringBuilder(); + string lineBreak = Interface.LineBreak;//detected linebreak kind in original source + List lineLength = new List();//line length table + /// + /// real position in char arrays, tab = 1 char + /// + int _currentLine = 0; + int _currentCol = 0; + + /// + /// Gets the total line count. + /// + public int LineCount { get { return lineLength.Count;}} + + /// + /// get a substring in the buffer + /// + /// a new string + /// absolute index in the buffer + /// length of the substring + public string GetSubString (int idx, int length){ + return buffer.ToString (idx, length); + } + /// + /// Gets length of a line. + /// + /// length of line + /// line nuber + public int GetLineLength (int lineIdx) { + return lineLength [lineIdx]; + } + /// + /// get a single charactere in the buffer + /// + /// a single char + /// absolute index in the buffer + public char GetCharAt (int idx){ + return buffer [idx]; + } + /// + /// return full text with original line breaks + /// + /// a string containing the full text + public string FullText { + get { return buffer.Replace("\n", lineBreak).ToString (); } + set { + Load (value); + } + } + /// + /// Gets the buffer pointer of a line + /// + /// absolute index of the line in the buffer + /// line number + public int GetBufferIndexOfLine (int i) { + int ptr = 0; + editMutex.EnterReadLock (); + for (int j = 0; j < i; j++) { + ptr += lineLength [j]; + } + editMutex.ExitReadLock (); + return ptr; + } + /// + /// Gets the buffer pointer for the current position + /// + /// absolute index in the buffer of current position + public int BufferIndexOfCurrentPosition { + get {return GetBufferIndexOfLine (_currentLine) + _currentCol;} + } + public int this[int i] + { + get { + int ptr = 0; + editMutex.EnterReadLock (); + for (int j = 0; j < i; j++) { + ptr += lineLength [j]; + } + editMutex.ExitReadLock (); + return ptr; + } + } + /// + /// remove line number i + /// + /// index of the line + public void RemoveLine(int i){ + editMutex.EnterWriteLock (); + buffer.Remove (GetBufferIndexOfLine (i), lineLength [i]); + lineLength.RemoveAt (i); + editMutex.ExitWriteLock (); + LineRemoveEvent.Raise (this, new TextBufferEventArgs (i)); + } + /// + /// insert string without linebreaks at position i in buff + /// + /// absolute index in the buffer + /// linebreak free string + public void InsertAt(int i, string str){ + editMutex.EnterWriteLock (); + buffer.Insert (this [i], str); + lineLength.Insert (i, str.Length); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new TextBufferEventArgs (i)); + } + public void AddLine(string str){ + editMutex.EnterWriteLock (); + if (lineLength.LastOrDefault() == 0) { + buffer.Append (str); + lineLength [lineLength.Count - 1] = str.Length; + lineLength.Add (0); + } + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new TextBufferEventArgs (lineLength.Count - 1)); + } + public void AddRange (string[] items){ + int start = lineLength.Count; + editMutex.EnterWriteLock (); + for (int i = 0; i < items.Length; i++) + AddLine (items [i]); + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, new TextBufferEventArgs (start, items.Length)); + } + public void Clear () { + editMutex.EnterWriteLock (); + lineLength.Clear (); + buffer.Clear (); + editMutex.ExitWriteLock (); + BufferCleared.Raise (this, null); + } + public void UpdateLine(int i, string newContent){ + editMutex.EnterWriteLock (); + int ptrL = this [i]; + buffer.Remove (ptrL, lineLength [i]); + buffer.Insert (ptrL, newContent); + lineLength [i] = newContent.Length; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new TextBufferEventArgs (i)); + } + public void AppenedLine(int i, string newContent){ + editMutex.EnterWriteLock (); + int ptr = this [i] + lineLength [i]; + if (i < LineCount - 1) + ptr--; + buffer.Insert(ptr, newContent); + lineLength [i] += newContent.Length; + editMutex.ExitWriteLock (); + LineUpadateEvent.Raise (this, new TextBufferEventArgs (i)); + } + /// + /// Insert new string at caret position, should be sure no line break is inside. + /// + /// String. + public void InsertAt(string str) + { + if (!SelectionIsEmpty) + this.Delete (); + + editMutex.EnterWriteLock (); + + string tmp = reghexLineBrk.Replace (str, "\n");//use single char line break in buffer + int buffPtr = this [CurrentLine] + CurrentColumn; + buffer.Insert (buffPtr, tmp); + + int lPtr = CurrentLine, strPtr = 0; + int remainingLength = lineLength [lPtr] - CurrentColumn; + lineLength [lPtr] = CurrentColumn; + foreach (Match match in slb.Matches(tmp)) + { + lineLength [lPtr] += match.Index + 1 - strPtr; + lPtr++; + lineLength.Insert (lPtr, 0); + strPtr = match.Index + 1; + //CurrentLine++; + } + remainingLength += tmp.Length - strPtr; + lineLength [lPtr] += remainingLength; + + CurrentLine = lPtr; + if (strPtr == 0) + CurrentColumn += tmp.Length; + else + CurrentColumn = tmp.Length - strPtr; + + editMutex.ExitWriteLock (); + if (strPtr>0) + LineAdditionEvent.Raise (this, null); + else + LineUpadateEvent.Raise (this, null); + } + /// + /// Insert a line break. + /// + public void InsertLineBreak() + { + editMutex.EnterWriteLock (); + buffer.Insert (this [CurrentLine] + CurrentColumn, '\n'); + int lgdiff = lineLength [CurrentLine] - CurrentColumn; + lineLength.Insert (CurrentLine + 1, lineLength [CurrentLine] - CurrentColumn); + lineLength [CurrentLine] = CurrentColumn + 1; + editMutex.ExitWriteLock (); + LineAdditionEvent.Raise (this, null); + CurrentColumn = 0; + CurrentLine++; + } + public void Delete() + { + editMutex.EnterWriteLock (); + if (SelectionIsEmpty) { + if (CurrentColumn == 0) { + if (CurrentLine == 0) { + editMutex.ExitWriteLock (); + return; + } + + buffer.Remove (this [CurrentLine] - 1, 1); + + int col = lineLength [CurrentLine - 1] - 1; + lineLength [CurrentLine - 1] += lineLength [CurrentLine] - 1; + lineLength.RemoveAt (CurrentLine); + + CurrentLine--; + CurrentColumn = col; + + editMutex.ExitWriteLock (); + LineRemoveEvent.Raise (this, null); + return; + } + CurrentColumn--; + buffer.Remove (this [CurrentLine] + CurrentColumn, 1); + lineLength [CurrentLine]--; + } else { + int linesToRemove = SelectionEnd.Y - SelectionStart.Y; + int ptr = this [SelectionStart.Y] + SelectionStart.X; + int length = lineLength [SelectionStart.Y] - SelectionStart.X; + int l = 1; + while (l <= linesToRemove ) { + length += lineLength [SelectionStart.Y + l]; + l++; + } + length -= lineLength [SelectionEnd.Y] - SelectionEnd.X; + buffer.Remove (ptr, length); + lineLength [SelectionStart.Y] = SelectionStart.X + lineLength [SelectionEnd.Y] - SelectionEnd.X; + lineLength.RemoveRange (SelectionStart.Y + 1, linesToRemove); + + CurrentLine = SelectionStart.Y; + CurrentColumn = SelectionStart.X; + ResetSelection (); + + if (linesToRemove > 0) + LineRemoveEvent.Raise (this, null); + else + LineUpadateEvent.Raise (this, null); + } + editMutex.ExitWriteLock (); + } + public void Load(string rawSource) { + this.Clear(); + + if (string.IsNullOrEmpty (rawSource)) + return; + + lineBreak = reghexLineBrk.Match (rawSource).Value;//store original line break + string tmp = reghexLineBrk.Replace (rawSource, "\n");//use single char line break in buffer + int ptr = 0; + foreach (Match match in slb.Matches(tmp)) + { + int l = match.Index + 1; + int lg = l - ptr; + lineLength.Add (lg); + ptr = l; + } + lineLength.Add (0); + + buffer = new StringBuilder (tmp); + } + +// public int CurrentTabulatedColumn { +// get { +//// return lines [_currentLine].Content.Substring (0, _currentCol). +//// Replace ("\t", new String (' ', Interface.TabSize)).Length; +// } +// } + + #region moving cursor an selection + 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); + } + /// + /// 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; + + editMutex.EnterWriteLock (); + + if (value < 0) + _currentCol = 0; + else if (value >= lineLength [_currentLine]) { + if (_currentLine < LineCount -1 && value > lineLength [_currentLine] - 1) + _currentCol = lineLength [_currentLine] - 1; + else + _currentCol = lineLength [_currentLine]; + }else + _currentCol = value; + + editMutex.ExitWriteLock (); + + PositionChanged.Raise (this, null); + } + } + /// + /// Current row in buffer coordinate, tabulation = 1 char + /// + public int CurrentLine{ + get { return _currentLine; } + set { + if (value == _currentLine) + return; + + editMutex.EnterWriteLock (); + + if (value >= lineLength.Count) + _currentLine = lineLength.Count-1; + else if (value < 0) + _currentLine = 0; + else + _currentLine = value; + + int c = _currentCol; + _currentCol = 0; + CurrentColumn = c; + + editMutex.ExitWriteLock(); + + PositionChanged.Raise (this, null); + } + } + /// + /// Current position in buffer coordinate, tabulation = 1 char + /// + public Point CurrentPosition { + get { return new Point(CurrentColumn, CurrentLine); } + } + /// + /// get char at current position in buffer + /// + protected Char CurrentChar { get { return buffer[this [CurrentLine]]; } } + public string SelectedText { + get { + if (SelectionIsEmpty) + return ""; + Point selStart = SelectionStart; + Point selEnd = SelectionEnd; + + int ptr = this [selStart.Y] + selStart.X; + int length = lineLength[selStart.Y] - selStart.X; + for (int i = selStart.Y+1; i <= selEnd.Y; i++) + length += lineLength [i]; + length -= lineLength[selEnd.Y] - selEnd.X; + + return buffer.ToString (ptr, length); + } + } + + public void GotoWordStart(){ + if (_currentCol == 0) + MoveLeft (); + if (_currentCol == 0) + return; + int ptrStart = BufferIndexOfCurrentPosition; + int ptr = ptrStart; + char c; + //skip white spaces + do { + ptr--; + c = this.GetCharAt (ptr); + } while (!char.IsLetterOrDigit (c) && c != '\n' && ptr > 1); + + do { + ptr--; + c = this.GetCharAt (ptr); + } while (char.IsLetterOrDigit (c) && c != '\n' && ptr > 1); + + if (ptr == 0) + CurrentColumn = 0; + else + CurrentColumn -= ptrStart - ptr - 1; + } + public void GotoWordEnd(){ + int limx = GetLineLength (_currentLine); + if (_currentLine < lineLength.Count - 1) + limx--; + + if (_currentCol == limx) { + MoveRight (); + limx = GetLineLength (_currentLine); + if (_currentLine < lineLength.Count - 1) + limx--; + } + + int ptrLine = GetBufferIndexOfLine(_currentLine); + int ptrCol = _currentCol; + + char c; + //skip white spaces + do { + c = GetCharAt (ptrLine+ptrCol); + ptrCol++; + } while (!char.IsLetterOrDigit (c) && ptrCol < limx); + + do { + c = GetCharAt (ptrLine + ptrCol); + ptrCol++; + } while (char.IsLetterOrDigit (c) && ptrCol < limx); + CurrentColumn = ptrCol - 1; + } + /// + /// Moves cursor one char to the left, move up if cursor reaches start of line + /// + public void MoveLeft(){ + if (CurrentColumn == 0) { + CurrentLine--; + CurrentColumn = int.MaxValue; + } else + CurrentColumn--; + } + /// + /// Moves cursor one char to the right, move down if cursor reaches end of line + /// + public void MoveRight(){ + if (_currentLine < LineCount -1){ + if (CurrentColumn >= lineLength [CurrentLine] - 1) { + CurrentColumn = 0; + CurrentLine++; + return; + } + } + CurrentColumn++; + } + + #endregion + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/TextBufferEventArgs.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/TextBufferEventArgs.cs new file mode 100644 index 00000000..3b67bb1c --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/TextBufferEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Crow.Text +{ + public class TextBufferEventArgs : EventArgs { + public int LineStart; + public int LineCount; + + public TextBufferEventArgs(int lineNumber) { + LineStart = lineNumber; + LineCount = 1; + } + public TextBufferEventArgs(int lineStart, int lineCount) { + LineStart = lineStart; + LineCount = lineCount; + } + } + +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/TextEditor.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/TextEditor.cs new file mode 100644 index 00000000..d5c41ec4 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/TextEditor.cs @@ -0,0 +1,701 @@ +// +// ScrollingTextBox.cs +// +// Author: +// Jean-Philippe Bruyère +// +// Copyright (c) 2013-2017 Jean-Philippe Bruyère +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.ComponentModel; +using vkvg; + +namespace Crow.Text +{ + /// + /// Scrolling text box optimized for monospace fonts, for coding + /// + public class TextEditor : Crow.Coding.Editor + { + #region CTOR + public TextEditor (): base() + { + buffer = new TextBuffer (); + buffer.LineUpadateEvent += Buffer_LineUpadateEvent; + buffer.LineAdditionEvent += Buffer_LineAdditionEvent;; + buffer.LineRemoveEvent += Buffer_LineRemoveEvent; + buffer.BufferCleared += Buffer_BufferCleared; + buffer.SelectionChanged += Buffer_SelectionChanged; + buffer.PositionChanged += Buffer_PositionChanged; + //buffer.Add (""); + } + #endregion + + string oldSource = ""; + volatile bool isDirty = false; + + #region private and protected fields + int visibleLines = 1; + int visibleColumns = 1; + + TextBuffer buffer; + + Color selBackground; + Color selForeground; + int selStartCol; + int selEndCol; + + protected Rectangle rText; + protected FontExtents fe; + protected TextExtents te; + + Point mouseLocalPos; + bool doubleClicked = false; + #endregion + + /// + /// Updates visible line in widget, adapt max scroll y and updatePrintedLines + /// + void updateVisibleLines(){ + visibleLines = (int)Math.Floor ((double)ClientRectangle.Height / (fe.Ascent+fe.Descent)); + NotifyValueChanged ("VisibleLines", visibleLines); + updateMaxScrollY (); + RegisterForGraphicUpdate (); +// System.Diagnostics.Debug.WriteLine ("update visible lines: " + visibleLines); +// System.Diagnostics.Debug.WriteLine ("update MaxScrollY: " + MaxScrollY); + } + void updateVisibleColumns(){ + visibleColumns = (int)Math.Floor ((double)(ClientRectangle.Width)/ fe.MaxXAdvance); + NotifyValueChanged ("VisibleColumns", visibleColumns); + RegisterForGraphicUpdate (); +// System.Diagnostics.Debug.WriteLine ("update visible columns: {0} leftMargin:{1}",visibleColumns, leftMargin); +// System.Diagnostics.Debug.WriteLine ("update MaxScrollX: " + MaxScrollX); + } + void updateMaxScrollX (int longestTabulatedLineLength) { + MaxScrollX = Math.Max (0, longestTabulatedLineLength - visibleColumns); + if (longestTabulatedLineLength > 0) + NotifyValueChanged ("ChildWidthRatio", Slot.Width * visibleColumns / longestTabulatedLineLength); + } + void updateMaxScrollY () { + int lc = buffer.LineCount; + MaxScrollY = Math.Max (0, lc - visibleLines); + if (lc > 0) + NotifyValueChanged ("ChildHeightRatio", Slot.Height * visibleLines / lc); + + } + + + + #region Editor overrides + protected override void updateEditorFromProjFile () + { + buffer.editMutex.EnterWriteLock (); + loadSource (); + buffer.editMutex.ExitWriteLock (); + + isDirty = false; + oldSource = projFile.Source; + projFile.RegisteredEditors [this] = true; + } + protected override void updateProjFileFromEditor () + { + buffer.editMutex.EnterWriteLock (); + string newsrc = buffer.FullText; + buffer.editMutex.ExitWriteLock (); + projFile.UpdateSource (this, newsrc); + } + protected override bool EditorIsDirty { + get { return isDirty; } + set { isDirty = value; } + } + protected override bool IsReady { + get { return projFile != null && buffer != null; } + } + #endregion + + #region Buffer events handlers + void Buffer_BufferCleared (object sender, EventArgs e) + { + editorMutex.EnterWriteLock (); + + MaxScrollX = MaxScrollY = 0; + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + + editorMutex.ExitWriteLock (); + } + void Buffer_LineAdditionEvent (object sender, TextBufferEventArgs e) + { + updateMaxScrollY (); + RegisterForGraphicUpdate (); + isDirty = true; + } + void Buffer_LineRemoveEvent (object sender, TextBufferEventArgs e) + { + updateMaxScrollY (); + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + } + void Buffer_LineUpadateEvent (object sender, TextBufferEventArgs e) + { + RegisterForGraphicUpdate (); + notifyPositionChanged (); + isDirty = true; + } + void Buffer_PositionChanged (object sender, EventArgs e) + { + int cc = getTabulatedColumn (buffer.CurrentPosition); + + if (cc > visibleColumns + ScrollX) { + ScrollX = cc - visibleColumns; + } else if (cc < ScrollX) + ScrollX = cc; + + if (buffer.CurrentLine >= visibleLines + ScrollY - 1) + ScrollY = buffer.CurrentLine - visibleLines + 1; + else if (buffer.CurrentLine < ScrollY) + ScrollY = buffer.CurrentLine; + + RegisterForGraphicUpdate (); + notifyPositionChanged (); + } + + void Buffer_SelectionChanged (object sender, EventArgs e) + { + RegisterForGraphicUpdate (); + } + #endregion + + void notifyPositionChanged (){ + try { + NotifyValueChanged ("CurrentLine", buffer.CurrentLine+1); + NotifyValueChanged ("CurrentColumn", buffer.CurrentColumn+1); + } catch (Exception ex) { + Console.WriteLine (ex.ToString ()); + } + } + + #region Public Crow Properties + public int CurrentLine{ + get { return buffer == null ? 0 : buffer.CurrentLine+1; } + set { + try { + int l = value - 1; + if (l == buffer.CurrentLine) + return; + buffer.CurrentLine = l; + } catch (Exception ex) { + Console.WriteLine ("Error cur column: " + ex.ToString ()); + } + } + } + public int CurrentColumn{ + get { return buffer == null ? 0 : buffer.CurrentColumn+1; } + set { + try { + if (value - 1 == buffer.CurrentColumn) + return; + buffer.CurrentColumn = value - 1; + } catch (Exception ex) { + Console.WriteLine ("Error cur column: " + ex.ToString ()); + } + } + } + [DefaultValue("Blue")] + public virtual Color SelectionBackground { + get { return selBackground; } + set { + if (value == selBackground) + return; + selBackground = value; + NotifyValueChanged ("SelectionBackground", selBackground); + RegisterForRedraw (); + } + } + [DefaultValue("White")] + public virtual Color SelectionForeground { + get { return selForeground; } + set { + if (value == selForeground) + return; + selForeground = value; + NotifyValueChanged ("SelectionForeground", selForeground); + RegisterForRedraw (); + } + } + public override int ScrollY { + get { + return base.ScrollY; + } + set { + if (value == base.ScrollY) + return; + editorMutex.EnterWriteLock (); + base.ScrollY = value; + editorMutex.ExitWriteLock (); + RegisterForGraphicUpdate (); + } + } + #endregion + + + void loadSource () { + buffer.Load (projFile.Source); + projFile.RegisteredEditors [this] = true; + updateMaxScrollY (); + RegisterForGraphicUpdate (); + } + + int getTabulatedColumn (int col, int line) { + return buffer.GetSubString (buffer [line], + buffer.GetLineLength (line)).Substring(0,col).Replace ("\t", new String (' ', Interface.TabSize)).Length; + } + int getTabulatedColumn (Point pos) { + return getTabulatedColumn (pos.X,pos.Y); + } + + #region Drawing + void drawLines(Context gr, Rectangle cb) { + int longestTabulatedLine = 0; + for (int i = 0; i < visibleLines; i++) { + int lineIndex = i + ScrollY; + if (lineIndex >= buffer.LineCount)//TODO:need optimize + break; + + double y = cb.Y + (fe.Ascent+fe.Descent) * i, x = cb.X; + + int lineLength = buffer.GetLineLength (lineIndex); + if (lineIndex < buffer.LineCount - 1)//dont print line break + lineLength--; + string lstr = buffer.GetSubString (buffer [lineIndex], + lineLength).Replace ("\t", new String (' ', Interface.TabSize)); + + int lstrLength = lstr.Length; + if (lstrLength > longestTabulatedLine) + longestTabulatedLine = lstrLength; + + if (ScrollX < lstrLength) + lstr = lstr.Substring (ScrollX); + else + lstr = ""; + + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (lstr); + gr.Fill (); + + if (!buffer.SelectionIsEmpty && lineIndex >= buffer.SelectionStart.Y && lineIndex <= buffer.SelectionEnd.Y) { + double rLineX = x, + rLineY = y, + rLineW = lstr.Length * fe.MaxXAdvance; + + if (lineIndex == buffer.SelectionStart.Y) { + rLineX += (selStartCol - ScrollX) * fe.MaxXAdvance; + rLineW -= (selStartCol - ScrollX) * fe.MaxXAdvance; + } + if (lineIndex == buffer.SelectionEnd.Y) + rLineW -= (lstr.Length - selEndCol + ScrollX) * fe.MaxXAdvance; + + gr.Save (); + gr.Operator = Operator.Source; + gr.Rectangle (rLineX, rLineY, rLineW, (fe.Ascent+fe.Descent)); + gr.SetSourceColor (SelectionBackground); + gr.FillPreserve (); + gr.Clip (); + gr.Operator = Operator.Over; + gr.SetSourceColor (SelectionForeground); + gr.MoveTo (x, y + fe.Ascent); + gr.ShowText (lstr); + gr.Fill (); + gr.Restore (); + } + + + } + + updateMaxScrollX(longestTabulatedLine); + } + #endregion + + #region GraphicObject overrides + public override Font Font { + get { return base.Font; } + set { + base.Font = value; + + using (Surface img = new Surface (IFace.dev, 1, 1)) { + using (Context gr = new Context (img)) { + gr.FontFace = Font.Name; + gr.FontSize = (uint)Font.Size; + + fe = gr.FontExtents; + } + } + MaxScrollY = 0; + RegisterForGraphicUpdate (); + } + } + protected override int measureRawSize(LayoutingType lt) + { + if (lt == LayoutingType.Height) + return (int)Math.Ceiling((fe.Ascent+fe.Descent) * buffer.LineCount) + Margin * 2; + + return 0;// (int)(fe.MaxXAdvance * buffer.GetLineLength(buffer.longestLineIdx)) + Margin * 2; + } + public override void OnLayoutChanges (LayoutingType layoutType) + { + base.OnLayoutChanges (layoutType); + + if (layoutType == LayoutingType.Height) + updateVisibleLines (); + else if (layoutType == LayoutingType.Width) + updateVisibleColumns (); + } + + protected override void onDraw (Context gr) + { + base.onDraw (gr); + + gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); + gr.SetFontSize (Font.Size); + gr.FontOptions = Interface.FontRenderingOptions; + gr.Antialias = Interface.Antialias; + + Rectangle cb = ClientRectangle; + + Foreground.SetAsSource (gr); + + buffer.editMutex.EnterReadLock (); + editorMutex.EnterReadLock (); + + #region draw text cursor + if (buffer.SelectionInProgress){ + selStartCol = getTabulatedColumn (buffer.SelectionStart); + selEndCol = getTabulatedColumn (buffer.SelectionEnd); + }else if (HasFocus && CurrentLine >= 0){ + gr.LineWidth = 1.0; + double cursorX = cb.X + (getTabulatedColumn(buffer.CurrentPosition) - ScrollX) * fe.MaxXAdvance ; + double cursorY = cb.Y + (buffer.CurrentLine - ScrollY) * (fe.Ascent+fe.Descent); + gr.MoveTo (0.5 + cursorX, cursorY); + gr.LineTo (0.5 + cursorX, cursorY + fe.Ascent+fe.Descent); + gr.Stroke(); + } + #endregion + + drawLines (gr, cb); + + editorMutex.ExitReadLock (); + + buffer.editMutex.ExitReadLock (); + + } + #endregion + + int getBufferColFromVisualCol (int line, int column) { + int i = 0; + int buffCol = 0; + int buffPtr = buffer [line]; + while (i < column && buffCol < buffer.GetLineLength(line)) { + if (buffer.GetCharAt(buffPtr + buffCol) == '\t') + i += Interface.TabSize; + else + i++; + buffCol++; + } + return buffCol; + } + + + #region Mouse handling + + int hoverLine = -1; + public int HoverLine { + get { return hoverLine; } + set { + if (hoverLine == value) + return; + hoverLine = value; + NotifyValueChanged ("HoverLine", hoverLine); + } + } + void updateHoverLine () { +// int hvl = (int)Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent+fe.Descent))); +// hvl = Math.Min (PrintedLines.Count, hvl); +// HoverLine = buffer.IndexOf (PrintedLines[hvl]); + } + void updateCurrentPosFromMouseLocalPos(){ + + buffer.CurrentLine = ScrollY + (int)Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent+fe.Descent))); + int curVisualCol = ScrollX + (int)Math.Round ((mouseLocalPos.X) / fe.MaxXAdvance); + buffer.CurrentColumn = getBufferColFromVisualCol (buffer.CurrentLine, curVisualCol); + } + + public override void onMouseEnter (object sender, MouseMoveEventArgs e) + { + base.onMouseEnter (sender, e); + IFace.MouseCursor = MouseCursors.Text; + } + public override void onMouseLeave (object sender, MouseMoveEventArgs e) + { + base.onMouseLeave (sender, e); + IFace.MouseCursor = MouseCursors.Default; + } + public override void onMouseMove (object sender, MouseMoveEventArgs e) + { + base.onMouseMove (sender, e); + + mouseLocalPos = e.Position - ScreenCoordinates(Slot).TopLeft - ClientRectangle.TopLeft; + + updateHoverLine (); + + if (e.Mouse.LeftButton == ButtonState.Released || !buffer.SelectionInProgress) + return; + + //mouse is down + updateCurrentPosFromMouseLocalPos(); + buffer.SetSelEndPos (); + } + public override void onMouseDown (object sender, MouseButtonEventArgs e) + { + if (!Focusable) + return; + + base.onMouseDown (sender, e); + + if (doubleClicked) { + doubleClicked = false; + return; + } + + updateCurrentPosFromMouseLocalPos (); + buffer.SetSelStartPos (); + } + public override void onMouseUp (object sender, MouseButtonEventArgs e) + { + base.onMouseUp (sender, e); + + if (buffer.SelectionIsEmpty) + buffer.ResetSelection (); + } + + public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e) + { + //doubleClicked = true; + base.onMouseDoubleClick (sender, e); + + buffer.GotoWordStart (); + buffer.SetSelStartPos (); + buffer.GotoWordEnd (); + buffer.SetSelEndPos (); + } + + public override void onMouseWheel (object sender, MouseWheelEventArgs e) + { + base.onMouseWheel (sender, e); + } + #endregion + + #region Keyboard handling + public override void onKeyDown (object sender, KeyEventArgs e) + { + //base.onKeyDown (sender, e); + + Key key = e.Key; + + if (IFace.Ctrl) { + switch (key) { + case Key.S: + projFile.Save (); + break; + case Key.W: + editorMutex.EnterWriteLock (); + if (IFace.Shift) + projFile.Redo (null); + else + projFile.Undo (null); + editorMutex.ExitWriteLock (); + break; + default: + Console.WriteLine (""); + break; + } + } + + switch (key) + { + case Key.BackSpace: + buffer.Delete (); + break; + case Key.Clear: + break; + case Key.Delete: + if (buffer.SelectionIsEmpty) + buffer.MoveRight (); +// else if (e.Shift) +// IFace.Clipboard = buffer.SelectedText; + buffer.Delete (); + break; + case Key.ISO_Enter: + case Key.KP_Enter: + if (!buffer.SelectionIsEmpty) + buffer.Delete (); + buffer.InsertLineBreak (); + break; + case Key.Escape: + buffer.ResetSelection (); + break; + case Key.Home: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.CurrentLine = 0; + buffer.CurrentColumn = 0; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.CurrentLine = 0; + buffer.CurrentColumn = 0; + break; + case Key.End: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.CurrentLine = int.MaxValue; + buffer.CurrentColumn = int.MaxValue; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.CurrentLine = int.MaxValue; + buffer.CurrentColumn = int.MaxValue; + break; + case Key.Insert: + if (IFace.Shift) + buffer.InsertAt (IFace.Clipboard); + else if (IFace.Ctrl && !buffer.SelectionIsEmpty) + IFace.Clipboard = buffer.SelectedText; + break; + case Key.Left: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.GotoWordStart (); + else + buffer.MoveLeft (); + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.GotoWordStart (); + else + buffer.MoveLeft(); + break; + case Key.Right: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + if (IFace.Ctrl) + buffer.GotoWordEnd (); + else + buffer.MoveRight (); + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + if (IFace.Ctrl) + buffer.GotoWordEnd (); + else + buffer.MoveRight (); + break; + case Key.Up: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + CurrentLine--; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + CurrentLine--; + break; + case Key.Down: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + CurrentLine++; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + CurrentLine++; + break; + case Key.Menu: + break; + case Key.Num_Lock: + break; + case Key.Page_Down: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + CurrentLine += visibleLines; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + CurrentLine += visibleLines; + break; + case Key.Page_Up: + if (IFace.Shift) { + if (buffer.SelectionIsEmpty) + buffer.SetSelStartPos (); + CurrentLine -= visibleLines; + buffer.SetSelEndPos (); + break; + } + buffer.ResetSelection (); + CurrentLine -= visibleLines; + break; + case Key.Tab: + buffer.InsertAt ("\t"); + break; + default: + break; + } + RegisterForGraphicUpdate(); + } + public override void onKeyPress (object sender, KeyPressEventArgs e) + { + base.onKeyPress (sender, e); + + buffer.InsertAt (e.KeyChar.ToString()); + buffer.ResetSelection (); + } + #endregion + } +} \ No newline at end of file diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/TextFormatting.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/TextFormatting.cs new file mode 100644 index 00000000..f7b2e518 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/TextFormatting.cs @@ -0,0 +1,19 @@ +using System; + +namespace Crow.Coding +{ + public struct TextFormatting { + public Color Foreground; + public Color Background; + public bool Bold; + public bool Italic; + + public TextFormatting(Color fg, Color bg, bool bold = false, bool italic = false){ + Foreground = fg; + Background = bg; + Bold = bold; + Italic = italic; + } + } +} + diff --git a/Samples/CrowIDE/src/Editors/CodeBuffer/Token.cs b/Samples/CrowIDE/src/Editors/CodeBuffer/Token.cs new file mode 100644 index 00000000..8b4df5e5 --- /dev/null +++ b/Samples/CrowIDE/src/Editors/CodeBuffer/Token.cs @@ -0,0 +1,71 @@ +// +// Token.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; + +namespace Crow.Coding +{ + public struct Token + { + public BufferParser.TokenType Type; + public string Content; + public Point Start; + public Point End; + + public string PrintableContent { + get { return string.IsNullOrEmpty(Content) ? "" : Content.Replace("\t", new String(' ', Interface.TabSize)); } + } + +// public Token (TokenType tokType, string content = ""){ +// Type = tokType; +// Content = content; +// } + + public bool IsNull { get { return IsEmpty && Type == BufferParser.TokenType.Unknown; }} + public bool IsEmpty { get { return string.IsNullOrEmpty(Content); }} + + public static bool operator == (Token t, System.Enum tt){ + return Convert.ToInt32(t.Type) == Convert.ToInt32(tt); + } + public static bool operator != (Token t, System.Enum tt){ + return Convert.ToInt32(t.Type) != Convert.ToInt32(tt); + } + public static bool operator == (System.Enum tt, Token t){ + return Convert.ToInt32(t.Type) == Convert.ToInt32(tt); + } + public static bool operator != (System.Enum tt, Token t){ + return Convert.ToInt32(t.Type) != Convert.ToInt32(tt); + } + + public static Token operator +(Token t, char c){ + t.Content += c; + return t; + } + public static Token operator +(Token t, string s){ + t.Content += s; + return t; + } + public override string ToString () + { + return string.Format ("[Tok{2}->{3}:{0}: {1}]", Type,Content,Start,End); + } + } +} + -- cgit v1.3.1-4-g156e