From: Jean-Philippe Bruyère Date: Thu, 31 Aug 2017 10:55:06 +0000 (+0200) Subject: tabulation handling X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=86c81d6d9f6222802ca95661a6bd1c65b0385aa1;p=jp%2Fcrowedit.git tabulation handling --- diff --git a/src/CodeBuffer.cs b/src/CodeBuffer.cs index 104ddb4..3c82d12 100644 --- a/src/CodeBuffer.cs +++ b/src/CodeBuffer.cs @@ -42,9 +42,21 @@ namespace Crow.Coding 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 Length { get { return lines.Count;}} + /// + /// Return line with tabs replaced by spaces + /// + public string GetPrintableLine(int i){ + return string.IsNullOrEmpty(lines[i]) ? "" : lines[i].Replace("\t", new String(' ', Interface.TabSize)); + } + public string this[int i] { get { return lines[i]; } @@ -83,14 +95,14 @@ namespace Crow.Coding AddRange (Regex.Split (rawSource, "\r\n|\r|\n|\\\\n")); lineBreak = detectLineBreakKind (rawSource); - findLongestLine (); + findLongestVisualLine (); } - void findLongestLine(){ + void findLongestVisualLine(){ longestLineCharCount = 0; - for (int i = 0; i < lines.Count; i++) { - if (lines[i].Length > longestLineCharCount) { - longestLineCharCount = lines[i].Length; + for (int i = 0; i < this.Length; i++) { + if (this.GetPrintableLine(i).Length > longestLineCharCount) { + longestLineCharCount = this.GetPrintableLine(i).Length; longestLineIdx = i; } } @@ -127,6 +139,207 @@ namespace Crow.Coding return lines.Count > 0 ? lines.Aggregate((i, j) => i + this.lineBreak + j) : ""; } } + + /// + /// 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); + } + /// + /// convert buffer postition to visual position + /// + Point getVisualPos (Point buffPos) { + int vCol = this[buffPos.Y].Substring(0, buffPos.X).Replace("\t", new String(' ', Interface.TabSize)).Length; + return new Point (vCol, buffPos.Y); + } + /// + /// Gets visual position computed from actual buffer position + /// + public Point VisualPosition { + get { return getVisualPos (new Point (_currentCol, _currentLine)); } + } + /// + /// set buffer current position from visual position + /// + public void SetBufferPos(Point visualPosition) { + CurrentPosition = getBuffPos(visualPosition); + } + + #region Editing and moving cursor + Point selectionStart = -1; + Point selectionEnd = -1; + public void SetSelection (Point visualStart, Point visualEnd) { + selectionStart = getBuffPos (visualStart); + selectionEnd = getBuffPos (visualEnd); + } + public void ResetSelection () { + selectionStart = selectionEnd = -1; + } + bool selectionIsEmpty { + get { return selectionStart == selectionEnd; } + } + 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; + } + } + 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; + //force recheck of currentCol for bounding + int cc = _currentCol; + _currentCol = 0; + CurrentColumn = cc; + } + } + public Point CurrentPosition { + get { return new Point(CurrentColumn, CurrentLine); } + set { + _currentCol = value.X; + _currentLine = value.Y; + } + } + protected Char CurrentChar { get { return lines [CurrentLine] [CurrentColumn]; } } + + /// + /// Moves cursor one char to the left. + /// + /// true if move succeed + public bool MoveLeft(){ + int tmp = _currentCol - 1; + if (tmp < 0) { + if (_currentLine == 0) + return false; + CurrentLine--; + CurrentColumn = int.MaxValue; + } else + CurrentColumn = tmp; + return true; + } + /// + /// Moves cursor one char to the right. + /// + /// true if move succeed + public bool MoveRight(){ + int tmp = _currentCol + 1; + if (tmp > this [_currentLine].Length){ + if (CurrentLine == this.Length - 1) + return false; + CurrentLine++; + CurrentColumn = 0; + } else + CurrentColumn = tmp; + return true; + } + 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() + { + if (selectionIsEmpty) { + if (CurrentColumn == 0) { + if (CurrentLine == 0 && this.Length == 1) + return; + CurrentLine--; + CurrentColumn = this [CurrentLine].Length; + this [CurrentLine] += this [CurrentLine + 1]; + RemoveAt (CurrentLine + 1); + return; + } + CurrentColumn--; + this [CurrentLine] = this [CurrentLine].Remove (CurrentColumn, 1); + } else { + int linesToRemove = selectionEnd.Y - selectionStart.Y + 1; + int l = selectionStart.Y; + + if (linesToRemove > 0) { + this [l] = this [l].Remove (selectionStart.X, this [l].Length - selectionStart.X) + + this [selectionEnd.Y].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 + this [l] = this [l].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(); + this [CurrentLine] = this [CurrentLine].Insert (CurrentColumn, strLines[0]); + CurrentColumn += strLines[0].Length; + for (int i = 1; i < strLines.Length; i++) { + InsertLineBreak (); + this [CurrentLine] = this [CurrentLine].Insert (CurrentColumn, strLines[i]); + CurrentColumn += strLines[i].Length; + } + } + /// + /// Insert a line break. + /// + public void InsertLineBreak() + { + Insert(CurrentLine + 1, this[CurrentLine].Substring(CurrentColumn)); + this [CurrentLine] = this [CurrentLine].Substring (0, CurrentColumn); + CurrentLine++; + CurrentColumn = 0; + } + #endregion } } diff --git a/src/SourceEditor.cs b/src/SourceEditor.cs index 7b73819..237a5d8 100644 --- a/src/SourceEditor.cs +++ b/src/SourceEditor.cs @@ -192,11 +192,13 @@ namespace Crow.Coding return; if (value < 0) _currentCol = 0; - else if (value > buffer [_currentLine].Length) - _currentCol = buffer [_currentLine].Length; + else if (value > buffer.GetPrintableLine(_currentLine).Length) + _currentCol = buffer.GetPrintableLine(_currentLine).Length; else _currentCol = value; + buffer.SetBufferPos (CurrentPosition); + if (_currentCol < ScrollX) ScrollX = _currentCol; else if (_currentCol >= ScrollX + visibleColumns) @@ -221,6 +223,9 @@ namespace Crow.Coding int cc = _currentCol; _currentCol = 0; CurrentColumn = cc; + + buffer.SetBufferPos (CurrentPosition); + //System.Diagnostics.Debug.WriteLine ("Scroll:{0} visibleLines:{1} CurLine:{2}", ScrollY, visibleLines, CurrentLine); if (_currentLine < ScrollY) ScrollY = _currentLine; @@ -231,6 +236,10 @@ namespace Crow.Coding } [XmlIgnore]public Point CurrentPosition { get { return new Point(CurrentColumn, CurrentLine); } + set { + CurrentColumn = value.X; + CurrentLine = value.Y; + } } //TODO:using HasFocus for drawing selection cause SelBegin and Release binding not to work /// @@ -319,87 +328,35 @@ namespace Crow.Coding /// /// true if move succeed public bool MoveLeft(){ - int tmp = _currentCol - 1; - if (tmp < 0) { - if (_currentLine == 0) - return false; - CurrentLine--; - CurrentColumn = int.MaxValue; - } else - CurrentColumn = tmp; - return true; + bool res = buffer.MoveLeft(); + CurrentPosition = buffer.VisualPosition; + return res; } /// /// Moves cursor one char to the right. /// /// true if move succeed public bool MoveRight(){ - int tmp = _currentCol + 1; - if (tmp > buffer [_currentLine].Length){ - if (CurrentLine == buffer.Length - 1) - return false; - CurrentLine++; - CurrentColumn = 0; - } else - CurrentColumn = tmp; - return true; + bool res = buffer.MoveRight(); + CurrentPosition = buffer.VisualPosition; + return res; } public void GotoWordStart(){ - if (buffer[CurrentLine].Length == 0) - return; - CurrentColumn--; - //skip white spaces - while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) - CurrentColumn--; - while (char.IsLetterOrDigit (buffer [CurrentLine] [CurrentColumn]) && CurrentColumn > 0) - CurrentColumn--; - if (!char.IsLetterOrDigit (this.CurrentChar)) - CurrentColumn++; + buffer.GotoWordStart(); + CurrentPosition = buffer.VisualPosition; } public void GotoWordEnd(){ - //skip white spaces - if (CurrentColumn >= buffer [CurrentLine].Length - 1) - return; - while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < buffer [CurrentLine].Length-1) - CurrentColumn++; - while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn < buffer [CurrentLine].Length-1) - CurrentColumn++; - if (char.IsLetterOrDigit (this.CurrentChar)) - CurrentColumn++; + buffer.GotoWordEnd(); + CurrentPosition = buffer.VisualPosition; } public void DeleteChar() { - if (selectionIsEmpty) { - if (CurrentColumn == 0) { - if (CurrentLine == 0 && buffer.Length == 1) - return; - CurrentLine--; - CurrentColumn = buffer [CurrentLine].Length; - buffer [CurrentLine] += buffer [CurrentLine + 1]; - buffer.RemoveAt (CurrentLine + 1); - OnTextChanged (this, null); - return; - } - CurrentColumn--; - buffer [CurrentLine] = buffer [CurrentLine].Remove (CurrentColumn, 1); - } else { - int linesToRemove = selectionEnd.Y - selectionStart.Y + 1; - int l = selectionStart.Y; - - if (linesToRemove > 0) { - buffer [l] = buffer [l].Remove (selectionStart.X, buffer [l].Length - selectionStart.X) + - buffer [selectionEnd.Y].Substring (selectionEnd.X, buffer [selectionEnd.Y].Length - selectionEnd.X); - l++; - for (int c = 0; c < linesToRemove-1; c++) - buffer.RemoveAt (l); - CurrentLine = selectionStart.Y; - CurrentColumn = selectionStart.X; - } else - buffer [l] = buffer [l].Remove (selectionStart.X, selectionEnd.X - selectionStart.X); - CurrentColumn = selectionStart.X; - SelBegin = -1; - SelRelease = -1; - } + if (!selectionIsEmpty) + buffer.SetSelection (selectionStart, selectionEnd); + buffer.DeleteChar (); + CurrentPosition = buffer.VisualPosition; + SelBegin = -1; + SelRelease = -1; OnTextChanged (this, null); } /// @@ -409,15 +366,11 @@ namespace Crow.Coding protected void Insert(string str) { if (!selectionIsEmpty) - this.DeleteChar (); - string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray(); - buffer [CurrentLine] = buffer [CurrentLine].Insert (CurrentColumn, strLines[0]); - CurrentColumn += strLines[0].Length; - for (int i = 1; i < strLines.Length; i++) { - InsertLineBreak (); - buffer [CurrentLine] = buffer [CurrentLine].Insert (CurrentColumn, strLines[i]); - CurrentColumn += strLines[i].Length; - } + DeleteChar (); + + buffer.Insert (str); + CurrentPosition = buffer.VisualPosition; + OnTextChanged (this, null); RegisterForGraphicUpdate(); } @@ -426,15 +379,16 @@ namespace Crow.Coding /// protected void InsertLineBreak() { - buffer.Insert(CurrentLine + 1, buffer[CurrentLine].Substring(CurrentColumn)); - buffer [CurrentLine] = buffer [CurrentLine].Substring (0, CurrentColumn); - CurrentLine++; - CurrentColumn = 0; + buffer.InsertLineBreak (); + CurrentPosition = buffer.VisualPosition; OnTextChanged (this, null); } #endregion #region Drawing + /// + /// Draw unparsed buffer. + /// void draw(Context gr){ gr.SelectFontFace (Font.Name, Font.Slant, Font.Wheight); gr.SetFontSize (Font.Size); @@ -538,7 +492,7 @@ namespace Crow.Coding int lPtr = 0; for (int t = 0; t < tokens.Count; t++) { - string lstr = tokens [t].Content; + string lstr = tokens [t].PrintableContent; if (lPtr < ScrollX) { if (lPtr - ScrollX + lstr.Length <= 0) { lPtr += lstr.Length; @@ -808,7 +762,7 @@ namespace Crow.Coding case Key.Left: if (e.Shift) { if (selectionIsEmpty) - SelBegin = new Point(CurrentColumn, CurrentLine); + SelBegin = CurrentPosition; if (e.Control) GotoWordStart (); else if (!MoveLeft ()) diff --git a/src/Token.cs b/src/Token.cs index df9f66e..e0cb7e0 100644 --- a/src/Token.cs +++ b/src/Token.cs @@ -30,6 +30,10 @@ namespace Crow.Coding 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;