From 72f5808025af2d3d24bf1ccca899f7d291cb1dda Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Tue, 7 Jun 2016 11:43:28 -0400 Subject: [PATCH] Replace rope with lineArray --- cmd/micro/bindings.go | 130 +++++++++++++++----------------- cmd/micro/buffer.go | 92 ++++++++++------------- cmd/micro/command.go | 10 +-- cmd/micro/cursor.go | 151 ++++++++++++-------------------------- cmd/micro/eventhandler.go | 22 +++--- cmd/micro/highlighter.go | 13 ++-- cmd/micro/lineArray.go | 133 +++++++++++++++++++++++++++++++++ cmd/micro/loc.go | 120 ++++++++++++++++++++++++++++++ cmd/micro/micro.go | 2 +- cmd/micro/search.go | 6 +- cmd/micro/util.go | 8 ++ cmd/micro/view.go | 41 +++++------ 12 files changed, 453 insertions(+), 275 deletions(-) create mode 100644 cmd/micro/lineArray.go create mode 100644 cmd/micro/loc.go diff --git a/cmd/micro/bindings.go b/cmd/micro/bindings.go index b3248131..89cd2702 100644 --- a/cmd/micro/bindings.go +++ b/cmd/micro/bindings.go @@ -405,7 +405,7 @@ func DefaultBindings() map[string]string { // CursorUp moves the cursor up func (v *View) CursorUp() bool { if v.Cursor.HasSelection() { - v.Cursor.SetLoc(v.Cursor.CurSelection[0]) + v.Cursor.Loc = v.Cursor.CurSelection[0] v.Cursor.ResetSelection() } v.Cursor.Up() @@ -415,7 +415,7 @@ func (v *View) CursorUp() bool { // CursorDown moves the cursor down func (v *View) CursorDown() bool { if v.Cursor.HasSelection() { - v.Cursor.SetLoc(v.Cursor.CurSelection[1]) + v.Cursor.Loc = v.Cursor.CurSelection[1] v.Cursor.ResetSelection() } v.Cursor.Down() @@ -425,7 +425,7 @@ func (v *View) CursorDown() bool { // CursorLeft moves the cursor left func (v *View) CursorLeft() bool { if v.Cursor.HasSelection() { - v.Cursor.SetLoc(v.Cursor.CurSelection[0]) + v.Cursor.Loc = v.Cursor.CurSelection[0] v.Cursor.ResetSelection() } else { v.Cursor.Left() @@ -436,7 +436,7 @@ func (v *View) CursorLeft() bool { // CursorRight moves the cursor right func (v *View) CursorRight() bool { if v.Cursor.HasSelection() { - v.Cursor.SetLoc(v.Cursor.CurSelection[1] - 1) + v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf) v.Cursor.ResetSelection() } else { v.Cursor.Right() @@ -458,75 +458,71 @@ func (v *View) WordLeft() bool { // SelectUp selects up one line func (v *View) SelectUp() bool { - loc := v.Cursor.Loc() if !v.Cursor.HasSelection() { - v.Cursor.OrigSelection[0] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc } v.Cursor.Up() - v.Cursor.SelectTo(v.Cursor.Loc()) + v.Cursor.SelectTo(v.Cursor.Loc) return true } // SelectDown selects down one line func (v *View) SelectDown() bool { - loc := v.Cursor.Loc() if !v.Cursor.HasSelection() { - v.Cursor.OrigSelection[0] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc } v.Cursor.Down() - v.Cursor.SelectTo(v.Cursor.Loc()) + v.Cursor.SelectTo(v.Cursor.Loc) return true } // SelectLeft selects the character to the left of the cursor func (v *View) SelectLeft() bool { - loc := v.Cursor.Loc() - count := v.Buf.Len() - 1 - if loc > count { + loc := v.Cursor.Loc + count := v.Buf.End().Move(-1, v.Buf) + if loc.GreaterThan(count) { loc = count } if !v.Cursor.HasSelection() { v.Cursor.OrigSelection[0] = loc } v.Cursor.Left() - v.Cursor.SelectTo(v.Cursor.Loc()) + v.Cursor.SelectTo(v.Cursor.Loc) return true } // SelectRight selects the character to the right of the cursor func (v *View) SelectRight() bool { - loc := v.Cursor.Loc() - count := v.Buf.Len() - 1 - if loc > count { + loc := v.Cursor.Loc + count := v.Buf.End().Move(-1, v.Buf) + if loc.GreaterThan(count) { loc = count } if !v.Cursor.HasSelection() { v.Cursor.OrigSelection[0] = loc } v.Cursor.Right() - v.Cursor.SelectTo(v.Cursor.Loc()) + v.Cursor.SelectTo(v.Cursor.Loc) return true } // SelectWordRight selects the word to the right of the cursor func (v *View) SelectWordRight() bool { - loc := v.Cursor.Loc() if !v.Cursor.HasSelection() { - v.Cursor.OrigSelection[0] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc } v.Cursor.WordRight() - v.Cursor.SelectTo(v.Cursor.Loc()) + v.Cursor.SelectTo(v.Cursor.Loc) return true } // SelectWordLeft selects the word to the left of the cursor func (v *View) SelectWordLeft() bool { - loc := v.Cursor.Loc() if !v.Cursor.HasSelection() { - v.Cursor.OrigSelection[0] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc } v.Cursor.WordLeft() - v.Cursor.SelectTo(v.Cursor.Loc()) + v.Cursor.SelectTo(v.Cursor.Loc) return true } @@ -544,23 +540,21 @@ func (v *View) EndOfLine() bool { // SelectToStartOfLine selects to the start of the current line func (v *View) SelectToStartOfLine() bool { - loc := v.Cursor.Loc() if !v.Cursor.HasSelection() { - v.Cursor.OrigSelection[0] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc } v.Cursor.Start() - v.Cursor.SelectTo(v.Cursor.Loc()) + v.Cursor.SelectTo(v.Cursor.Loc) return true } // SelectToEndOfLine selects to the end of the current line func (v *View) SelectToEndOfLine() bool { - loc := v.Cursor.Loc() if !v.Cursor.HasSelection() { - v.Cursor.OrigSelection[0] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc } v.Cursor.End() - v.Cursor.SelectTo(v.Cursor.Loc()) + v.Cursor.SelectTo(v.Cursor.Loc) return true } @@ -573,29 +567,27 @@ func (v *View) CursorStart() bool { // CursorEnd moves the cursor to the end of the buffer func (v *View) CursorEnd() bool { - v.Cursor.SetLoc(v.Buf.Len()) + v.Cursor.Loc = v.Buf.End() return true } // SelectToStart selects the text from the cursor to the start of the buffer func (v *View) SelectToStart() bool { - loc := v.Cursor.Loc() if !v.Cursor.HasSelection() { - v.Cursor.OrigSelection[0] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc } v.CursorStart() - v.Cursor.SelectTo(0) + v.Cursor.SelectTo(v.Buf.Start()) return true } // SelectToEnd selects the text from the cursor to the end of the buffer func (v *View) SelectToEnd() bool { - loc := v.Cursor.Loc() if !v.Cursor.HasSelection() { - v.Cursor.OrigSelection[0] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc } v.CursorEnd() - v.Cursor.SelectTo(v.Buf.Len()) + v.Cursor.SelectTo(v.Buf.End()) return true } @@ -605,7 +597,7 @@ func (v *View) InsertSpace() bool { v.Cursor.DeleteSelection() v.Cursor.ResetSelection() } - v.Buf.Insert(v.Cursor.Loc(), " ") + v.Buf.Insert(v.Cursor.Loc, " ") v.Cursor.Right() return true } @@ -618,12 +610,12 @@ func (v *View) InsertEnter() bool { v.Cursor.ResetSelection() } - v.Buf.Insert(v.Cursor.Loc(), "\n") - ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.Y]) + v.Buf.Insert(v.Cursor.Loc, "\n") + ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y)) v.Cursor.Right() if settings["autoindent"].(bool) { - v.Buf.Insert(v.Cursor.Loc(), ws) + v.Buf.Insert(v.Cursor.Loc, ws) for i := 0; i < len(ws); i++ { v.Cursor.Right() } @@ -638,7 +630,7 @@ func (v *View) Backspace() bool { if v.Cursor.HasSelection() { v.Cursor.DeleteSelection() v.Cursor.ResetSelection() - } else if v.Cursor.Loc() > 0 { + } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) { // We have to do something a bit hacky here because we want to // delete the line by first moving left and then deleting backwards // but the undo redo would place the cursor in the wrong place @@ -648,21 +640,21 @@ func (v *View) Backspace() bool { // If the user is using spaces instead of tabs and they are deleting // whitespace at the start of the line, we should delete as if its a // tab (tabSize number of spaces) - lineStart := v.Buf.Lines[v.Cursor.Y][:v.Cursor.X] + lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X] tabSize := int(settings["tabsize"].(float64)) if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 { - loc := v.Cursor.Loc() - v.Cursor.SetLoc(loc - tabSize) + loc := v.Cursor.Loc + v.Cursor.Loc = loc.Move(-tabSize, v.Buf) cx, cy := v.Cursor.X, v.Cursor.Y - v.Cursor.SetLoc(loc) - v.Buf.Remove(loc-tabSize, loc) + v.Cursor.Loc = loc + v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc) v.Cursor.X, v.Cursor.Y = cx, cy } else { v.Cursor.Left() cx, cy := v.Cursor.X, v.Cursor.Y v.Cursor.Right() - loc := v.Cursor.Loc() - v.Buf.Remove(loc-1, loc) + loc := v.Cursor.Loc + v.Buf.Remove(loc.Move(-1, v.Buf), loc) v.Cursor.X, v.Cursor.Y = cx, cy } } @@ -696,9 +688,9 @@ func (v *View) Delete() bool { v.Cursor.DeleteSelection() v.Cursor.ResetSelection() } else { - loc := v.Cursor.Loc() - if loc < v.Buf.Len() { - v.Buf.Remove(loc, loc+1) + loc := v.Cursor.Loc + if loc.LessThan(v.Buf.End()) { + v.Buf.Remove(loc, loc.Move(1, v.Buf)) } } return true @@ -713,12 +705,12 @@ func (v *View) InsertTab() bool { } if settings["tabstospaces"].(bool) { tabSize := int(settings["tabsize"].(float64)) - v.Buf.Insert(v.Cursor.Loc(), Spaces(tabSize)) + v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize)) for i := 0; i < tabSize; i++ { v.Cursor.Right() } } else { - v.Buf.Insert(v.Cursor.Loc(), "\t") + v.Buf.Insert(v.Cursor.Loc, "\t") v.Cursor.Right() } return true @@ -765,9 +757,9 @@ func (v *View) Save() bool { // Find opens a prompt and searches forward for the input func (v *View) Find() bool { if v.Cursor.HasSelection() { - searchStart = v.Cursor.CurSelection[1] + searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf) } else { - searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf) + searchStart = ToCharPos(v.Cursor.Loc, v.Buf) } BeginSearch() return true @@ -776,9 +768,9 @@ func (v *View) Find() bool { // FindNext searches forwards for the last used search term func (v *View) FindNext() bool { if v.Cursor.HasSelection() { - searchStart = v.Cursor.CurSelection[1] + searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf) } else { - searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf) + searchStart = ToCharPos(v.Cursor.Loc, v.Buf) } messenger.Message("Finding: " + lastSearch) Search(lastSearch, v, true) @@ -788,9 +780,9 @@ func (v *View) FindNext() bool { // FindPrevious searches backwards for the last used search term func (v *View) FindPrevious() bool { if v.Cursor.HasSelection() { - searchStart = v.Cursor.CurSelection[0] + searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf) } else { - searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf) + searchStart = ToCharPos(v.Cursor.Loc, v.Buf) } messenger.Message("Finding: " + lastSearch) Search(lastSearch, v, false) @@ -861,7 +853,7 @@ func (v *View) Cut() bool { // DuplicateLine duplicates the current line func (v *View) DuplicateLine() bool { v.Cursor.End() - v.Buf.Insert(v.Cursor.Loc(), "\n"+v.Buf.Lines[v.Cursor.Y]) + v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y)) v.Cursor.Right() messenger.Message("Duplicated line") return true @@ -875,8 +867,8 @@ func (v *View) Paste() bool { v.Cursor.ResetSelection() } clip, _ := clipboard.ReadAll() - v.Buf.Insert(v.Cursor.Loc(), clip) - v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip)) + v.Buf.Insert(v.Cursor.Loc, clip) + v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf) v.freshClip = false messenger.Message("Pasted clipboard") return true @@ -884,8 +876,8 @@ func (v *View) Paste() bool { // SelectAll selects the entire buffer func (v *View) SelectAll() bool { - v.Cursor.CurSelection[0] = 0 - v.Cursor.CurSelection[1] = v.Buf.Len() + v.Cursor.CurSelection[0] = v.Buf.Start() + v.Cursor.CurSelection[1] = v.Buf.End() // Put the cursor at the beginning v.Cursor.X = 0 v.Cursor.Y = 0 @@ -907,7 +899,7 @@ func (v *View) OpenFile() bool { messenger.Error(err.Error()) return true } - buf := NewBuffer(string(file), filename) + buf := NewBuffer(file, filename) v.OpenBuffer(buf) } return true @@ -952,7 +944,7 @@ func (v *View) PageDown() bool { // CursorPageUp places the cursor a page up func (v *View) CursorPageUp() bool { if v.Cursor.HasSelection() { - v.Cursor.SetLoc(v.Cursor.CurSelection[0]) + v.Cursor.Loc = v.Cursor.CurSelection[0] v.Cursor.ResetSelection() } v.Cursor.UpN(v.height) @@ -962,7 +954,7 @@ func (v *View) CursorPageUp() bool { // CursorPageDown places the cursor a page up func (v *View) CursorPageDown() bool { if v.Cursor.HasSelection() { - v.Cursor.SetLoc(v.Cursor.CurSelection[1]) + v.Cursor.Loc = v.Cursor.CurSelection[1] v.Cursor.ResetSelection() } v.Cursor.DownN(v.height) @@ -1036,7 +1028,7 @@ func (v *View) ClearStatus() bool { func (v *View) ToggleHelp() bool { if !v.helpOpen { v.lastBuffer = v.Buf - helpBuffer := NewBuffer(helpTxt, "help.md") + helpBuffer := NewBuffer([]byte(helpTxt), "help.md") helpBuffer.Name = "Help" v.helpOpen = true v.OpenBuffer(helpBuffer) diff --git a/cmd/micro/buffer.go b/cmd/micro/buffer.go index a9451e93..6b908f9c 100644 --- a/cmd/micro/buffer.go +++ b/cmd/micro/buffer.go @@ -8,10 +8,8 @@ import ( "os/exec" "os/signal" "path/filepath" - "strings" "time" - - "github.com/vinzmay/go-rope" + "unicode/utf8" ) // Buffer stores the text for files that are loaded into the text editor @@ -20,9 +18,7 @@ import ( type Buffer struct { // The eventhandler for undo/redo *EventHandler - - // Stores the text of the buffer - r *rope.Rope + *LineArray Cursor Cursor @@ -36,10 +32,6 @@ type Buffer struct { // Stores the last modification time of the file the buffer is pointing to ModTime time.Time - // Provide efficient and easy access to text and lines so the rope String does not - // need to be constantly recalculated - // These variables are updated in the update() function - Lines []string NumLines int // Syntax highlighting rules @@ -56,13 +48,9 @@ type SerializedBuffer struct { } // NewBuffer creates a new buffer from `txt` with path and name `path` -func NewBuffer(txt, path string) *Buffer { +func NewBuffer(txt []byte, path string) *Buffer { b := new(Buffer) - if txt == "" { - b.r = new(rope.Rope) - } else { - b.r = rope.New(txt) - } + b.LineArray = NewLineArray(txt) b.Path = path b.Name = path @@ -79,8 +67,10 @@ func NewBuffer(txt, path string) *Buffer { // Put the cursor at the first spot b.Cursor = Cursor{ - X: 0, - Y: 0, + Loc: Loc{ + X: 0, + Y: 0, + }, buf: b, } @@ -121,13 +111,6 @@ func (b *Buffer) UpdateRules() { b.rules, b.FileType = GetRules(b) } -func (b *Buffer) String() string { - if b.r.Len() != 0 { - return b.r.String() - } - return "" -} - // CheckModTime makes sure that the file this buffer points to hasn't been updated // by an external program since it was last read // If it has, we ask the user if they would like to reload the file @@ -168,8 +151,7 @@ func (b *Buffer) ReOpen() { // Update fetches the string from the rope and updates the `text` and `lines` in the buffer func (b *Buffer) Update() { - b.Lines = strings.Split(b.String(), "\n") - b.NumLines = len(b.Lines) + b.NumLines = len(b.lines) } // Save saves the buffer to its default path @@ -268,42 +250,44 @@ func (b *Buffer) SaveAsWithSudo(filename string) error { return err } -// This directly inserts value at idx, bypassing all undo/redo -func (b *Buffer) insert(idx int, value string) { +func (b *Buffer) insert(pos Loc, value []byte) { b.IsModified = true - b.r = b.r.Insert(idx, value) + b.LineArray.insert(pos, value) b.Update() } - -// Remove a slice of the rope from start to end (exclusive) -// Returns the string that was removed -// This directly removes from start to end from the buffer, bypassing all undo/redo -func (b *Buffer) remove(start, end int) string { +func (b *Buffer) remove(start, end Loc) string { b.IsModified = true - if start < 0 { - start = 0 - } - if end > b.Len() { - end = b.Len() - } - if start == end { - return "" - } - removed := b.Substr(start, end) - // The rope implenentation I am using wants indicies starting at 1 instead of 0 - start++ - end++ - b.r = b.r.Delete(start, end-start) + sub := b.LineArray.remove(start, end) b.Update() - return removed + return sub } -// Substr returns the substring of the rope from start to end -func (b *Buffer) Substr(start, end int) string { - return b.r.Substr(start+1, end-start).String() +// Start returns the location of the first character in the buffer +func (b *Buffer) Start() Loc { + return Loc{0, 0} +} + +// End returns the location of the last character in the buffer +func (b *Buffer) End() Loc { + return Loc{utf8.RuneCount(b.lines[len(b.lines)-1]), b.NumLines - 1} +} + +// Line returns a single line +func (b *Buffer) Line(n int) string { + return string(b.lines[n]) +} + +// Lines returns an array of strings containing the lines from start to end +func (b *Buffer) Lines(start, end int) []string { + lines := b.lines[start:end] + var slice []string + for _, line := range lines { + slice = append(slice, string(line)) + } + return slice } // Len gives the length of the buffer func (b *Buffer) Len() int { - return b.r.Len() + return Count(b.String()) } diff --git a/cmd/micro/command.go b/cmd/micro/command.go index 04a9ad79..e2a60749 100644 --- a/cmd/micro/command.go +++ b/cmd/micro/command.go @@ -158,7 +158,7 @@ func Replace(args []string) { choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)") if canceled { if view.Cursor.HasSelection() { - view.Cursor.SetLoc(view.Cursor.CurSelection[0]) + view.Cursor.Loc = view.Cursor.CurSelection[0] view.Cursor.ResetSelection() } messenger.Reset() @@ -166,19 +166,19 @@ func Replace(args []string) { } if choice { view.Cursor.DeleteSelection() - view.Buf.Insert(match[0], replace) + view.Buf.Insert(FromCharPos(match[0], view.Buf), replace) view.Cursor.ResetSelection() messenger.Reset() } else { if view.Cursor.HasSelection() { - searchStart = view.Cursor.CurSelection[1] + searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf) } else { - searchStart = ToCharPos(view.Cursor.X, view.Cursor.Y, view.Buf) + searchStart = ToCharPos(view.Cursor.Loc, view.Buf) } continue } } else { - view.Buf.Replace(match[0], match[1], replace) + view.Buf.Replace(FromCharPos(match[0], view.Buf), FromCharPos(match[1], view.Buf), replace) } } if !found { diff --git a/cmd/micro/cursor.go b/cmd/micro/cursor.go index 19f36dfc..2caffae6 100644 --- a/cmd/micro/cursor.go +++ b/cmd/micro/cursor.go @@ -1,40 +1,5 @@ package main -// FromCharPos converts from a character position to an x, y position -func FromCharPos(loc int, buf *Buffer) (int, int) { - return FromCharPosStart(0, 0, 0, loc, buf) -} - -// FromCharPosStart converts from a character position to an x, y position, starting at the specified character location -func FromCharPosStart(startLoc, startX, startY, loc int, buf *Buffer) (int, int) { - charNum := startLoc - x, y := startX, startY - - lineLen := Count(buf.Lines[y]) + 1 - for charNum+lineLen <= loc { - charNum += lineLen - y++ - if y >= buf.NumLines { - return 0, 0 - } - lineLen = Count(buf.Lines[y]) + 1 - } - x = loc - charNum - - return x, y -} - -// ToCharPos converts from an x, y position to a character position -func ToCharPos(x, y int, buf *Buffer) int { - loc := 0 - for i := 0; i < y; i++ { - // + 1 for the newline - loc += Count(buf.Lines[i]) + 1 - } - loc += x - return loc -} - // The Cursor struct stores the location of the cursor in the view // The complicated part about the cursor is storing its location. // The cursor must be displayed at an x, y location, but since the buffer @@ -43,20 +8,17 @@ func ToCharPos(x, y int, buf *Buffer) int { // selection. type Cursor struct { buf *Buffer - - // The cursor display location - X int - Y int + Loc // Last cursor x position LastVisualX int // The current selection as a range of character numbers (inclusive) - CurSelection [2]int + CurSelection [2]Loc // The original selection as a range of character numbers // This is used for line and word selection where it is necessary // to know what the original selection was - OrigSelection [2]int + OrigSelection [2]Loc } // Goto puts the cursor at the given cursor's location and gives the current cursor its selection too @@ -65,25 +27,10 @@ func (c *Cursor) Goto(b Cursor) { c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection } -// SetLoc sets the location of the cursor in terms of character number -// and not x, y location -// It's just a simple wrapper of FromCharPos -func (c *Cursor) SetLoc(loc int) { - c.X, c.Y = FromCharPos(loc, c.buf) - c.LastVisualX = c.GetVisualX() -} - -// Loc gets the cursor location in terms of character number instead -// of x, y location -// It's just a simple wrapper of ToCharPos -func (c *Cursor) Loc() int { - return ToCharPos(c.X, c.Y, c.buf) -} - // ResetSelection resets the user's selection func (c *Cursor) ResetSelection() { - c.CurSelection[0] = 0 - c.CurSelection[1] = 0 + c.CurSelection[0] = c.buf.Start() + c.CurSelection[1] = c.buf.Start() } // HasSelection returns whether or not the user has selected anything @@ -93,20 +40,20 @@ func (c *Cursor) HasSelection() bool { // DeleteSelection deletes the currently selected text func (c *Cursor) DeleteSelection() { - if c.CurSelection[0] > c.CurSelection[1] { + if c.CurSelection[0].GreaterThan(c.CurSelection[1]) { c.buf.Remove(c.CurSelection[1], c.CurSelection[0]) - c.SetLoc(c.CurSelection[1]) + c.Loc = c.CurSelection[1] } else if c.GetSelection() == "" { return } else { c.buf.Remove(c.CurSelection[0], c.CurSelection[1]) - c.SetLoc(c.CurSelection[0]) + c.Loc = c.CurSelection[0] } } // GetSelection returns the cursor's selection func (c *Cursor) GetSelection() string { - if c.CurSelection[0] > c.CurSelection[1] { + if c.CurSelection[0].GreaterThan(c.CurSelection[1]) { return c.buf.Substr(c.CurSelection[1], c.CurSelection[0]) } return c.buf.Substr(c.CurSelection[0], c.CurSelection[1]) @@ -115,12 +62,12 @@ func (c *Cursor) GetSelection() string { // SelectLine selects the current line func (c *Cursor) SelectLine() { c.Start() - c.CurSelection[0] = c.Loc() + c.CurSelection[0] = c.Loc c.End() if c.buf.NumLines-1 > c.Y { - c.CurSelection[1] = c.Loc() + 1 + c.CurSelection[1] = c.Loc.Move(1, c.buf) } else { - c.CurSelection[1] = c.Loc() + c.CurSelection[1] = c.Loc } c.OrigSelection = c.CurSelection @@ -128,34 +75,31 @@ func (c *Cursor) SelectLine() { // AddLineToSelection adds the current line to the selection func (c *Cursor) AddLineToSelection() { - loc := c.Loc() - - if loc < c.OrigSelection[0] { + if c.Loc.LessThan(c.OrigSelection[0]) { c.Start() - c.CurSelection[0] = c.Loc() + c.CurSelection[0] = c.Loc c.CurSelection[1] = c.OrigSelection[1] } - if loc > c.OrigSelection[1] { + if c.Loc.GreaterThan(c.OrigSelection[1]) { c.End() - c.CurSelection[1] = c.Loc() + 1 + c.CurSelection[1] = c.Loc.Move(1, c.buf) c.CurSelection[0] = c.OrigSelection[0] } - if loc < c.OrigSelection[1] && loc > c.OrigSelection[0] { + if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) { c.CurSelection = c.OrigSelection } } // SelectWord selects the word the cursor is currently on func (c *Cursor) SelectWord() { - if len(c.buf.Lines[c.Y]) == 0 { + if len(c.buf.Line(c.Y)) == 0 { return } if !IsWordChar(string(c.RuneUnder(c.X))) { - loc := c.Loc() - c.CurSelection[0] = loc - c.CurSelection[1] = loc + 1 + c.CurSelection[0] = c.Loc + c.CurSelection[1] = c.Loc.Move(1, c.buf) c.OrigSelection = c.CurSelection return } @@ -166,55 +110,53 @@ func (c *Cursor) SelectWord() { backward-- } - c.CurSelection[0] = ToCharPos(backward, c.Y, c.buf) + c.CurSelection[0] = Loc{backward, c.Y} c.OrigSelection[0] = c.CurSelection[0] - for forward < Count(c.buf.Lines[c.Y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) { + for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) { forward++ } - c.CurSelection[1] = ToCharPos(forward, c.Y, c.buf) + 1 + c.CurSelection[1] = Loc{forward, c.Y}.Move(1, c.buf) c.OrigSelection[1] = c.CurSelection[1] - c.SetLoc(c.CurSelection[1]) + c.Loc = c.CurSelection[1] } // AddWordToSelection adds the word the cursor is currently on to the selection func (c *Cursor) AddWordToSelection() { - loc := c.Loc() - - if loc > c.OrigSelection[0] && loc < c.OrigSelection[1] { + if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) { c.CurSelection = c.OrigSelection return } - if loc < c.OrigSelection[0] { + if c.Loc.LessThan(c.OrigSelection[0]) { backward := c.X for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) { backward-- } - c.CurSelection[0] = ToCharPos(backward, c.Y, c.buf) + c.CurSelection[0] = Loc{backward, c.Y} c.CurSelection[1] = c.OrigSelection[1] } - if loc > c.OrigSelection[1] { + if c.Loc.GreaterThan(c.OrigSelection[1]) { forward := c.X - for forward < Count(c.buf.Lines[c.Y])-1 && IsWordChar(string(c.RuneUnder(forward+1))) { + for forward < Count(c.buf.Line(c.Y))-1 && IsWordChar(string(c.RuneUnder(forward+1))) { forward++ } - c.CurSelection[1] = ToCharPos(forward, c.Y, c.buf) + 1 + c.CurSelection[1] = Loc{forward, c.Y}.Move(1, c.buf) c.CurSelection[0] = c.OrigSelection[0] } - c.SetLoc(c.CurSelection[1]) + c.Loc = c.CurSelection[1] } // SelectTo selects from the current cursor location to the given location -func (c *Cursor) SelectTo(loc int) { - if loc > c.OrigSelection[0] { +func (c *Cursor) SelectTo(loc Loc) { + if loc.GreaterThan(c.OrigSelection[0]) { c.CurSelection[0] = c.OrigSelection[0] c.CurSelection[1] = loc } else { @@ -227,13 +169,13 @@ func (c *Cursor) SelectTo(loc int) { func (c *Cursor) WordRight() { c.Right() for IsWhitespace(c.RuneUnder(c.X)) { - if c.X == Count(c.buf.Lines[c.Y]) { + if c.X == Count(c.buf.Line(c.Y)) { return } c.Right() } for !IsWhitespace(c.RuneUnder(c.X)) { - if c.X == Count(c.buf.Lines[c.Y]) { + if c.X == Count(c.buf.Line(c.Y)) { return } c.Right() @@ -260,7 +202,7 @@ func (c *Cursor) WordLeft() { // RuneUnder returns the rune under the given x position func (c *Cursor) RuneUnder(x int) rune { - line := []rune(c.buf.Lines[c.Y]) + line := []rune(c.buf.Line(c.Y)) if len(line) == 0 { return '\n' } @@ -285,7 +227,7 @@ func (c *Cursor) UpN(amount int) { } c.Y = proposedY - runes := []rune(c.buf.Lines[c.Y]) + runes := []rune(c.buf.Line(c.Y)) c.X = c.GetCharPosInLine(c.Y, c.LastVisualX) if c.X > len(runes) { c.X = len(runes) @@ -309,7 +251,7 @@ func (c *Cursor) Down() { // Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning func (c *Cursor) Left() { - if c.Loc() == 0 { + if c.Loc == c.buf.Start() { return } if c.X > 0 { @@ -323,11 +265,10 @@ func (c *Cursor) Left() { // Right moves the cursor right one cell (if possible) or to the next line if it is at the end func (c *Cursor) Right() { - if c.Loc() == c.buf.Len() { + if c.Loc == c.buf.End() { return } - // TermMessage(Count(c.buf.Lines[c.Y])) - if c.X < Count(c.buf.Lines[c.Y]) { + if c.X < Count(c.buf.Line(c.Y)) { c.X++ } else { c.Down() @@ -338,7 +279,7 @@ func (c *Cursor) Right() { // End moves the cursor to the end of the line it is on func (c *Cursor) End() { - c.X = Count(c.buf.Lines[c.Y]) + c.X = Count(c.buf.Line(c.Y)) c.LastVisualX = c.GetVisualX() } @@ -353,11 +294,11 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int { // Get the tab size tabSize := int(settings["tabsize"].(float64)) // This is the visual line -- every \t replaced with the correct number of spaces - visualLineLen := StringWidth(c.buf.Lines[lineNum]) + visualLineLen := StringWidth(c.buf.Line(lineNum)) if visualPos > visualLineLen { visualPos = visualLineLen } - width := WidthOfLargeRunes(c.buf.Lines[lineNum]) + width := WidthOfLargeRunes(c.buf.Line(lineNum)) if visualPos >= width { return visualPos - width } @@ -366,7 +307,7 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int { // GetVisualX returns the x value of the cursor in visual spaces func (c *Cursor) GetVisualX() int { - runes := []rune(c.buf.Lines[c.Y]) + runes := []rune(c.buf.Line(c.Y)) return StringWidth(string(runes[:c.X])) } @@ -381,7 +322,7 @@ func (c *Cursor) Relocate() { if c.X < 0 { c.X = 0 - } else if c.X > Count(c.buf.Lines[c.Y]) { - c.X = Count(c.buf.Lines[c.Y]) + } else if c.X > Count(c.buf.Line(c.Y)) { + c.X = Count(c.buf.Line(c.Y)) } } diff --git a/cmd/micro/eventhandler.go b/cmd/micro/eventhandler.go index 75ceb8b9..fee42b5a 100644 --- a/cmd/micro/eventhandler.go +++ b/cmd/micro/eventhandler.go @@ -21,15 +21,15 @@ type TextEvent struct { EventType int Text string - Start int - End int + Start Loc + End Loc Time time.Time } // ExecuteTextEvent runs a text event func ExecuteTextEvent(t *TextEvent, buf *Buffer) { if t.EventType == TextEventInsert { - buf.insert(t.Start, t.Text) + buf.insert(t.Start, []byte(t.Text)) } else if t.EventType == TextEventRemove { t.Text = buf.remove(t.Start, t.End) } @@ -64,32 +64,32 @@ func NewEventHandler(buf *Buffer) *EventHandler { func (eh *EventHandler) ApplyDiff(new string) { differ := dmp.New() diff := differ.DiffMain(eh.buf.String(), new, false) - var charNum int + loc := eh.buf.Start() for _, d := range diff { if d.Type == dmp.DiffInsert { - eh.Insert(charNum, d.Text) + eh.Insert(loc, d.Text) } else if d.Type == dmp.DiffDelete { - eh.Remove(charNum, charNum+Count(d.Text)) + eh.Remove(loc, loc.Move(Count(d.Text), eh.buf)) } - charNum += Count(d.Text) + loc = loc.Move(Count(d.Text), eh.buf) } } // Insert creates an insert text event and executes it -func (eh *EventHandler) Insert(start int, text string) { +func (eh *EventHandler) Insert(start Loc, text string) { e := &TextEvent{ C: eh.buf.Cursor, EventType: TextEventInsert, Text: text, Start: start, - End: start + Count(text), + End: start.Move(Count(text), eh.buf), Time: time.Now(), } eh.Execute(e) } // Remove creates a remove text event and executes it -func (eh *EventHandler) Remove(start, end int) { +func (eh *EventHandler) Remove(start, end Loc) { e := &TextEvent{ C: eh.buf.Cursor, EventType: TextEventRemove, @@ -101,7 +101,7 @@ func (eh *EventHandler) Remove(start, end int) { } // Replace deletes from start to end and replaces it with the given string -func (eh *EventHandler) Replace(start, end int, replace string) { +func (eh *EventHandler) Replace(start, end Loc, replace string) { eh.Remove(start, end) eh.Insert(start, replace) } diff --git a/cmd/micro/highlighter.go b/cmd/micro/highlighter.go index d1d46a16..956de1d7 100644 --- a/cmd/micro/highlighter.go +++ b/cmd/micro/highlighter.go @@ -366,7 +366,7 @@ func GetRules(buf *Buffer) ([]SyntaxRule, string) { if r[0] != nil && r[0].MatchString(buf.Path) { // Check if the syntax statement matches the extension return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype - } else if r[1] != nil && r[1].MatchString(buf.Lines[0]) { + } else if r[1] != nil && r[1].MatchString(buf.Line(0)) { // Check if the header statement matches the first line return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype } @@ -390,7 +390,7 @@ func Match(v *View) SyntaxMatches { viewEnd = buf.NumLines } - lines := buf.Lines[viewStart:viewEnd] + lines := buf.Lines(viewStart, viewEnd) matches := make(SyntaxMatches, len(lines)) for i, line := range lines { @@ -407,10 +407,10 @@ func Match(v *View) SyntaxMatches { totalEnd = buf.NumLines } - str := strings.Join(buf.Lines[totalStart:totalEnd], "\n") - startNum := ToCharPos(0, totalStart, v.Buf) + str := strings.Join(buf.Lines(totalStart, totalEnd), "\n") + startNum := ToCharPos(Loc{0, totalStart}, v.Buf) - toplineNum := ToCharPos(0, v.Topline, v.Buf) + toplineNum := ToCharPos(Loc{0, v.Topline}, v.Buf) for _, rule := range rules { if rule.startend { @@ -422,7 +422,8 @@ func Match(v *View) SyntaxMatches { if i < toplineNum { continue } - colNum, lineNum := FromCharPosStart(toplineNum, 0, v.Topline, i, buf) + loc := FromCharPos(i, buf) + colNum, lineNum := loc.X, loc.Y if lineNum == -1 || colNum == -1 { continue } diff --git a/cmd/micro/lineArray.go b/cmd/micro/lineArray.go new file mode 100644 index 00000000..6b5746ec --- /dev/null +++ b/cmd/micro/lineArray.go @@ -0,0 +1,133 @@ +package main + +import ( + "bytes" + "unicode/utf8" +) + +func runeToByteIndex(n int, txt []byte) int { + if n == 0 { + return 0 + } + + count := 0 + i := 0 + for len(txt) > 0 { + _, size := utf8.DecodeRune(txt) + + txt = txt[size:] + count += size + i++ + + if i == n { + break + } + } + return count +} + +type LineArray struct { + lines [][]byte +} + +func NewLineArray(text []byte) *LineArray { + la := new(LineArray) + split := bytes.Split(text, []byte("\n")) + la.lines = make([][]byte, len(split)) + for i := range split { + la.lines[i] = make([]byte, len(split[i])) + copy(la.lines[i], split[i]) + } + + return la +} + +func (la *LineArray) String() string { + return string(bytes.Join(la.lines, []byte("\n"))) +} + +func (la *LineArray) NewlineBelow(y int) { + la.lines = append(la.lines, []byte(" ")) + copy(la.lines[y+2:], la.lines[y+1:]) + la.lines[y+1] = []byte("") +} + +func (la *LineArray) insert(pos Loc, value []byte) { + x, y := runeToByteIndex(pos.X, la.lines[pos.Y]), pos.Y + // x, y := pos.x, pos.y + for i := 0; i < len(value); i++ { + if value[i] == '\n' { + la.Split(Loc{x, y}) + x = 0 + y++ + continue + } + la.insertByte(Loc{x, y}, value[i]) + x++ + } +} + +func (la *LineArray) insertByte(pos Loc, value byte) { + la.lines[pos.Y] = append(la.lines[pos.Y], 0) + copy(la.lines[pos.Y][pos.X+1:], la.lines[pos.Y][pos.X:]) + la.lines[pos.Y][pos.X] = value +} + +func (la *LineArray) JoinLines(a, b int) { + la.insert(Loc{len(la.lines[a]), a}, la.lines[b]) + la.DeleteLine(b) +} + +func (la *LineArray) Split(pos Loc) { + la.NewlineBelow(pos.Y) + la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y][pos.X:]) + la.DeleteToEnd(Loc{pos.X, pos.Y}) +} + +func (la *LineArray) remove(start, end Loc) string { + sub := la.Substr(start, end) + startX := runeToByteIndex(start.X, la.lines[start.Y]) + endX := runeToByteIndex(end.X, la.lines[end.Y]) + if start.Y == end.Y { + la.lines[start.Y] = append(la.lines[start.Y][:startX], la.lines[start.Y][endX:]...) + } else { + for i := start.Y + 1; i <= end.Y-1; i++ { + la.DeleteLine(i) + } + la.DeleteToEnd(Loc{startX, start.Y}) + la.DeleteFromStart(Loc{endX - 1, start.Y + 1}) + la.JoinLines(start.Y, start.Y+1) + } + return sub +} + +func (la *LineArray) DeleteToEnd(pos Loc) { + la.lines[pos.Y] = la.lines[pos.Y][:pos.X] +} + +func (la *LineArray) DeleteFromStart(pos Loc) { + la.lines[pos.Y] = la.lines[pos.Y][pos.X+1:] +} + +func (la *LineArray) DeleteLine(y int) { + la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])] +} + +func (la *LineArray) DeleteByte(pos Loc) { + la.lines[pos.Y] = la.lines[pos.Y][:pos.X+copy(la.lines[pos.Y][pos.X:], la.lines[pos.Y][pos.X+1:])] +} + +func (la *LineArray) Substr(start, end Loc) string { + startX := runeToByteIndex(start.X, la.lines[start.Y]) + endX := runeToByteIndex(end.X, la.lines[end.Y]) + if start.Y == end.Y { + return string(la.lines[start.Y][startX:endX]) + } + var str string + str += string(la.lines[start.Y][startX:]) + "\n" + for i := start.Y + 1; i <= end.Y-1; i++ { + str += string(la.lines[i]) + "\n" + } + str += string(la.lines[end.Y][:endX]) + return str +} diff --git a/cmd/micro/loc.go b/cmd/micro/loc.go new file mode 100644 index 00000000..f5d7d6b3 --- /dev/null +++ b/cmd/micro/loc.go @@ -0,0 +1,120 @@ +package main + +// FromCharPos converts from a character position to an x, y position +func FromCharPos(loc int, buf *Buffer) Loc { + charNum := 0 + x, y := 0, 0 + + lineLen := Count(buf.Line(y)) + 1 + for charNum+lineLen <= loc { + charNum += lineLen + y++ + lineLen = Count(buf.Line(y)) + 1 + } + x = loc - charNum + + return Loc{x, y} +} + +// ToCharPos converts from an x, y position to a character position +func ToCharPos(start Loc, buf *Buffer) int { + x, y := start.X, start.Y + loc := 0 + for i := 0; i < y; i++ { + // + 1 for the newline + loc += Count(buf.Line(i)) + 1 + } + loc += x + return loc +} + +// Loc stores a location +type Loc struct { + X, Y int +} + +// LessThan returns true if b is smaller +func (l Loc) LessThan(b Loc) bool { + if l.Y < b.Y { + return true + } + if l.Y == b.Y && l.X < b.X { + return true + } + return false +} + +// GreaterThan returns true if b is bigger +func (l Loc) GreaterThan(b Loc) bool { + if l.Y > b.Y { + return true + } + if l.Y == b.Y && l.X > b.X { + return true + } + return false +} + +// GreaterEqual returns true if b is greater than or equal to b +func (l Loc) GreaterEqual(b Loc) bool { + if l.Y > b.Y { + return true + } + if l.Y == b.Y && l.X > b.X { + return true + } + if l == b { + return true + } + return false +} + +// LessEqual returns true if b is less than or equal to b +func (l Loc) LessEqual(b Loc) bool { + if l.Y < b.Y { + return true + } + if l.Y == b.Y && l.X < b.X { + return true + } + if l == b { + return true + } + return false +} + +func (l Loc) right(n int, buf *Buffer) Loc { + if l == buf.End() { + return l + } + var res Loc + if l.X < Count(buf.Line(l.Y)) { + res = Loc{l.X + 1, l.Y} + } else { + res = Loc{0, l.Y + 1} + } + return res +} +func (l Loc) left(n int, buf *Buffer) Loc { + if l == buf.Start() { + return l + } + var res Loc + if l.X > 0 { + res = Loc{l.X - 1, l.Y} + } else { + res = Loc{Count(buf.Line(l.Y - 1)), l.Y - 1} + } + return res +} + +func (l Loc) Move(n int, buf *Buffer) Loc { + if n > 0 { + return l.right(n, buf) + } + return l.left(Abs(n), buf) +} + +// func (l Loc) DistanceTo(b Loc, buf *Buffer) int { +// +// } diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 03807f7e..e9ed08f4 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -210,7 +210,7 @@ func main() { // Load the help files LoadHelp() - buf := NewBuffer(string(input), filename) + buf := NewBuffer(input, filename) InitScreen() diff --git a/cmd/micro/search.go b/cmd/micro/search.go index bef2936e..ddf77aef 100644 --- a/cmd/micro/search.go +++ b/cmd/micro/search.go @@ -125,9 +125,9 @@ func Search(searchStr string, v *View, down bool) { return } - v.Cursor.CurSelection[0] = charPos + runePos(match[0], str) - v.Cursor.CurSelection[1] = charPos + runePos(match[1], str) - v.Cursor.X, v.Cursor.Y = FromCharPos(charPos+match[1]-1, v.Buf) + v.Cursor.CurSelection[0] = FromCharPos(charPos+runePos(match[0], str), v.Buf) + v.Cursor.CurSelection[1] = FromCharPos(charPos+runePos(match[1], str), v.Buf) + v.Cursor.Loc = FromCharPos(charPos+match[1]-1, v.Buf) if v.Relocate() { v.matches = Match(v) } diff --git a/cmd/micro/util.go b/cmd/micro/util.go index 46da66da..db452e06 100644 --- a/cmd/micro/util.go +++ b/cmd/micro/util.go @@ -165,3 +165,11 @@ func WidthOfLargeRunes(str string) int { func runePos(p int, str string) int { return utf8.RuneCountInString(str[:p]) } + +// Abs is a simple absolute value function for ints +func Abs(n int) int { + if n < 0 { + return -n + } + return n +} diff --git a/cmd/micro/view.go b/cmd/micro/view.go index 0940acc9..ca1ad2ca 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -251,8 +251,8 @@ func (v *View) MoveToMouseClick(x, y int) { } x = v.Cursor.GetCharPosInLine(y, x) - if x > Count(v.Buf.Lines[y]) { - x = Count(v.Buf.Lines[y]) + if x > Count(v.Buf.Line(y)) { + x = Count(v.Buf.Line(y)) } v.Cursor.X = x v.Cursor.Y = y @@ -278,7 +278,7 @@ func (v *View) HandleEvent(event tcell.Event) { v.Cursor.DeleteSelection() v.Cursor.ResetSelection() } - v.Buf.Insert(v.Cursor.Loc(), string(e.Rune())) + v.Buf.Insert(v.Cursor.Loc, string(e.Rune())) v.Cursor.Right() } else { for key, actions := range bindings { @@ -310,8 +310,8 @@ func (v *View) HandleEvent(event tcell.Event) { v.Cursor.ResetSelection() } clip := e.Text() - v.Buf.Insert(v.Cursor.Loc(), clip) - v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip)) + v.Buf.Insert(v.Cursor.Loc, clip) + v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf) v.freshClip = false case *tcell.EventMouse: x, y := e.Position() @@ -350,10 +350,9 @@ func (v *View) HandleEvent(event tcell.Event) { v.tripleClick = false v.lastClickTime = time.Now() - loc := v.Cursor.Loc() - v.Cursor.OrigSelection[0] = loc - v.Cursor.CurSelection[0] = loc - v.Cursor.CurSelection[1] = loc + v.Cursor.OrigSelection[0] = v.Cursor.Loc + v.Cursor.CurSelection[0] = v.Cursor.Loc + v.Cursor.CurSelection[1] = v.Cursor.Loc } v.mouseReleased = false } else if !v.mouseReleased { @@ -363,7 +362,7 @@ func (v *View) HandleEvent(event tcell.Event) { } else if v.doubleClick { v.Cursor.AddWordToSelection() } else { - v.Cursor.CurSelection[1] = v.Cursor.Loc() + v.Cursor.CurSelection[1] = v.Cursor.Loc } } case tcell.ButtonNone: @@ -379,7 +378,7 @@ func (v *View) HandleEvent(event tcell.Event) { if !v.doubleClick && !v.tripleClick { v.MoveToMouseClick(x, y) - v.Cursor.CurSelection[1] = v.Cursor.Loc() + v.Cursor.CurSelection[1] = v.Cursor.Loc } v.mouseReleased = true } @@ -436,7 +435,7 @@ func (v *View) ClearAllGutterMessages() { // DisplayView renders the view to the screen func (v *View) DisplayView() { // The character number of the character in the top left of the screen - charNum := ToCharPos(0, v.Topline, v.Buf) + charNum := Loc{0, v.Topline} // Convert the length of buffer to a string, and get the length of the string // We are going to have to offset by that amount @@ -470,7 +469,7 @@ func (v *View) DisplayView() { continue } - line := v.Buf.Lines[lineN+v.Topline] + line := v.Buf.Line(lineN + v.Topline) if hasGutterMessages { msgOnLine := false @@ -551,8 +550,8 @@ func (v *View) DisplayView() { } if v.Cursor.HasSelection() && - (charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] || - charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) { + (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) || + charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) { lineStyle = tcell.StyleDefault.Reverse(true) @@ -576,8 +575,8 @@ func (v *View) DisplayView() { lineIndentStyle = style } if v.Cursor.HasSelection() && - (charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] || - charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) { + (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) || + charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) { lineIndentStyle = tcell.StyleDefault.Reverse(true) @@ -617,7 +616,7 @@ func (v *View) DisplayView() { screen.SetContent(x-v.leftCol, lineN, ch, nil, lineStyle) } } - charNum++ + charNum = charNum.Move(1, v.Buf) x++ } // Here we are at a newline @@ -625,8 +624,8 @@ func (v *View) DisplayView() { // The newline may be selected, in which case we should draw the selection style // with a space to represent it if v.Cursor.HasSelection() && - (charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] || - charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) { + (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) || + charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) { selectStyle := defStyle.Reverse(true) @@ -637,7 +636,7 @@ func (v *View) DisplayView() { x++ } - charNum++ + charNum = charNum.Move(1, v.Buf) for i := 0; i < v.width-(x-v.leftCol); i++ { lineStyle := tcell.StyleDefault