5

gui.cs

 3 years ago
source link: https://github.com/migueldeicaza/gui.cs/blob/master/Terminal.Gui/Views/TextView.cs
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

gui.cs/TextView.cs at master · migueldeicaza/gui.cs · GitHubPermalink

1286 lines (1168 sloc) 32 KB

// // TextView.cs: multi-line text editing // // Authors: // Miguel de Icaza ([email protected]) // // // TODO: // In ReadOnly mode backspace/space behave like pageup/pagedown // Attributed text on spans // Replace insertion with Insert method // String accumulation (Control-k, control-k is not preserving the last new line, see StringToRunes // Alt-D, Alt-Backspace // API to set the cursor position // API to scroll to a particular place // keybindings to go to top/bottom // public API to insert, remove ranges // Add word forward/word backwards commands // Save buffer API // Mouse // // Desirable: // Move all the text manipulation into the TextModel

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using NStack;

using Rune = System.Rune;

namespace Terminal.Gui { class TextModel { List<List<Rune>> lines = new List<List<Rune>> ();

public bool LoadFile (string file) { if (file == null) throw new ArgumentNullException (nameof (file)); try { FilePath = file; var stream = File.OpenRead (file); } catch { return false; } LoadStream (File.OpenRead (file)); return true; }

public bool CloseFile () { if (FilePath == null) throw new ArgumentNullException (nameof (FilePath)); try { FilePath = null; lines = new List<List<Rune>> (); } catch { return false; } return true; }

// Turns the ustring into runes, this does not split the // contents on a newline if it is present. internal static List<Rune> ToRunes (ustring str) { List<Rune> runes = new List<Rune> (); foreach (var x in str.ToRunes ()) { runes.Add (x); } return runes; }

// Splits a string into a List that contains a List<Rune> for each line public static List<List<Rune>> StringToRunes (ustring content) { var lines = new List<List<Rune>> (); int start = 0, i = 0; // BUGBUG: I think this is buggy w.r.t Unicode. content.Length is bytes, and content[i] is bytes // and content[i] == 10 may be the middle of a Rune. for (; i < content.Length; i++) { if (content [i] == 10) { if (i - start > 0) lines.Add (ToRunes (content [start, i])); else lines.Add (ToRunes (ustring.Empty)); start = i + 1; } } if (i - start >= 0) lines.Add (ToRunes (content [start, null])); return lines; }

void Append (List<byte> line) { var str = ustring.Make (line.ToArray ()); lines.Add (ToRunes (str)); }

public void LoadStream (Stream input) { if (input == null) throw new ArgumentNullException (nameof (input));

lines = new List<List<Rune>> (); var buff = new BufferedStream (input); int v; var line = new List<byte> (); while ((v = buff.ReadByte ()) != -1) { if (v == 10) { Append (line); line.Clear (); continue; } line.Add ((byte)v); } if (line.Count > 0) Append (line); }

public void LoadString (ustring content) { lines = StringToRunes (content); }

public override string ToString () { var sb = new StringBuilder (); for (int i = 0; i < lines.Count; i++) { sb.Append (ustring.Make (lines[i])); if ((i + 1) < lines.Count) { sb.AppendLine (); } } return sb.ToString (); }

public string FilePath { get; set; }

/// <summary> /// The number of text lines in the model /// </summary> public int Count => lines.Count;

/// <summary> /// Returns the specified line as a List of Rune /// </summary> /// <returns>The line.</returns> /// <param name="line">Line number to retrieve.</param> public List<Rune> GetLine (int line) => line < Count ? lines [line] : lines [Count - 1];

/// <summary> /// Adds a line to the model at the specified position. /// </summary> /// <param name="pos">Line number where the line will be inserted.</param> /// <param name="runes">The line of text, as a List of Rune.</param> public void AddLine (int pos, List<Rune> runes) { lines.Insert (pos, runes); }

/// <summary> /// Removes the line at the specified position /// </summary> /// <param name="pos">Position.</param> public void RemoveLine (int pos) { lines.RemoveAt (pos); } }

/// <summary> /// Multi-line text editing <see cref="View"/> /// </summary> /// <remarks> /// <para> /// <see cref="TextView"/> provides a multi-line text editor. Users interact /// with it with the standard Emacs commands for movement or the arrow /// keys. /// </para> /// <list type="table"> /// <listheader> /// <term>Shortcut</term> /// <description>Action performed</description> /// </listheader> /// <item> /// <term>Left cursor, Control-b</term> /// <description> /// Moves the editing point left. /// </description> /// </item> /// <item> /// <term>Right cursor, Control-f</term> /// <description> /// Moves the editing point right. /// </description> /// </item> /// <item> /// <term>Alt-b</term> /// <description> /// Moves one word back. /// </description> /// </item> /// <item> /// <term>Alt-f</term> /// <description> /// Moves one word forward. /// </description> /// </item> /// <item> /// <term>Up cursor, Control-p</term> /// <description> /// Moves the editing point one line up. /// </description> /// </item> /// <item> /// <term>Down cursor, Control-n</term> /// <description> /// Moves the editing point one line down /// </description> /// </item> /// <item> /// <term>Home key, Control-a</term> /// <description> /// Moves the cursor to the beginning of the line. /// </description> /// </item> /// <item> /// <term>End key, Control-e</term> /// <description> /// Moves the cursor to the end of the line. /// </description> /// </item> /// <item> /// <term>Control-Home</term> /// <description> /// Scrolls to the first line and moves the cursor there. /// </description> /// </item> /// <item> /// <term>Control-End</term> /// <description> /// Scrolls to the last line and moves the cursor there. /// </description> /// </item> /// <item> /// <term>Delete, Control-d</term> /// <description> /// Deletes the character in front of the cursor. /// </description> /// </item> /// <item> /// <term>Backspace</term> /// <description> /// Deletes the character behind the cursor. /// </description> /// </item> /// <item> /// <term>Control-k</term> /// <description> /// Deletes the text until the end of the line and replaces the kill buffer /// with the deleted text. You can paste this text in a different place by /// using Control-y. /// </description> /// </item> /// <item> /// <term>Control-y</term> /// <description> /// Pastes the content of the kill ring into the current position. /// </description> /// </item> /// <item> /// <term>Alt-d</term> /// <description> /// Deletes the word above the cursor and adds it to the kill ring. You /// can paste the contents of the kill ring with Control-y. /// </description> /// </item> /// <item> /// <term>Control-q</term> /// <description> /// Quotes the next input character, to prevent the normal processing of /// key handling to take place. /// </description> /// </item> /// </list> /// </remarks> public class TextView : View { TextModel model = new TextModel (); int topRow; int leftColumn; int currentRow; int currentColumn; int selectionStartColumn, selectionStartRow; bool selecting; //bool used;

/// <summary> /// Raised when the <see cref="Text"/> of the <see cref="TextView"/> changes. /// </summary> public event Action TextChanged;

#if false /// <summary> /// Changed event, raised when the text has clicked. /// </summary> /// <remarks> /// Client code can hook up to this event, it is /// raised when the text in the entry changes. /// </remarks> public Action Changed; #endif /// <summary> /// Initializes a <see cref="TextView"/> on the specified area, with absolute position and size. /// </summary> /// <remarks> /// </remarks> public TextView (Rect frame) : base (frame) { CanFocus = true; }

/// <summary> /// Initializes a <see cref="TextView"/> on the specified area, /// with dimensions controlled with the X, Y, Width and Height properties. /// </summary> public TextView () : base () { CanFocus = true; }

void ResetPosition () { topRow = leftColumn = currentRow = currentColumn = 0; }

/// <summary> /// Sets or gets the text in the <see cref="TextView"/>. /// </summary> /// <remarks> /// </remarks> public override ustring Text { get { return model.ToString (); }

set { ResetPosition (); model.LoadString (value); TextChanged?.Invoke (); SetNeedsDisplay (); } }

/// <summary> /// Loads the contents of the file into the <see cref="TextView"/>. /// </summary> /// <returns><c>true</c>, if file was loaded, <c>false</c> otherwise.</returns> /// <param name="path">Path to the file to load.</param> public bool LoadFile (string path) { if (path == null) throw new ArgumentNullException (nameof (path)); ResetPosition (); var res = model.LoadFile (path); SetNeedsDisplay (); return res; }

/// <summary> /// Loads the contents of the stream into the <see cref="TextView"/>. /// </summary> /// <returns><c>true</c>, if stream was loaded, <c>false</c> otherwise.</returns> /// <param name="stream">Stream to load the contents from.</param> public void LoadStream (Stream stream) { if (stream == null) throw new ArgumentNullException (nameof (stream)); ResetPosition (); model.LoadStream (stream); SetNeedsDisplay (); }

/// <summary> /// Closes the contents of the stream into the <see cref="TextView"/>. /// </summary> /// <returns><c>true</c>, if stream was closed, <c>false</c> otherwise.</returns> public bool CloseFile () { ResetPosition (); var res = model.CloseFile (); SetNeedsDisplay (); return res; }

/// <summary> /// Gets the current cursor row. /// </summary> public int CurrentRow => currentRow;

/// <summary> /// Gets the cursor column. /// </summary> /// <value>The cursor column.</value> public int CurrentColumn => currentColumn;

/// <summary> /// Positions the cursor on the current row and column /// </summary> public override void PositionCursor () { if (selecting) { var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Frame.Height); var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height);

SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow)); } var line = model.GetLine (currentRow); var retreat = 0; if (line.Count > 0) { retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)]) ? 1 : 0), 0); } Move (CurrentColumn - leftColumn - retreat, CurrentRow - topRow); }

