Replace rope with lineArray

This commit is contained in:
Zachary Yedidia
2016-06-07 11:43:28 -04:00
parent d6307b2718
commit 72f5808025
12 changed files with 453 additions and 275 deletions

View File

@@ -405,7 +405,7 @@ func DefaultBindings() map[string]string {
// CursorUp moves the cursor up // CursorUp moves the cursor up
func (v *View) CursorUp() bool { func (v *View) CursorUp() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.CurSelection[0]) v.Cursor.Loc = v.Cursor.CurSelection[0]
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
v.Cursor.Up() v.Cursor.Up()
@@ -415,7 +415,7 @@ func (v *View) CursorUp() bool {
// CursorDown moves the cursor down // CursorDown moves the cursor down
func (v *View) CursorDown() bool { func (v *View) CursorDown() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.CurSelection[1]) v.Cursor.Loc = v.Cursor.CurSelection[1]
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
v.Cursor.Down() v.Cursor.Down()
@@ -425,7 +425,7 @@ func (v *View) CursorDown() bool {
// CursorLeft moves the cursor left // CursorLeft moves the cursor left
func (v *View) CursorLeft() bool { func (v *View) CursorLeft() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.CurSelection[0]) v.Cursor.Loc = v.Cursor.CurSelection[0]
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} else { } else {
v.Cursor.Left() v.Cursor.Left()
@@ -436,7 +436,7 @@ func (v *View) CursorLeft() bool {
// CursorRight moves the cursor right // CursorRight moves the cursor right
func (v *View) CursorRight() bool { func (v *View) CursorRight() bool {
if v.Cursor.HasSelection() { 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() v.Cursor.ResetSelection()
} else { } else {
v.Cursor.Right() v.Cursor.Right()
@@ -458,75 +458,71 @@ func (v *View) WordLeft() bool {
// SelectUp selects up one line // SelectUp selects up one line
func (v *View) SelectUp() bool { func (v *View) SelectUp() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = v.Cursor.Loc
} }
v.Cursor.Up() v.Cursor.Up()
v.Cursor.SelectTo(v.Cursor.Loc()) v.Cursor.SelectTo(v.Cursor.Loc)
return true return true
} }
// SelectDown selects down one line // SelectDown selects down one line
func (v *View) SelectDown() bool { func (v *View) SelectDown() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = v.Cursor.Loc
} }
v.Cursor.Down() v.Cursor.Down()
v.Cursor.SelectTo(v.Cursor.Loc()) v.Cursor.SelectTo(v.Cursor.Loc)
return true return true
} }
// SelectLeft selects the character to the left of the cursor // SelectLeft selects the character to the left of the cursor
func (v *View) SelectLeft() bool { func (v *View) SelectLeft() bool {
loc := v.Cursor.Loc() loc := v.Cursor.Loc
count := v.Buf.Len() - 1 count := v.Buf.End().Move(-1, v.Buf)
if loc > count { if loc.GreaterThan(count) {
loc = count loc = count
} }
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = loc
} }
v.Cursor.Left() v.Cursor.Left()
v.Cursor.SelectTo(v.Cursor.Loc()) v.Cursor.SelectTo(v.Cursor.Loc)
return true return true
} }
// SelectRight selects the character to the right of the cursor // SelectRight selects the character to the right of the cursor
func (v *View) SelectRight() bool { func (v *View) SelectRight() bool {
loc := v.Cursor.Loc() loc := v.Cursor.Loc
count := v.Buf.Len() - 1 count := v.Buf.End().Move(-1, v.Buf)
if loc > count { if loc.GreaterThan(count) {
loc = count loc = count
} }
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = loc
} }
v.Cursor.Right() v.Cursor.Right()
v.Cursor.SelectTo(v.Cursor.Loc()) v.Cursor.SelectTo(v.Cursor.Loc)
return true return true
} }
// SelectWordRight selects the word to the right of the cursor // SelectWordRight selects the word to the right of the cursor
func (v *View) SelectWordRight() bool { func (v *View) SelectWordRight() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = v.Cursor.Loc
} }
v.Cursor.WordRight() v.Cursor.WordRight()
v.Cursor.SelectTo(v.Cursor.Loc()) v.Cursor.SelectTo(v.Cursor.Loc)
return true return true
} }
// SelectWordLeft selects the word to the left of the cursor // SelectWordLeft selects the word to the left of the cursor
func (v *View) SelectWordLeft() bool { func (v *View) SelectWordLeft() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = v.Cursor.Loc
} }
v.Cursor.WordLeft() v.Cursor.WordLeft()
v.Cursor.SelectTo(v.Cursor.Loc()) v.Cursor.SelectTo(v.Cursor.Loc)
return true return true
} }
@@ -544,23 +540,21 @@ func (v *View) EndOfLine() bool {
// SelectToStartOfLine selects to the start of the current line // SelectToStartOfLine selects to the start of the current line
func (v *View) SelectToStartOfLine() bool { func (v *View) SelectToStartOfLine() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = v.Cursor.Loc
} }
v.Cursor.Start() v.Cursor.Start()
v.Cursor.SelectTo(v.Cursor.Loc()) v.Cursor.SelectTo(v.Cursor.Loc)
return true return true
} }
// SelectToEndOfLine selects to the end of the current line // SelectToEndOfLine selects to the end of the current line
func (v *View) SelectToEndOfLine() bool { func (v *View) SelectToEndOfLine() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = v.Cursor.Loc
} }
v.Cursor.End() v.Cursor.End()
v.Cursor.SelectTo(v.Cursor.Loc()) v.Cursor.SelectTo(v.Cursor.Loc)
return true return true
} }
@@ -573,29 +567,27 @@ func (v *View) CursorStart() bool {
// CursorEnd moves the cursor to the end of the buffer // CursorEnd moves the cursor to the end of the buffer
func (v *View) CursorEnd() bool { func (v *View) CursorEnd() bool {
v.Cursor.SetLoc(v.Buf.Len()) v.Cursor.Loc = v.Buf.End()
return true return true
} }
// SelectToStart selects the text from the cursor to the start of the buffer // SelectToStart selects the text from the cursor to the start of the buffer
func (v *View) SelectToStart() bool { func (v *View) SelectToStart() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = v.Cursor.Loc
} }
v.CursorStart() v.CursorStart()
v.Cursor.SelectTo(0) v.Cursor.SelectTo(v.Buf.Start())
return true return true
} }
// SelectToEnd selects the text from the cursor to the end of the buffer // SelectToEnd selects the text from the cursor to the end of the buffer
func (v *View) SelectToEnd() bool { func (v *View) SelectToEnd() bool {
loc := v.Cursor.Loc()
if !v.Cursor.HasSelection() { if !v.Cursor.HasSelection() {
v.Cursor.OrigSelection[0] = loc v.Cursor.OrigSelection[0] = v.Cursor.Loc
} }
v.CursorEnd() v.CursorEnd()
v.Cursor.SelectTo(v.Buf.Len()) v.Cursor.SelectTo(v.Buf.End())
return true return true
} }
@@ -605,7 +597,7 @@ func (v *View) InsertSpace() bool {
v.Cursor.DeleteSelection() v.Cursor.DeleteSelection()
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
v.Buf.Insert(v.Cursor.Loc(), " ") v.Buf.Insert(v.Cursor.Loc, " ")
v.Cursor.Right() v.Cursor.Right()
return true return true
} }
@@ -618,12 +610,12 @@ func (v *View) InsertEnter() bool {
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
v.Buf.Insert(v.Cursor.Loc(), "\n") v.Buf.Insert(v.Cursor.Loc, "\n")
ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.Y]) ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
v.Cursor.Right() v.Cursor.Right()
if settings["autoindent"].(bool) { 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++ { for i := 0; i < len(ws); i++ {
v.Cursor.Right() v.Cursor.Right()
} }
@@ -638,7 +630,7 @@ func (v *View) Backspace() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
v.Cursor.DeleteSelection() v.Cursor.DeleteSelection()
v.Cursor.ResetSelection() 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 // We have to do something a bit hacky here because we want to
// delete the line by first moving left and then deleting backwards // delete the line by first moving left and then deleting backwards
// but the undo redo would place the cursor in the wrong place // 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 // 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 // whitespace at the start of the line, we should delete as if its a
// tab (tabSize number of spaces) // 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)) tabSize := int(settings["tabsize"].(float64))
if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 { if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
loc := v.Cursor.Loc() loc := v.Cursor.Loc
v.Cursor.SetLoc(loc - tabSize) v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
cx, cy := v.Cursor.X, v.Cursor.Y cx, cy := v.Cursor.X, v.Cursor.Y
v.Cursor.SetLoc(loc) v.Cursor.Loc = loc
v.Buf.Remove(loc-tabSize, loc) v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
v.Cursor.X, v.Cursor.Y = cx, cy v.Cursor.X, v.Cursor.Y = cx, cy
} else { } else {
v.Cursor.Left() v.Cursor.Left()
cx, cy := v.Cursor.X, v.Cursor.Y cx, cy := v.Cursor.X, v.Cursor.Y
v.Cursor.Right() v.Cursor.Right()
loc := v.Cursor.Loc() loc := v.Cursor.Loc
v.Buf.Remove(loc-1, loc) v.Buf.Remove(loc.Move(-1, v.Buf), loc)
v.Cursor.X, v.Cursor.Y = cx, cy v.Cursor.X, v.Cursor.Y = cx, cy
} }
} }
@@ -696,9 +688,9 @@ func (v *View) Delete() bool {
v.Cursor.DeleteSelection() v.Cursor.DeleteSelection()
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} else { } else {
loc := v.Cursor.Loc() loc := v.Cursor.Loc
if loc < v.Buf.Len() { if loc.LessThan(v.Buf.End()) {
v.Buf.Remove(loc, loc+1) v.Buf.Remove(loc, loc.Move(1, v.Buf))
} }
} }
return true return true
@@ -713,12 +705,12 @@ func (v *View) InsertTab() bool {
} }
if settings["tabstospaces"].(bool) { if settings["tabstospaces"].(bool) {
tabSize := int(settings["tabsize"].(float64)) 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++ { for i := 0; i < tabSize; i++ {
v.Cursor.Right() v.Cursor.Right()
} }
} else { } else {
v.Buf.Insert(v.Cursor.Loc(), "\t") v.Buf.Insert(v.Cursor.Loc, "\t")
v.Cursor.Right() v.Cursor.Right()
} }
return true return true
@@ -765,9 +757,9 @@ func (v *View) Save() bool {
// Find opens a prompt and searches forward for the input // Find opens a prompt and searches forward for the input
func (v *View) Find() bool { func (v *View) Find() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
searchStart = v.Cursor.CurSelection[1] searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
} else { } else {
searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf) searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
} }
BeginSearch() BeginSearch()
return true return true
@@ -776,9 +768,9 @@ func (v *View) Find() bool {
// FindNext searches forwards for the last used search term // FindNext searches forwards for the last used search term
func (v *View) FindNext() bool { func (v *View) FindNext() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
searchStart = v.Cursor.CurSelection[1] searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
} else { } else {
searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf) searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
} }
messenger.Message("Finding: " + lastSearch) messenger.Message("Finding: " + lastSearch)
Search(lastSearch, v, true) Search(lastSearch, v, true)
@@ -788,9 +780,9 @@ func (v *View) FindNext() bool {
// FindPrevious searches backwards for the last used search term // FindPrevious searches backwards for the last used search term
func (v *View) FindPrevious() bool { func (v *View) FindPrevious() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
searchStart = v.Cursor.CurSelection[0] searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
} else { } else {
searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf) searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
} }
messenger.Message("Finding: " + lastSearch) messenger.Message("Finding: " + lastSearch)
Search(lastSearch, v, false) Search(lastSearch, v, false)
@@ -861,7 +853,7 @@ func (v *View) Cut() bool {
// DuplicateLine duplicates the current line // DuplicateLine duplicates the current line
func (v *View) DuplicateLine() bool { func (v *View) DuplicateLine() bool {
v.Cursor.End() 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() v.Cursor.Right()
messenger.Message("Duplicated line") messenger.Message("Duplicated line")
return true return true
@@ -875,8 +867,8 @@ func (v *View) Paste() bool {
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
clip, _ := clipboard.ReadAll() clip, _ := clipboard.ReadAll()
v.Buf.Insert(v.Cursor.Loc(), clip) v.Buf.Insert(v.Cursor.Loc, clip)
v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip)) v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
v.freshClip = false v.freshClip = false
messenger.Message("Pasted clipboard") messenger.Message("Pasted clipboard")
return true return true
@@ -884,8 +876,8 @@ func (v *View) Paste() bool {
// SelectAll selects the entire buffer // SelectAll selects the entire buffer
func (v *View) SelectAll() bool { func (v *View) SelectAll() bool {
v.Cursor.CurSelection[0] = 0 v.Cursor.CurSelection[0] = v.Buf.Start()
v.Cursor.CurSelection[1] = v.Buf.Len() v.Cursor.CurSelection[1] = v.Buf.End()
// Put the cursor at the beginning // Put the cursor at the beginning
v.Cursor.X = 0 v.Cursor.X = 0
v.Cursor.Y = 0 v.Cursor.Y = 0
@@ -907,7 +899,7 @@ func (v *View) OpenFile() bool {
messenger.Error(err.Error()) messenger.Error(err.Error())
return true return true
} }
buf := NewBuffer(string(file), filename) buf := NewBuffer(file, filename)
v.OpenBuffer(buf) v.OpenBuffer(buf)
} }
return true return true
@@ -952,7 +944,7 @@ func (v *View) PageDown() bool {
// CursorPageUp places the cursor a page up // CursorPageUp places the cursor a page up
func (v *View) CursorPageUp() bool { func (v *View) CursorPageUp() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.CurSelection[0]) v.Cursor.Loc = v.Cursor.CurSelection[0]
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
v.Cursor.UpN(v.height) v.Cursor.UpN(v.height)
@@ -962,7 +954,7 @@ func (v *View) CursorPageUp() bool {
// CursorPageDown places the cursor a page up // CursorPageDown places the cursor a page up
func (v *View) CursorPageDown() bool { func (v *View) CursorPageDown() bool {
if v.Cursor.HasSelection() { if v.Cursor.HasSelection() {
v.Cursor.SetLoc(v.Cursor.CurSelection[1]) v.Cursor.Loc = v.Cursor.CurSelection[1]
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
v.Cursor.DownN(v.height) v.Cursor.DownN(v.height)
@@ -1036,7 +1028,7 @@ func (v *View) ClearStatus() bool {
func (v *View) ToggleHelp() bool { func (v *View) ToggleHelp() bool {
if !v.helpOpen { if !v.helpOpen {
v.lastBuffer = v.Buf v.lastBuffer = v.Buf
helpBuffer := NewBuffer(helpTxt, "help.md") helpBuffer := NewBuffer([]byte(helpTxt), "help.md")
helpBuffer.Name = "Help" helpBuffer.Name = "Help"
v.helpOpen = true v.helpOpen = true
v.OpenBuffer(helpBuffer) v.OpenBuffer(helpBuffer)

View File

@@ -8,10 +8,8 @@ import (
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"unicode/utf8"
"github.com/vinzmay/go-rope"
) )
// Buffer stores the text for files that are loaded into the text editor // Buffer stores the text for files that are loaded into the text editor
@@ -20,9 +18,7 @@ import (
type Buffer struct { type Buffer struct {
// The eventhandler for undo/redo // The eventhandler for undo/redo
*EventHandler *EventHandler
*LineArray
// Stores the text of the buffer
r *rope.Rope
Cursor Cursor Cursor Cursor
@@ -36,10 +32,6 @@ type Buffer struct {
// Stores the last modification time of the file the buffer is pointing to // Stores the last modification time of the file the buffer is pointing to
ModTime time.Time 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 NumLines int
// Syntax highlighting rules // Syntax highlighting rules
@@ -56,13 +48,9 @@ type SerializedBuffer struct {
} }
// NewBuffer creates a new buffer from `txt` with path and name `path` // 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) b := new(Buffer)
if txt == "" { b.LineArray = NewLineArray(txt)
b.r = new(rope.Rope)
} else {
b.r = rope.New(txt)
}
b.Path = path b.Path = path
b.Name = path b.Name = path
@@ -79,8 +67,10 @@ func NewBuffer(txt, path string) *Buffer {
// Put the cursor at the first spot // Put the cursor at the first spot
b.Cursor = Cursor{ b.Cursor = Cursor{
X: 0, Loc: Loc{
Y: 0, X: 0,
Y: 0,
},
buf: b, buf: b,
} }
@@ -121,13 +111,6 @@ func (b *Buffer) UpdateRules() {
b.rules, b.FileType = GetRules(b) 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 // CheckModTime makes sure that the file this buffer points to hasn't been updated
// by an external program since it was last read // by an external program since it was last read
// If it has, we ask the user if they would like to reload the file // 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 // Update fetches the string from the rope and updates the `text` and `lines` in the buffer
func (b *Buffer) Update() { 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 // Save saves the buffer to its default path
@@ -268,42 +250,44 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
return err return err
} }
// This directly inserts value at idx, bypassing all undo/redo func (b *Buffer) insert(pos Loc, value []byte) {
func (b *Buffer) insert(idx int, value string) {
b.IsModified = true b.IsModified = true
b.r = b.r.Insert(idx, value) b.LineArray.insert(pos, value)
b.Update() b.Update()
} }
func (b *Buffer) remove(start, end Loc) string {
// 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 {
b.IsModified = true b.IsModified = true
if start < 0 { sub := b.LineArray.remove(start, end)
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)
b.Update() b.Update()
return removed return sub
} }
// Substr returns the substring of the rope from start to end // Start returns the location of the first character in the buffer
func (b *Buffer) Substr(start, end int) string { func (b *Buffer) Start() Loc {
return b.r.Substr(start+1, end-start).String() 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 // Len gives the length of the buffer
func (b *Buffer) Len() int { func (b *Buffer) Len() int {
return b.r.Len() return Count(b.String())
} }

View File

@@ -158,7 +158,7 @@ func Replace(args []string) {
choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)") choice, canceled := messenger.YesNoPrompt("Perform replacement? (y,n)")
if canceled { if canceled {
if view.Cursor.HasSelection() { if view.Cursor.HasSelection() {
view.Cursor.SetLoc(view.Cursor.CurSelection[0]) view.Cursor.Loc = view.Cursor.CurSelection[0]
view.Cursor.ResetSelection() view.Cursor.ResetSelection()
} }
messenger.Reset() messenger.Reset()
@@ -166,19 +166,19 @@ func Replace(args []string) {
} }
if choice { if choice {
view.Cursor.DeleteSelection() view.Cursor.DeleteSelection()
view.Buf.Insert(match[0], replace) view.Buf.Insert(FromCharPos(match[0], view.Buf), replace)
view.Cursor.ResetSelection() view.Cursor.ResetSelection()
messenger.Reset() messenger.Reset()
} else { } else {
if view.Cursor.HasSelection() { if view.Cursor.HasSelection() {
searchStart = view.Cursor.CurSelection[1] searchStart = ToCharPos(view.Cursor.CurSelection[1], view.Buf)
} else { } else {
searchStart = ToCharPos(view.Cursor.X, view.Cursor.Y, view.Buf) searchStart = ToCharPos(view.Cursor.Loc, view.Buf)
} }
continue continue
} }
} else { } 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 { if !found {

View File

@@ -1,40 +1,5 @@
package main 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 Cursor struct stores the location of the cursor in the view
// The complicated part about the cursor is storing its location. // The complicated part about the cursor is storing its location.
// The cursor must be displayed at an x, y location, but since the buffer // 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. // selection.
type Cursor struct { type Cursor struct {
buf *Buffer buf *Buffer
Loc
// The cursor display location
X int
Y int
// Last cursor x position // Last cursor x position
LastVisualX int LastVisualX int
// The current selection as a range of character numbers (inclusive) // 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 // The original selection as a range of character numbers
// This is used for line and word selection where it is necessary // This is used for line and word selection where it is necessary
// to know what the original selection was // 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 // 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 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 // ResetSelection resets the user's selection
func (c *Cursor) ResetSelection() { func (c *Cursor) ResetSelection() {
c.CurSelection[0] = 0 c.CurSelection[0] = c.buf.Start()
c.CurSelection[1] = 0 c.CurSelection[1] = c.buf.Start()
} }
// HasSelection returns whether or not the user has selected anything // 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 // DeleteSelection deletes the currently selected text
func (c *Cursor) DeleteSelection() { 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.buf.Remove(c.CurSelection[1], c.CurSelection[0])
c.SetLoc(c.CurSelection[1]) c.Loc = c.CurSelection[1]
} else if c.GetSelection() == "" { } else if c.GetSelection() == "" {
return return
} else { } else {
c.buf.Remove(c.CurSelection[0], c.CurSelection[1]) 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 // GetSelection returns the cursor's selection
func (c *Cursor) GetSelection() string { 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[1], c.CurSelection[0])
} }
return c.buf.Substr(c.CurSelection[0], c.CurSelection[1]) return c.buf.Substr(c.CurSelection[0], c.CurSelection[1])
@@ -115,12 +62,12 @@ func (c *Cursor) GetSelection() string {
// SelectLine selects the current line // SelectLine selects the current line
func (c *Cursor) SelectLine() { func (c *Cursor) SelectLine() {
c.Start() c.Start()
c.CurSelection[0] = c.Loc() c.CurSelection[0] = c.Loc
c.End() c.End()
if c.buf.NumLines-1 > c.Y { if c.buf.NumLines-1 > c.Y {
c.CurSelection[1] = c.Loc() + 1 c.CurSelection[1] = c.Loc.Move(1, c.buf)
} else { } else {
c.CurSelection[1] = c.Loc() c.CurSelection[1] = c.Loc
} }
c.OrigSelection = c.CurSelection c.OrigSelection = c.CurSelection
@@ -128,34 +75,31 @@ func (c *Cursor) SelectLine() {
// AddLineToSelection adds the current line to the selection // AddLineToSelection adds the current line to the selection
func (c *Cursor) AddLineToSelection() { func (c *Cursor) AddLineToSelection() {
loc := c.Loc() if c.Loc.LessThan(c.OrigSelection[0]) {
if loc < c.OrigSelection[0] {
c.Start() c.Start()
c.CurSelection[0] = c.Loc() c.CurSelection[0] = c.Loc
c.CurSelection[1] = c.OrigSelection[1] c.CurSelection[1] = c.OrigSelection[1]
} }
if loc > c.OrigSelection[1] { if c.Loc.GreaterThan(c.OrigSelection[1]) {
c.End() c.End()
c.CurSelection[1] = c.Loc() + 1 c.CurSelection[1] = c.Loc.Move(1, c.buf)
c.CurSelection[0] = c.OrigSelection[0] 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 c.CurSelection = c.OrigSelection
} }
} }
// SelectWord selects the word the cursor is currently on // SelectWord selects the word the cursor is currently on
func (c *Cursor) SelectWord() { func (c *Cursor) SelectWord() {
if len(c.buf.Lines[c.Y]) == 0 { if len(c.buf.Line(c.Y)) == 0 {
return return
} }
if !IsWordChar(string(c.RuneUnder(c.X))) { if !IsWordChar(string(c.RuneUnder(c.X))) {
loc := c.Loc() c.CurSelection[0] = c.Loc
c.CurSelection[0] = loc c.CurSelection[1] = c.Loc.Move(1, c.buf)
c.CurSelection[1] = loc + 1
c.OrigSelection = c.CurSelection c.OrigSelection = c.CurSelection
return return
} }
@@ -166,55 +110,53 @@ func (c *Cursor) SelectWord() {
backward-- backward--
} }
c.CurSelection[0] = ToCharPos(backward, c.Y, c.buf) c.CurSelection[0] = Loc{backward, c.Y}
c.OrigSelection[0] = c.CurSelection[0] 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++ 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.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 // AddWordToSelection adds the word the cursor is currently on to the selection
func (c *Cursor) AddWordToSelection() { func (c *Cursor) AddWordToSelection() {
loc := c.Loc() if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) {
if loc > c.OrigSelection[0] && loc < c.OrigSelection[1] {
c.CurSelection = c.OrigSelection c.CurSelection = c.OrigSelection
return return
} }
if loc < c.OrigSelection[0] { if c.Loc.LessThan(c.OrigSelection[0]) {
backward := c.X backward := c.X
for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) { for backward > 0 && IsWordChar(string(c.RuneUnder(backward-1))) {
backward-- backward--
} }
c.CurSelection[0] = ToCharPos(backward, c.Y, c.buf) c.CurSelection[0] = Loc{backward, c.Y}
c.CurSelection[1] = c.OrigSelection[1] c.CurSelection[1] = c.OrigSelection[1]
} }
if loc > c.OrigSelection[1] { if c.Loc.GreaterThan(c.OrigSelection[1]) {
forward := c.X 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++ 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.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 // SelectTo selects from the current cursor location to the given location
func (c *Cursor) SelectTo(loc int) { func (c *Cursor) SelectTo(loc Loc) {
if loc > c.OrigSelection[0] { if loc.GreaterThan(c.OrigSelection[0]) {
c.CurSelection[0] = c.OrigSelection[0] c.CurSelection[0] = c.OrigSelection[0]
c.CurSelection[1] = loc c.CurSelection[1] = loc
} else { } else {
@@ -227,13 +169,13 @@ func (c *Cursor) SelectTo(loc int) {
func (c *Cursor) WordRight() { func (c *Cursor) WordRight() {
c.Right() c.Right()
for IsWhitespace(c.RuneUnder(c.X)) { 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 return
} }
c.Right() c.Right()
} }
for !IsWhitespace(c.RuneUnder(c.X)) { 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 return
} }
c.Right() c.Right()
@@ -260,7 +202,7 @@ func (c *Cursor) WordLeft() {
// RuneUnder returns the rune under the given x position // RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune { 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 { if len(line) == 0 {
return '\n' return '\n'
} }
@@ -285,7 +227,7 @@ func (c *Cursor) UpN(amount int) {
} }
c.Y = proposedY 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) c.X = c.GetCharPosInLine(c.Y, c.LastVisualX)
if c.X > len(runes) { if c.X > len(runes) {
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 // Left moves the cursor left one cell (if possible) or to the last line if it is at the beginning
func (c *Cursor) Left() { func (c *Cursor) Left() {
if c.Loc() == 0 { if c.Loc == c.buf.Start() {
return return
} }
if c.X > 0 { 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 // Right moves the cursor right one cell (if possible) or to the next line if it is at the end
func (c *Cursor) Right() { func (c *Cursor) Right() {
if c.Loc() == c.buf.Len() { if c.Loc == c.buf.End() {
return return
} }
// TermMessage(Count(c.buf.Lines[c.Y])) if c.X < Count(c.buf.Line(c.Y)) {
if c.X < Count(c.buf.Lines[c.Y]) {
c.X++ c.X++
} else { } else {
c.Down() c.Down()
@@ -338,7 +279,7 @@ func (c *Cursor) Right() {
// End moves the cursor to the end of the line it is on // End moves the cursor to the end of the line it is on
func (c *Cursor) End() { func (c *Cursor) End() {
c.X = Count(c.buf.Lines[c.Y]) c.X = Count(c.buf.Line(c.Y))
c.LastVisualX = c.GetVisualX() c.LastVisualX = c.GetVisualX()
} }
@@ -353,11 +294,11 @@ func (c *Cursor) GetCharPosInLine(lineNum, visualPos int) int {
// Get the tab size // Get the tab size
tabSize := int(settings["tabsize"].(float64)) tabSize := int(settings["tabsize"].(float64))
// This is the visual line -- every \t replaced with the correct number of spaces // 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 { if visualPos > visualLineLen {
visualPos = visualLineLen visualPos = visualLineLen
} }
width := WidthOfLargeRunes(c.buf.Lines[lineNum]) width := WidthOfLargeRunes(c.buf.Line(lineNum))
if visualPos >= width { if visualPos >= width {
return 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 // GetVisualX returns the x value of the cursor in visual spaces
func (c *Cursor) GetVisualX() int { 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])) return StringWidth(string(runes[:c.X]))
} }
@@ -381,7 +322,7 @@ func (c *Cursor) Relocate() {
if c.X < 0 { if c.X < 0 {
c.X = 0 c.X = 0
} else if c.X > Count(c.buf.Lines[c.Y]) { } else if c.X > Count(c.buf.Line(c.Y)) {
c.X = Count(c.buf.Lines[c.Y]) c.X = Count(c.buf.Line(c.Y))
} }
} }

View File

@@ -21,15 +21,15 @@ type TextEvent struct {
EventType int EventType int
Text string Text string
Start int Start Loc
End int End Loc
Time time.Time Time time.Time
} }
// ExecuteTextEvent runs a text event // ExecuteTextEvent runs a text event
func ExecuteTextEvent(t *TextEvent, buf *Buffer) { func ExecuteTextEvent(t *TextEvent, buf *Buffer) {
if t.EventType == TextEventInsert { if t.EventType == TextEventInsert {
buf.insert(t.Start, t.Text) buf.insert(t.Start, []byte(t.Text))
} else if t.EventType == TextEventRemove { } else if t.EventType == TextEventRemove {
t.Text = buf.remove(t.Start, t.End) t.Text = buf.remove(t.Start, t.End)
} }
@@ -64,32 +64,32 @@ func NewEventHandler(buf *Buffer) *EventHandler {
func (eh *EventHandler) ApplyDiff(new string) { func (eh *EventHandler) ApplyDiff(new string) {
differ := dmp.New() differ := dmp.New()
diff := differ.DiffMain(eh.buf.String(), new, false) diff := differ.DiffMain(eh.buf.String(), new, false)
var charNum int loc := eh.buf.Start()
for _, d := range diff { for _, d := range diff {
if d.Type == dmp.DiffInsert { if d.Type == dmp.DiffInsert {
eh.Insert(charNum, d.Text) eh.Insert(loc, d.Text)
} else if d.Type == dmp.DiffDelete { } 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 // 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{ e := &TextEvent{
C: eh.buf.Cursor, C: eh.buf.Cursor,
EventType: TextEventInsert, EventType: TextEventInsert,
Text: text, Text: text,
Start: start, Start: start,
End: start + Count(text), End: start.Move(Count(text), eh.buf),
Time: time.Now(), Time: time.Now(),
} }
eh.Execute(e) eh.Execute(e)
} }
// Remove creates a remove text event and executes it // 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{ e := &TextEvent{
C: eh.buf.Cursor, C: eh.buf.Cursor,
EventType: TextEventRemove, 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 // 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.Remove(start, end)
eh.Insert(start, replace) eh.Insert(start, replace)
} }

View File

@@ -366,7 +366,7 @@ func GetRules(buf *Buffer) ([]SyntaxRule, string) {
if r[0] != nil && r[0].MatchString(buf.Path) { if r[0] != nil && r[0].MatchString(buf.Path) {
// Check if the syntax statement matches the extension // Check if the syntax statement matches the extension
return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype 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 // Check if the header statement matches the first line
return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype return LoadRulesFromFile(syntaxFiles[r].text, syntaxFiles[r].filename), syntaxFiles[r].filetype
} }
@@ -390,7 +390,7 @@ func Match(v *View) SyntaxMatches {
viewEnd = buf.NumLines viewEnd = buf.NumLines
} }
lines := buf.Lines[viewStart:viewEnd] lines := buf.Lines(viewStart, viewEnd)
matches := make(SyntaxMatches, len(lines)) matches := make(SyntaxMatches, len(lines))
for i, line := range lines { for i, line := range lines {
@@ -407,10 +407,10 @@ func Match(v *View) SyntaxMatches {
totalEnd = buf.NumLines totalEnd = buf.NumLines
} }
str := strings.Join(buf.Lines[totalStart:totalEnd], "\n") str := strings.Join(buf.Lines(totalStart, totalEnd), "\n")
startNum := ToCharPos(0, totalStart, v.Buf) 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 { for _, rule := range rules {
if rule.startend { if rule.startend {
@@ -422,7 +422,8 @@ func Match(v *View) SyntaxMatches {
if i < toplineNum { if i < toplineNum {
continue 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 { if lineNum == -1 || colNum == -1 {
continue continue
} }

133
cmd/micro/lineArray.go Normal file
View File

@@ -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
}

120
cmd/micro/loc.go Normal file
View File

@@ -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 {
//
// }

View File

@@ -210,7 +210,7 @@ func main() {
// Load the help files // Load the help files
LoadHelp() LoadHelp()
buf := NewBuffer(string(input), filename) buf := NewBuffer(input, filename)
InitScreen() InitScreen()

View File

@@ -125,9 +125,9 @@ func Search(searchStr string, v *View, down bool) {
return return
} }
v.Cursor.CurSelection[0] = charPos + runePos(match[0], str) v.Cursor.CurSelection[0] = FromCharPos(charPos+runePos(match[0], str), v.Buf)
v.Cursor.CurSelection[1] = charPos + runePos(match[1], str) v.Cursor.CurSelection[1] = FromCharPos(charPos+runePos(match[1], str), v.Buf)
v.Cursor.X, v.Cursor.Y = FromCharPos(charPos+match[1]-1, v.Buf) v.Cursor.Loc = FromCharPos(charPos+match[1]-1, v.Buf)
if v.Relocate() { if v.Relocate() {
v.matches = Match(v) v.matches = Match(v)
} }

View File

@@ -165,3 +165,11 @@ func WidthOfLargeRunes(str string) int {
func runePos(p int, str string) int { func runePos(p int, str string) int {
return utf8.RuneCountInString(str[:p]) 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
}

View File

@@ -251,8 +251,8 @@ func (v *View) MoveToMouseClick(x, y int) {
} }
x = v.Cursor.GetCharPosInLine(y, x) x = v.Cursor.GetCharPosInLine(y, x)
if x > Count(v.Buf.Lines[y]) { if x > Count(v.Buf.Line(y)) {
x = Count(v.Buf.Lines[y]) x = Count(v.Buf.Line(y))
} }
v.Cursor.X = x v.Cursor.X = x
v.Cursor.Y = y v.Cursor.Y = y
@@ -278,7 +278,7 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Cursor.DeleteSelection() v.Cursor.DeleteSelection()
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
v.Buf.Insert(v.Cursor.Loc(), string(e.Rune())) v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
v.Cursor.Right() v.Cursor.Right()
} else { } else {
for key, actions := range bindings { for key, actions := range bindings {
@@ -310,8 +310,8 @@ func (v *View) HandleEvent(event tcell.Event) {
v.Cursor.ResetSelection() v.Cursor.ResetSelection()
} }
clip := e.Text() clip := e.Text()
v.Buf.Insert(v.Cursor.Loc(), clip) v.Buf.Insert(v.Cursor.Loc, clip)
v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip)) v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
v.freshClip = false v.freshClip = false
case *tcell.EventMouse: case *tcell.EventMouse:
x, y := e.Position() x, y := e.Position()
@@ -350,10 +350,9 @@ func (v *View) HandleEvent(event tcell.Event) {
v.tripleClick = false v.tripleClick = false
v.lastClickTime = time.Now() v.lastClickTime = time.Now()
loc := v.Cursor.Loc() v.Cursor.OrigSelection[0] = v.Cursor.Loc
v.Cursor.OrigSelection[0] = loc v.Cursor.CurSelection[0] = v.Cursor.Loc
v.Cursor.CurSelection[0] = loc v.Cursor.CurSelection[1] = v.Cursor.Loc
v.Cursor.CurSelection[1] = loc
} }
v.mouseReleased = false v.mouseReleased = false
} else if !v.mouseReleased { } else if !v.mouseReleased {
@@ -363,7 +362,7 @@ func (v *View) HandleEvent(event tcell.Event) {
} else if v.doubleClick { } else if v.doubleClick {
v.Cursor.AddWordToSelection() v.Cursor.AddWordToSelection()
} else { } else {
v.Cursor.CurSelection[1] = v.Cursor.Loc() v.Cursor.CurSelection[1] = v.Cursor.Loc
} }
} }
case tcell.ButtonNone: case tcell.ButtonNone:
@@ -379,7 +378,7 @@ func (v *View) HandleEvent(event tcell.Event) {
if !v.doubleClick && !v.tripleClick { if !v.doubleClick && !v.tripleClick {
v.MoveToMouseClick(x, y) v.MoveToMouseClick(x, y)
v.Cursor.CurSelection[1] = v.Cursor.Loc() v.Cursor.CurSelection[1] = v.Cursor.Loc
} }
v.mouseReleased = true v.mouseReleased = true
} }
@@ -436,7 +435,7 @@ func (v *View) ClearAllGutterMessages() {
// DisplayView renders the view to the screen // DisplayView renders the view to the screen
func (v *View) DisplayView() { func (v *View) DisplayView() {
// The character number of the character in the top left of the screen // 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 // 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 // We are going to have to offset by that amount
@@ -470,7 +469,7 @@ func (v *View) DisplayView() {
continue continue
} }
line := v.Buf.Lines[lineN+v.Topline] line := v.Buf.Line(lineN + v.Topline)
if hasGutterMessages { if hasGutterMessages {
msgOnLine := false msgOnLine := false
@@ -551,8 +550,8 @@ func (v *View) DisplayView() {
} }
if v.Cursor.HasSelection() && if v.Cursor.HasSelection() &&
(charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] || (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) { charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
lineStyle = tcell.StyleDefault.Reverse(true) lineStyle = tcell.StyleDefault.Reverse(true)
@@ -576,8 +575,8 @@ func (v *View) DisplayView() {
lineIndentStyle = style lineIndentStyle = style
} }
if v.Cursor.HasSelection() && if v.Cursor.HasSelection() &&
(charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] || (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) { charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
lineIndentStyle = tcell.StyleDefault.Reverse(true) lineIndentStyle = tcell.StyleDefault.Reverse(true)
@@ -617,7 +616,7 @@ func (v *View) DisplayView() {
screen.SetContent(x-v.leftCol, lineN, ch, nil, lineStyle) screen.SetContent(x-v.leftCol, lineN, ch, nil, lineStyle)
} }
} }
charNum++ charNum = charNum.Move(1, v.Buf)
x++ x++
} }
// Here we are at a newline // 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 // The newline may be selected, in which case we should draw the selection style
// with a space to represent it // with a space to represent it
if v.Cursor.HasSelection() && if v.Cursor.HasSelection() &&
(charNum >= v.Cursor.CurSelection[0] && charNum < v.Cursor.CurSelection[1] || (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
charNum < v.Cursor.CurSelection[0] && charNum >= v.Cursor.CurSelection[1]) { charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
selectStyle := defStyle.Reverse(true) selectStyle := defStyle.Reverse(true)
@@ -637,7 +636,7 @@ func (v *View) DisplayView() {
x++ x++
} }
charNum++ charNum = charNum.Move(1, v.Buf)
for i := 0; i < v.width-(x-v.leftCol); i++ { for i := 0; i < v.width-(x-v.leftCol); i++ {
lineStyle := tcell.StyleDefault lineStyle := tcell.StyleDefault