Something went wrong. Try again.
Fork of micro-editor/micro
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628package buffer
import ( "github.com/micro-editor/micro/v2/internal/clipboard" "github.com/micro-editor/micro/v2/internal/util")
// InBounds returns whether the given location is a valid character position in the given bufferfunc InBounds(pos Loc, buf *Buffer) bool { if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > util.CharacterCount(buf.LineBytes(pos.Y)) { return false }
return true}
// The Cursor struct stores the location of the cursor in the buffer// as well as the selectiontype Cursor struct { buf *Buffer Loc
// Last visual x position of the cursor. Used in cursor up/down movements // for remembering the original x position when moving to a line that is // shorter than current x position. LastVisualX int // Similar to LastVisualX but takes softwrapping into account, i.e. last // visual x position in a visual (wrapped) line on the screen, which may be // different from the line in the buffer. LastWrappedVisualX int
// The current selection as a range of character numbers (inclusive) 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]Loc
// The line number where a new trailing whitespace has been added // or -1 if there is no new trailing whitespace at this cursor. // This is used for checking if a trailing whitespace should be highlighted NewTrailingWsY int
// Which cursor index is this (for multiple cursors) Num int}
func NewCursor(b *Buffer, l Loc) *Cursor { c := &Cursor{ buf: b, Loc: l,
NewTrailingWsY: -1, } c.StoreVisualX() return c}
func (c *Cursor) SetBuf(b *Buffer) { c.buf = b}
func (c *Cursor) Buf() *Buffer { return c.buf}
// Goto puts the cursor at the given cursor's location and gives// the current cursor its selection toofunc (c *Cursor) Goto(b Cursor) { c.X, c.Y = b.X, b.Y c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection c.StoreVisualX()}
// GotoLoc puts the cursor at the given cursor's location and gives// the current cursor its selection toofunc (c *Cursor) GotoLoc(l Loc) { c.X, c.Y = l.X, l.Y c.StoreVisualX()}
// GetVisualX returns the x value of the cursor in visual spacesfunc (c *Cursor) GetVisualX(wrap bool) int { if wrap && c.buf.GetVisualX != nil { return c.buf.GetVisualX(c.Loc) }
if c.X <= 0 { c.X = 0 return 0 }
bytes := c.buf.LineBytes(c.Y) tabsize := int(c.buf.Settings["tabsize"].(float64))
return util.StringWidth(bytes, c.X, tabsize)}
// GetCharPosInLine gets the char position of a visual x y// coordinate (this is necessary because tabs are 1 char but// 4 visual spaces)func (c *Cursor) GetCharPosInLine(b []byte, visualPos int) int { tabsize := int(c.buf.Settings["tabsize"].(float64)) return util.GetCharPosInLine(b, visualPos, tabsize)}
// Start moves the cursor to the start of the line it is onfunc (c *Cursor) Start() { c.X = 0 c.StoreVisualX()}
// StartOfText moves the cursor to the first non-whitespace rune of// the line it is onfunc (c *Cursor) StartOfText() { c.Start() for util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { break } c.Right() }}
// IsStartOfText returns whether the cursor is at the first// non-whitespace rune of the line it is onfunc (c *Cursor) IsStartOfText() bool { x := 0 for util.IsWhitespace(c.RuneUnder(x)) { if x == util.CharacterCount(c.buf.LineBytes(c.Y)) { break } x++ } return c.X == x}
// End moves the cursor to the end of the line it is onfunc (c *Cursor) End() { c.X = util.CharacterCount(c.buf.LineBytes(c.Y)) c.StoreVisualX()}
// CopySelection copies the user's selection to either "primary"// or "clipboard"func (c *Cursor) CopySelection(target clipboard.Register) { if c.HasSelection() { if target != clipboard.PrimaryReg || c.buf.Settings["useprimary"].(bool) { clipboard.WriteMulti(string(c.GetSelection()), target, c.Num, c.buf.NumCursors()) } }}
// ResetSelection resets the user's selectionfunc (c *Cursor) ResetSelection() { c.CurSelection[0] = c.buf.Start() c.CurSelection[1] = c.buf.Start()}
// SetSelectionStart sets the start of the selectionfunc (c *Cursor) SetSelectionStart(pos Loc) { c.CurSelection[0] = pos}
// SetSelectionEnd sets the end of the selectionfunc (c *Cursor) SetSelectionEnd(pos Loc) { c.CurSelection[1] = pos}
// HasSelection returns whether or not the user has selected anythingfunc (c *Cursor) HasSelection() bool { return c.CurSelection[0] != c.CurSelection[1]}
// DeleteSelection deletes the currently selected textfunc (c *Cursor) DeleteSelection() { if c.CurSelection[0].GreaterThan(c.CurSelection[1]) { c.buf.Remove(c.CurSelection[1], c.CurSelection[0]) c.Loc = c.CurSelection[1] } else if !c.HasSelection() { return } else { c.buf.Remove(c.CurSelection[0], c.CurSelection[1]) c.Loc = c.CurSelection[0] }}
// Deselect closes the cursor's current selection// Start indicates whether the cursor should be placed// at the start or end of the selectionfunc (c *Cursor) Deselect(start bool) { if c.HasSelection() { if start { c.Loc = c.CurSelection[0] } else { c.Loc = c.CurSelection[1] } c.ResetSelection() c.StoreVisualX() }}
// GetSelection returns the cursor's selectionfunc (c *Cursor) GetSelection() []byte { if InBounds(c.CurSelection[0], c.buf) && InBounds(c.CurSelection[1], c.buf) { 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]) } return []byte{}}
// SelectLine selects the current linefunc (c *Cursor) SelectLine() { c.Start() c.SetSelectionStart(c.Loc) c.End() if len(c.buf.lines)-1 > c.Y { c.SetSelectionEnd(c.Loc.Move(1, c.buf)) } else { c.SetSelectionEnd(c.Loc) }
c.OrigSelection = c.CurSelection}
// AddLineToSelection adds the current line to the selectionfunc (c *Cursor) AddLineToSelection() { if c.Loc.LessThan(c.OrigSelection[0]) { c.Start() c.SetSelectionStart(c.Loc) c.SetSelectionEnd(c.OrigSelection[1]) } if c.Loc.GreaterThan(c.OrigSelection[1]) { c.End() c.SetSelectionEnd(c.Loc.Move(1, c.buf)) c.SetSelectionStart(c.OrigSelection[0]) }
if c.Loc.LessThan(c.OrigSelection[1]) && c.Loc.GreaterThan(c.OrigSelection[0]) { c.CurSelection = c.OrigSelection }}
// UpN moves the cursor up N lines (if possible)func (c *Cursor) UpN(amount int) { proposedY := c.Y - amount if proposedY < 0 { proposedY = 0 } else if proposedY >= len(c.buf.lines) { proposedY = len(c.buf.lines) - 1 }
bytes := c.buf.LineBytes(proposedY) c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
if c.X > util.CharacterCount(bytes) || (amount < 0 && proposedY == c.Y) { c.X = util.CharacterCount(bytes) c.StoreVisualX() }
if c.X < 0 || (amount > 0 && proposedY == c.Y) { c.X = 0 c.StoreVisualX() }
c.Y = proposedY}
// DownN moves the cursor down N lines (if possible)func (c *Cursor) DownN(amount int) { c.UpN(-amount)}
// Up moves the cursor up one line (if possible)func (c *Cursor) Up() { c.UpN(1)}
// Down moves the cursor down one line (if possible)func (c *Cursor) Down() { c.DownN(1)}
// Left moves the cursor left one cell (if possible) or to// the previous line if it is at the beginningfunc (c *Cursor) Left() { if c.Loc == c.buf.Start() { return } if c.X > 0 { c.X-- } else { c.Up() c.End() } c.StoreVisualX()}
// Right moves the cursor right one cell (if possible) or// to the next line if it is at the endfunc (c *Cursor) Right() { if c.Loc == c.buf.End() { return } if c.X < util.CharacterCount(c.buf.LineBytes(c.Y)) { c.X++ } else { c.Down() c.Start() } c.StoreVisualX()}
// Relocate makes sure that the cursor is inside the bounds// of the buffer If it isn't, it moves it to be within the// buffer's linesfunc (c *Cursor) Relocate() { if c.Y < 0 { c.Y = 0 } else if c.Y >= len(c.buf.lines) { c.Y = len(c.buf.lines) - 1 }
if c.X < 0 { c.X = 0 } else if c.X > util.CharacterCount(c.buf.LineBytes(c.Y)) { c.X = util.CharacterCount(c.buf.LineBytes(c.Y)) }}
// SelectWord selects the word the cursor is currently onfunc (c *Cursor) SelectWord() { if len(c.buf.LineBytes(c.Y)) == 0 { return }
if !util.IsWordChar(c.RuneUnder(c.X)) { c.SetSelectionStart(c.Loc) c.SetSelectionEnd(c.Loc.Move(1, c.buf)) c.OrigSelection = c.CurSelection return }
forward, backward := c.X, c.X
for backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) { backward-- }
c.SetSelectionStart(Loc{backward, c.Y}) c.OrigSelection[0] = c.CurSelection[0]
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1 for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) { forward++ }
c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf)) c.OrigSelection[1] = c.CurSelection[1] c.Loc = c.CurSelection[1]}
// AddWordToSelection adds the word the cursor is currently on// to the selectionfunc (c *Cursor) AddWordToSelection() { if c.Loc.GreaterThan(c.OrigSelection[0]) && c.Loc.LessThan(c.OrigSelection[1]) { c.CurSelection = c.OrigSelection return }
if c.Loc.LessThan(c.OrigSelection[0]) { backward := c.X
for backward > 0 && util.IsWordChar(c.RuneUnder(backward-1)) { backward-- }
c.SetSelectionStart(Loc{backward, c.Y}) c.SetSelectionEnd(c.OrigSelection[1]) }
if c.Loc.GreaterThan(c.OrigSelection[1]) { forward := c.X
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1 for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) { forward++ }
c.SetSelectionEnd(Loc{forward, c.Y}.Move(1, c.buf)) c.SetSelectionStart(c.OrigSelection[0]) }
c.Loc = c.CurSelection[1]}
// SelectTo selects from the current cursor location to the given// locationfunc (c *Cursor) SelectTo(loc Loc) { if loc.GreaterThan(c.OrigSelection[0]) { c.SetSelectionStart(c.OrigSelection[0]) c.SetSelectionEnd(loc) } else { c.SetSelectionStart(loc) c.SetSelectionEnd(c.OrigSelection[0]) }}
// WordRight moves the cursor one word to the rightfunc (c *Cursor) WordRight() { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { c.Right() return } for util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } c.Right() } if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) && util.IsNonWordChar(c.RuneUnder(c.X+1)) { for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } c.Right() } return } c.Right() for util.IsWordChar(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } c.Right() }}
// WordLeft moves the cursor one word to the leftfunc (c *Cursor) WordLeft() { if c.X == 0 { c.Left() return } c.Left() for util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == 0 { return } c.Left() } if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) && util.IsNonWordChar(c.RuneUnder(c.X-1)) { for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == 0 { return } c.Left() } c.Right() return } c.Left() for util.IsWordChar(c.RuneUnder(c.X)) { if c.X == 0 { return } c.Left() } c.Right()}
// SubWordRight moves the cursor one sub-word to the rightfunc (c *Cursor) SubWordRight() { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { c.Right() return } if util.IsWhitespace(c.RuneUnder(c.X)) { for util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } c.Right() } return } if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } c.Right() } return } if util.IsSubwordDelimiter(c.RuneUnder(c.X)) { for util.IsSubwordDelimiter(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } c.Right() } if util.IsWhitespace(c.RuneUnder(c.X)) { return } } if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } if util.IsUpperLetter(c.RuneUnder(c.X)) && util.IsUpperLetter(c.RuneUnder(c.X+1)) { for util.IsUpperAlphanumeric(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } c.Right() } if util.IsLowerAlphanumeric(c.RuneUnder(c.X)) { c.Left() } } else { c.Right() for util.IsLowerAlphanumeric(c.RuneUnder(c.X)) { if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) { return } c.Right() } }}
// SubWordLeft moves the cursor one sub-word to the leftfunc (c *Cursor) SubWordLeft() { if c.X == 0 { c.Left() return } c.Left() if util.IsWhitespace(c.RuneUnder(c.X)) { for util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == 0 { return } c.Left() } c.Right() return } if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) { if c.X == 0 { return } c.Left() } c.Right() return } if util.IsSubwordDelimiter(c.RuneUnder(c.X)) { for util.IsSubwordDelimiter(c.RuneUnder(c.X)) { if c.X == 0 { return } c.Left() } if util.IsWhitespace(c.RuneUnder(c.X)) { c.Right() return } } if c.X == 0 { return } if util.IsUpperLetter(c.RuneUnder(c.X)) && util.IsUpperLetter(c.RuneUnder(c.X-1)) { for util.IsUpperAlphanumeric(c.RuneUnder(c.X)) { if c.X == 0 { return } c.Left() } if !util.IsUpperAlphanumeric(c.RuneUnder(c.X)) { c.Right() } } else { for util.IsLowerAlphanumeric(c.RuneUnder(c.X)) { if c.X == 0 { return } c.Left() } if !util.IsAlphanumeric(c.RuneUnder(c.X)) { c.Right() } }}
// RuneUnder returns the rune under the given x positionfunc (c *Cursor) RuneUnder(x int) rune { line := c.buf.LineBytes(c.Y) if len(line) == 0 || x >= util.CharacterCount(line) { return '\n' } else if x < 0 { x = 0 } i := 0 for len(line) > 0 { r, _, size := util.DecodeCharacter(line) line = line[size:]
if i == x { return r }
i++ } return '\n'}
func (c *Cursor) StoreVisualX() { c.LastVisualX = c.GetVisualX(false) c.LastWrappedVisualX = c.GetVisualX(true)}