void ClearRegion (int left, int top, int right, int bottom) { for (int row = top; row < bottom; row++) { Move (left, row); for (int col = left; col < right; col++) AddRune (col, row, ' '); } }

void ColorNormal () { Driver.SetAttribute (ColorScheme.Normal); }

void ColorSelection () { if (HasFocus) Driver.SetAttribute (ColorScheme.Focus); else Driver.SetAttribute (ColorScheme.Normal); }

bool isReadOnly = false;

/// <summary> /// Gets or sets whether the <see cref="TextView"/> is in read-only mode or not /// </summary> /// <value>Boolean value(Default false)</value> public bool ReadOnly { get => isReadOnly; set { isReadOnly = value; } }

// Returns an encoded region start..end (top 32 bits are the row, low32 the column) void GetEncodedRegionBounds (out long start, out long end) { long selection = ((long)(uint)selectionStartRow << 32) | (uint)selectionStartColumn; long point = ((long)(uint)currentRow << 32) | (uint)currentColumn; if (selection > point) { start = point; end = selection; } else { start = selection; end = point; } }

bool PointInSelection (int col, int row) { long start, end; GetEncodedRegionBounds (out start, out end); var q = ((long)(uint)row << 32) | (uint)col; return q >= start && q <= end; }

// // Returns a ustring with the text in the selected // region. // ustring GetRegion () { long start, end; GetEncodedRegionBounds (out start, out end); int startRow = (int)(start >> 32); var maxrow = ((int)(end >> 32)); int startCol = (int)(start & 0xffffffff); var endCol = (int)(end & 0xffffffff); var line = model.GetLine (startRow);

if (startRow == maxrow) return StringFromRunes (line.GetRange (startCol, endCol));

ustring res = StringFromRunes (line.GetRange (startCol, line.Count - startCol));

for (int row = startRow + 1; row < maxrow; row++) { res = res + ustring.Make ((Rune)10) + StringFromRunes (model.GetLine (row)); } line = model.GetLine (maxrow); res = res + ustring.Make ((Rune)10) + StringFromRunes (line.GetRange (0, endCol)); return res; }

// // Clears the contents of the selected region // void ClearRegion () { long start, end; long currentEncoded = ((long)(uint)currentRow << 32) | (uint)currentColumn; GetEncodedRegionBounds (out start, out end); int startRow = (int)(start >> 32); var maxrow = ((int)(end >> 32)); int startCol = (int)(start & 0xffffffff); var endCol = (int)(end & 0xffffffff); var line = model.GetLine (startRow);

if (startRow == maxrow) { line.RemoveRange (startCol, endCol - startCol); currentColumn = startCol; SetNeedsDisplay (new Rect (0, startRow - topRow, Frame.Width, startRow - topRow + 1)); return; }

line.RemoveRange (startCol, line.Count - startCol); var line2 = model.GetLine (maxrow); line.AddRange (line2.Skip (endCol)); for (int row = startRow + 1; row <= maxrow; row++) { model.RemoveLine (startRow + 1); } if (currentEncoded == end) { currentRow -= maxrow - (startRow); } currentColumn = startCol;

SetNeedsDisplay (); }

///<inheritdoc/> public override void Redraw (Rect bounds) { ColorNormal ();

int bottom = bounds.Bottom; int right = bounds.Right; for (int row = bounds.Top; row < bottom; row++) { int textLine = topRow + row; if (textLine >= model.Count) { ColorNormal (); ClearRegion (bounds.Left, row, bounds.Right, row + 1); continue; } var line = model.GetLine (textLine); int lineRuneCount = line.Count; if (line.Count < bounds.Left) { ClearRegion (bounds.Left, row, bounds.Right, row + 1); continue; }

Move (bounds.Left, row); for (int col = bounds.Left; col < right; col++) { var lineCol = leftColumn + col; var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol]; if (selecting && PointInSelection (col, row)) { ColorSelection (); } else { ColorNormal (); }

if (!SpecialRune (rune)) { AddRune (col, row, rune); } } } PositionCursor (); }

bool SpecialRune (Rune rune) { switch (rune) { case (uint)Key.Enter: case 0xd: return true; default: return false; } }

///<inheritdoc/> public override bool CanFocus { get => base.CanFocus; set { base.CanFocus = value; } }

void SetClipboard (ustring text) { Clipboard.Contents = text; }

void AppendClipboard (ustring text) { Clipboard.Contents = Clipboard.Contents + text; }

void Insert (Rune rune) { var line = GetCurrentLine (); line.Insert (currentColumn, rune); var prow = currentRow - topRow;

SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1)); }

ustring StringFromRunes (List<Rune> runes) { if (runes == null) throw new ArgumentNullException (nameof (runes)); int size = 0; foreach (var rune in runes) { size += Utf8.RuneLen (rune); } var encoded = new byte [size]; int offset = 0; foreach (var rune in runes) { offset += Utf8.EncodeRune (rune, encoded, offset); } return ustring.Make (encoded); }

List<Rune> GetCurrentLine () => model.GetLine (currentRow);

void InsertText (ustring text) { var lines = TextModel.StringToRunes (text);

if (lines.Count == 0) return;

var line = GetCurrentLine ();

// Optmize single line if (lines.Count == 1) { line.InsertRange (currentColumn, lines [0]); currentColumn += lines [0].Count; if (currentColumn - leftColumn > Frame.Width) leftColumn = currentColumn - Frame.Width + 1; SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1)); return; }

// Keep a copy of the rest of the line var restCount = line.Count - currentColumn; var rest = line.GetRange (currentColumn, restCount); line.RemoveRange (currentColumn, restCount);

// First line is inserted at the current location, the rest is appended line.InsertRange (currentColumn, lines [0]);

for (int i = 1; i < lines.Count; i++) model.AddLine (currentRow + i, lines [i]);

var last = model.GetLine (currentRow + lines.Count - 1); var lastp = last.Count; last.InsertRange (last.Count, rest);

// Now adjjust column and row positions currentRow += lines.Count - 1; currentColumn = lastp; if (currentRow - topRow > Frame.Height) { topRow = currentRow - Frame.Height + 1; if (topRow < 0) topRow = 0; } if (currentColumn < leftColumn) leftColumn = currentColumn; if (currentColumn - leftColumn >= Frame.Width) leftColumn = currentColumn - Frame.Width + 1; SetNeedsDisplay (); }

// The column we are tracking, or -1 if we are not tracking any column int columnTrack = -1;

// Tries to snap the cursor to the tracking column void TrackColumn () { // Now track the column var line = GetCurrentLine (); if (line.Count < columnTrack) currentColumn = line.Count; else if (columnTrack != -1) currentColumn = columnTrack; else if (currentColumn > line.Count) currentColumn = line.Count; Adjust (); }

void Adjust () { bool need = false; if (currentColumn < leftColumn) { currentColumn = leftColumn; need = true; } if (currentColumn - leftColumn > Frame.Width) { leftColumn = currentColumn - Frame.Width + 1; need = true; } if (currentRow < topRow) { topRow = currentRow; need = true; } if (currentRow - topRow > Frame.Height) { topRow = currentRow - Frame.Height + 1; need = true; } if (need) SetNeedsDisplay (); else PositionCursor (); }

/// <summary> /// Will scroll the <see cref="TextView"/> to display the specified row at the top /// </summary> /// <param name="row">Row that should be displayed at the top, if the value is negative it will be reset to zero</param> public void ScrollTo (int row) { if (row < 0) row = 0; topRow = row > model.Count ? model.Count - 1 : row; SetNeedsDisplay (); }

bool lastWasKill;

///<inheritdoc/> public override bool ProcessKey (KeyEvent kb) { int restCount; List<Rune> rest;

// Handle some state here - whether the last command was a kill // operation and the column tracking (up/down) switch (kb.Key) { case Key.N | Key.CtrlMask: case Key.CursorDown: case Key.P | Key.CtrlMask: case Key.CursorUp: lastWasKill = false; break; case Key.K | Key.CtrlMask: break; default: lastWasKill = false; columnTrack = -1; break; }

// Dispatch the command. switch (kb.Key) { case Key.PageDown: case Key.V | Key.CtrlMask: int nPageDnShift = Frame.Height - 1; if (currentRow < model.Count) { if (columnTrack == -1) columnTrack = currentColumn; currentRow = (currentRow + nPageDnShift) > model.Count ? model.Count : currentRow + nPageDnShift; if (topRow < currentRow - nPageDnShift) { topRow = currentRow >= model.Count ? currentRow - nPageDnShift : topRow + nPageDnShift; SetNeedsDisplay (); } TrackColumn (); PositionCursor (); } break;

case Key.PageUp: case ((int)'v' + Key.AltMask): int nPageUpShift = Frame.Height - 1; if (currentRow > 0) { if (columnTrack == -1) columnTrack = currentColumn; currentRow = currentRow - nPageUpShift < 0 ? 0 : currentRow - nPageUpShift; if (currentRow < topRow) { topRow = topRow - nPageUpShift < 0 ? 0 : topRow - nPageUpShift; SetNeedsDisplay (); } TrackColumn (); PositionCursor (); } break;

case Key.N | Key.CtrlMask: case Key.CursorDown: MoveDown (); break;

case Key.P | Key.CtrlMask: case Key.CursorUp: MoveUp (); break;

case Key.F | Key.CtrlMask: case Key.CursorRight: var currentLine = GetCurrentLine (); if (currentColumn < currentLine.Count) { currentColumn++; if (currentColumn >= leftColumn + Frame.Width) { leftColumn++; SetNeedsDisplay (); } PositionCursor (); } else { if (currentRow + 1 < model.Count) { currentRow++; currentColumn = 0; leftColumn = 0; if (currentRow >= topRow + Frame.Height) { topRow++; } SetNeedsDisplay (); PositionCursor (); } break; } break;

case Key.B | Key.CtrlMask: case Key.CursorLeft: if (currentColumn > 0) { currentColumn--; if (currentColumn < leftColumn) { leftColumn--; SetNeedsDisplay (); } PositionCursor (); } else { if (currentRow > 0) { currentRow--; if (currentRow < topRow) { topRow--; SetNeedsDisplay (); } currentLine = GetCurrentLine (); currentColumn = currentLine.Count; int prev = leftColumn; leftColumn = currentColumn - Frame.Width + 1; if (leftColumn < 0) leftColumn = 0; if (prev != leftColumn) SetNeedsDisplay (); PositionCursor (); } } break;

case Key.Delete: case Key.Backspace: if (isReadOnly) break; if (currentColumn > 0) { // Delete backwards currentLine = GetCurrentLine (); currentLine.RemoveAt (currentColumn - 1); currentColumn--; if (currentColumn < leftColumn) { leftColumn--; SetNeedsDisplay (); } else SetNeedsDisplay (new Rect (0, currentRow - topRow, 1, Frame.Width)); } else { // Merges the current line with the previous one. if (currentRow == 0) return true; var prowIdx = currentRow - 1; var prevRow = model.GetLine (prowIdx); var prevCount = prevRow.Count; model.GetLine (prowIdx).AddRange (GetCurrentLine ()); model.RemoveLine (currentRow); currentRow--; currentColumn = prevCount; leftColumn = currentColumn - Frame.Width + 1; if (leftColumn < 0) leftColumn = 0; SetNeedsDisplay (); } break;

// Home, C-A case Key.Home: case Key.A | Key.CtrlMask: currentColumn = 0; if (currentColumn < leftColumn) { leftColumn = 0; SetNeedsDisplay (); } else PositionCursor (); break; case Key.DeleteChar: case Key.D | Key.CtrlMask: // Delete if (isReadOnly) break; currentLine = GetCurrentLine (); if (currentColumn == currentLine.Count) { if (currentRow + 1 == model.Count) break; var nextLine = model.GetLine (currentRow + 1); currentLine.AddRange (nextLine); model.RemoveLine (currentRow + 1); var sr = currentRow - topRow; SetNeedsDisplay (new Rect (0, sr, Frame.Width, sr + 1)); } else { currentLine.RemoveAt (currentColumn); var r = currentRow - topRow; SetNeedsDisplay (new Rect (currentColumn - leftColumn, r, Frame.Width, r + 1)); } break;

case Key.End: case Key.E | Key.CtrlMask: // End currentLine = GetCurrentLine (); currentColumn = currentLine.Count; int pcol = leftColumn; leftColumn = currentColumn - Frame.Width + 1; if (leftColumn < 0) leftColumn = 0; if (pcol != leftColumn) SetNeedsDisplay (); PositionCursor (); break;

case Key.K | Key.CtrlMask: // kill-to-end if (isReadOnly) break; currentLine = GetCurrentLine (); if (currentLine.Count == 0) { model.RemoveLine (currentRow); var val = ustring.Make ((Rune)'\n'); if (lastWasKill) AppendClipboard (val); else SetClipboard (val); } else { restCount = currentLine.Count - currentColumn; rest = currentLine.GetRange (currentColumn, restCount); var val = StringFromRunes (rest); if (lastWasKill) AppendClipboard (val); else SetClipboard (val); currentLine.RemoveRange (currentColumn, restCount); } SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height)); lastWasKill = true; break;

case Key.Y | Key.CtrlMask: // Control-y, yank if (isReadOnly) break; InsertText (Clipboard.Contents); selecting = false; break;

case Key.Space | Key.CtrlMask: selecting = true; selectionStartColumn = currentColumn; selectionStartRow = currentRow; break;

case ((int)'w' + Key.AltMask): SetClipboard (GetRegion ()); selecting = false; break;

case Key.W | Key.CtrlMask: SetClipboard (GetRegion ()); if (!isReadOnly) ClearRegion (); selecting = false; break;

case (Key)((int)'b' + Key.AltMask): var newPos = WordBackward (currentColumn, currentRow); if (newPos.HasValue) { currentColumn = newPos.Value.col; currentRow = newPos.Value.row; } Adjust ();

break;

case (Key)((int)'f' + Key.AltMask): newPos = WordForward (currentColumn, currentRow); if (newPos.HasValue) { currentColumn = newPos.Value.col; currentRow = newPos.Value.row; } Adjust (); break;

case Key.Enter: if (isReadOnly) break; var orow = currentRow; currentLine = GetCurrentLine (); restCount = currentLine.Count - currentColumn; rest = currentLine.GetRange (currentColumn, restCount); currentLine.RemoveRange (currentColumn, restCount); model.AddLine (currentRow + 1, rest); currentRow++; bool fullNeedsDisplay = false; if (currentRow >= topRow + Frame.Height) { topRow++; fullNeedsDisplay = true; } currentColumn = 0; if (currentColumn < leftColumn) { fullNeedsDisplay = true; leftColumn = 0; }

if (fullNeedsDisplay) SetNeedsDisplay (); else SetNeedsDisplay (new Rect (0, currentRow - topRow, 2, Frame.Height)); break;

case Key.CtrlMask | Key.End: MoveEnd (); break;

case Key.CtrlMask | Key.Home: MoveHome (); break;

default: // Ignore control characters and other special keys if (kb.Key < Key.Space || kb.Key > Key.CharMask) return false; //So that special keys like tab can be processed if (isReadOnly) return true; Insert ((uint)kb.Key); currentColumn++; if (currentColumn >= leftColumn + Frame.Width) { leftColumn++; SetNeedsDisplay (); } PositionCursor (); return true; } return true; }

private void MoveUp () { if (currentRow > 0) { if (columnTrack == -1) columnTrack = currentColumn; currentRow--; if (currentRow < topRow) { topRow--; SetNeedsDisplay (); } TrackColumn (); PositionCursor (); } }

private void MoveDown () { if (currentRow + 1 < model.Count) { if (columnTrack == -1) columnTrack = currentColumn; currentRow++; if (currentRow >= topRow + Frame.Height) { topRow++; SetNeedsDisplay (); } TrackColumn (); PositionCursor (); } }

IEnumerable<(int col, int row, Rune rune)> ForwardIterator (int col, int row) { if (col < 0 || row < 0) yield break; if (row >= model.Count) yield break; var line = GetCurrentLine (); if (col >= line.Count) yield break;

while (row < model.Count) { for (int c = col; c < line.Count; c++) { yield return (c, row, line [c]); } col = 0; row++; line = GetCurrentLine (); } }

Rune RuneAt (int col, int row) => model.GetLine (row) [col];

/// <summary> /// Will scroll the <see cref="TextView"/> to the last line and position the cursor there. /// </summary> public void MoveEnd () { currentRow = model.Count - 1; TrackColumn (); PositionCursor (); }

/// <summary> /// Will scroll the <see cref="TextView"/> to the first line and position the cursor there. /// </summary> public void MoveHome () { currentRow = 0; TrackColumn (); PositionCursor (); }

bool MoveNext (ref int col, ref int row, out Rune rune) { var line = model.GetLine (row); if (col + 1 < line.Count) { col++; rune = line [col]; return true; } while (row + 1 < model.Count) { col = 0; row++; line = model.GetLine (row); if (line.Count > 0) { rune = line [0]; return true; } } rune = 0; return false; }

bool MovePrev (ref int col, ref int row, out Rune rune) { var line = model.GetLine (row);

if (col > 0) { col--; rune = line [col]; return true; } if (row == 0) { rune = 0; return false; } while (row > 0) { row--; line = model.GetLine (row); col = line.Count - 1; if (col >= 0) { rune = line [col]; return true; } } rune = 0; return false; }

(int col, int row)? WordForward (int fromCol, int fromRow) { var col = fromCol; var row = fromRow; var line = GetCurrentLine (); var rune = RuneAt (col, row);

var srow = row; if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) { while (MoveNext (ref col, ref row, out rune)) { if (Rune.IsLetterOrDigit (rune)) break; } while (MoveNext (ref col, ref row, out rune)) { if (!Rune.IsLetterOrDigit (rune)) break; } } else { while (MoveNext (ref col, ref row, out rune)) { if (!Rune.IsLetterOrDigit (rune)) break; } } if (fromCol != col || fromRow != row) return (col, row); return null; }

(int col, int row)? WordBackward (int fromCol, int fromRow) { if (fromRow == 0 && fromCol == 0) return null;

var col = fromCol; var row = fromRow; var line = GetCurrentLine (); var rune = RuneAt (col, row);

if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) { while (MovePrev (ref col, ref row, out rune)) { if (Rune.IsLetterOrDigit (rune)) break; } while (MovePrev (ref col, ref row, out rune)) { if (!Rune.IsLetterOrDigit (rune)) break; } } else { while (MovePrev (ref col, ref row, out rune)) { if (!Rune.IsLetterOrDigit (rune)) break; } } if (fromCol != col || fromRow != row) return (col, row); return null; }

///<inheritdoc/> public override bool MouseEvent (MouseEvent ev) { if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked) && !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp)) { return false; }

if (!CanFocus) { return true; }

if (!HasFocus) { SetFocus (); }

if (ev.Flags == MouseFlags.Button1Clicked) { if (model.Count > 0) { var maxCursorPositionableLine = (model.Count - 1) - topRow; if (ev.Y > maxCursorPositionableLine) { currentRow = maxCursorPositionableLine; } else { currentRow = ev.Y + topRow; } var r = GetCurrentLine (); if (ev.X - leftColumn >= r.Count) currentColumn = r.Count - leftColumn; else currentColumn = ev.X - leftColumn; } PositionCursor (); } else if (ev.Flags == MouseFlags.WheeledDown) { lastWasKill = false; MoveDown (); } else if (ev.Flags == MouseFlags.WheeledUp) { lastWasKill = false; MoveUp (); }

return true; } }

}


Recommend

  • 50
    • Github github.com 6 years ago
    • Cache

    GitHub - yangxikun/ytrace_gui: ytrace web ui

    中文README This is a web ui for ytrace, see also

  • 46
    • www.10tiao.com 6 years ago
    • Cache

    13个Python GUI库

    Python Python是一门高级编程语言。它用于通用编程。Python语言由Guido van Rossum创建,并于1991年首次发布。Python的设计哲学着重于代码的可读性。因此空白在Python中具有重要的意义。...

  • 25
    • zhuanlan.zhihu.com 5 years ago
    • Cache

    被滥用的 GUI 设计模式 - 知乎

  • 32
    • www.tuicool.com 5 years ago
    • Cache

    npm-gui

    npm-gui About npm-gui is a tool for managing java...

  • 33
    • www.tuicool.com 5 years ago
    • Cache

    Lazydocker, a Terminal GUI for Docker

    A simple terminal UI for docker and docker-compose, written in Go with the gocui li...

  • 39
    • www.tuicool.com 5 years ago
    • Cache

    Photon Micro GUI

    Let me tell you a bit about Photon… From my previous post aboutAscend, some folks noticed its existence and actually found the github repository . The repository is rather un...

  • 37
    • www.tuicool.com 5 years ago
    • Cache

    Memory Footprint of GUI Toolkits

    By Richard Szibele 16 August, 2019 While searching for the "perfect" GUI toolkit/library I've started measuring the memory usage of the various toolkits and I'm going to share my results here...

  • 45
    • www.tuicool.com 5 years ago
    • Cache

    Rust vs GUI

    I’m neither an experienced Rust programmer nor GTK developer. I don’t even have that much experience with building native GUIs or with system level programming. Hell, I might be the least qualified guy on the internet to...

  • 45
    • www.tuicool.com 4 years ago
    • Cache

    Rust 2020: GUI and community

    In response to the call for blogs about the vision for Rust for 2020 , I’m going to write about GUI. I believe the time is right for a native...

  • 42
    • towardsdatascience.com 4 years ago
    • Cache

    GUI on Linux servers(AWS EC2s)

    Windows users have it easy, they have the RDP. Fret no more, fellow Linux users! Meet VNC, an alternative to the SSH CLI interface to get GUI on Linux servers.

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK