package buffer import "testing" func TestSetCursorClampsIntoTheBuffer(t *testing.T) { b := NewFromString("ab\ncdef") tests := []struct { name string give Position want Position }{ {"past the end of a line", Position{Line: 0, Col: 99}, Position{Line: 0, Col: 2}}, {"past the last line", Position{Line: 99, Col: 0}, Position{Line: 1, Col: 4}}, {"negative line", Position{Line: -1, Col: 3}, Position{Line: 0, Col: 0}}, {"negative column", Position{Line: 1, Col: -3}, Position{Line: 1, Col: 0}}, {"already valid", Position{Line: 1, Col: 2}, Position{Line: 1, Col: 2}}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { b.SetCursor(tc.give) if got := b.Cursor(); got != tc.want { t.Errorf("SetCursor(%+v) left the cursor at %+v, want %+v", tc.give, got, tc.want) } }) } } func TestMoveLeftWrapsOntoThePreviousLine(t *testing.T) { b := NewFromString("ab\ncd") b.SetCursor(Position{Line: 1, Col: 0}) b.MoveLeft() if got := b.Cursor(); got != (Position{Line: 0, Col: 2}) { t.Errorf("Cursor() = %+v, want the end of line 0", got) } } func TestMoveLeftStopsAtTheStartOfTheBuffer(t *testing.T) { b := NewFromString("ab") b.SetCursor(Position{}) b.MoveLeft() if got := b.Cursor(); got != (Position{}) { t.Errorf("Cursor() = %+v, want it to stay at the origin", got) } } func TestMoveRightWrapsOntoTheNextLine(t *testing.T) { b := NewFromString("ab\ncd") b.SetCursor(Position{Line: 0, Col: 2}) b.MoveRight() if got := b.Cursor(); got != (Position{Line: 1, Col: 0}) { t.Errorf("Cursor() = %+v, want the start of line 1", got) } } func TestMoveRightStopsAtTheEndOfTheBuffer(t *testing.T) { b := NewFromString("ab") b.MoveBufferEnd() b.MoveRight() if got := b.Cursor(); got != (Position{Line: 0, Col: 2}) { t.Errorf("Cursor() = %+v, want it to stay at the end", got) } } func TestMoveUpAndDownClampTheColumn(t *testing.T) { b := NewFromString("longer line\nab") b.SetCursor(Position{Line: 0, Col: 9}) b.MoveDown() if got := b.Cursor(); got != (Position{Line: 1, Col: 2}) { t.Errorf("after MoveDown, Cursor() = %+v, want the column clamped to 2", got) } b.MoveUp() if got := b.Cursor(); got != (Position{Line: 0, Col: 2}) { t.Errorf("after MoveUp, Cursor() = %+v, want the clamped column kept", got) } } func TestMoveToLineAndBufferBounds(t *testing.T) { b := NewFromString("abc\ndefgh") b.SetCursor(Position{Line: 1, Col: 2}) b.MoveLineStart() if got := b.Cursor(); got != (Position{Line: 1, Col: 0}) { t.Errorf("MoveLineStart left the cursor at %+v", got) } b.MoveLineEnd() if got := b.Cursor(); got != (Position{Line: 1, Col: 5}) { t.Errorf("MoveLineEnd left the cursor at %+v", got) } b.MoveBufferStart() if got := b.Cursor(); got != (Position{}) { t.Errorf("MoveBufferStart left the cursor at %+v", got) } b.MoveBufferEnd() if got := b.Cursor(); got != (Position{Line: 1, Col: 5}) { t.Errorf("MoveBufferEnd left the cursor at %+v", got) } } func TestMoveWordLeft(t *testing.T) { tests := []struct { name string text string from int wantCol int }{ {"from the end of a word", "foo bar", 7, 4}, {"from the start of a word", "foo bar", 4, 0}, {"over several separators", "foo bar", 9, 6}, {"from just after separators", "foo bar", 6, 0}, {"punctuation is a separator", "a.b", 3, 2}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { b := NewFromString(tc.text) b.SetCursor(Position{Line: 0, Col: tc.from}) b.MoveWordLeft() if got := b.Cursor().Col; got != tc.wantCol { t.Errorf("Cursor().Col = %d, want %d", got, tc.wantCol) } }) } } func TestMoveWordLeftAtTheStartOfTheBufferDoesNothing(t *testing.T) { b := NewFromString("foo") b.SetCursor(Position{}) b.MoveWordLeft() if got := b.Cursor(); got != (Position{}) { t.Errorf("Cursor() = %+v, want the origin", got) } } func TestMoveWordRight(t *testing.T) { tests := []struct { name string text string from int wantCol int }{ {"from the start of a word", "foo bar", 0, 4}, {"from inside a word", "foo bar", 1, 4}, {"over several separators", "foo bar", 0, 6}, {"no word left, stops at the end", "foo bar", 4, 7}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { b := NewFromString(tc.text) b.SetCursor(Position{Line: 0, Col: tc.from}) b.MoveWordRight() if got := b.Cursor().Col; got != tc.wantCol { t.Errorf("Cursor().Col = %d, want %d", got, tc.wantCol) } }) } } func TestMoveWordRightAtTheEndOfALineWrapsOver(t *testing.T) { b := NewFromString("foo\nbar") b.SetCursor(Position{Line: 0, Col: 3}) b.MoveWordRight() if got := b.Cursor(); got != (Position{Line: 1, Col: 0}) { t.Errorf("Cursor() = %+v, want the start of the next line", got) } } func TestDisplayColumnExpandsTabs(t *testing.T) { b := NewFromString("\tab\tc") b.SetTabWidth(4) tests := []struct { runeCol int screenCol int }{ {0, 0}, {1, 4}, // just after the leading tab {2, 5}, {3, 6}, {4, 8}, // the second tab fills columns 6 and 7 {5, 9}, } for _, tc := range tests { if got := b.DisplayColumn(0, tc.runeCol); got != tc.screenCol { t.Errorf("DisplayColumn(0, %d) = %d, want %d", tc.runeCol, got, tc.screenCol) } } } func TestDisplayColumnClampsItsArguments(t *testing.T) { b := NewFromString("ab") if got := b.DisplayColumn(0, 99); got != 2 { t.Errorf("DisplayColumn(0, 99) = %d, want 2", got) } if got := b.DisplayColumn(0, -1); got != 0 { t.Errorf("DisplayColumn(0, -1) = %d, want 0", got) } if got := b.DisplayColumn(99, 0); got != 0 { t.Errorf("DisplayColumn(99, 0) = %d, want 0", got) } } func TestRuneColumnIsTheInverseOfDisplayColumn(t *testing.T) { b := NewFromString("\tab\tc") b.SetTabWidth(4) for runeCol := 0; runeCol <= b.LineLen(0); runeCol++ { screenCol := b.DisplayColumn(0, runeCol) if got := b.RuneColumn(0, screenCol); got != runeCol { t.Errorf("RuneColumn(0, DisplayColumn(0, %d)=%d) = %d, want %d", runeCol, screenCol, got, runeCol) } } } func TestRuneColumnInsideATabPicksThatTab(t *testing.T) { b := NewFromString("\tx") b.SetTabWidth(4) for screenCol := 0; screenCol < 4; screenCol++ { if got := b.RuneColumn(0, screenCol); got != 0 { t.Errorf("RuneColumn(0, %d) = %d, want 0 — a click inside a tab lands on it", screenCol, got) } } if got := b.RuneColumn(0, 99); got != 2 { t.Errorf("RuneColumn(0, 99) = %d, want 2 — past the end lands after the last rune", got) } } func TestIsWordRuneAcceptsIdentifierCharacters(t *testing.T) { for _, r := range []rune{'a', 'Z', '0', '_', 'é', 'π'} { if !IsWordRune(r) { t.Errorf("IsWordRune(%q) = false, want true", r) } } for _, r := range []rune{' ', '\t', '.', '(', '-'} { if IsWordRune(r) { t.Errorf("IsWordRune(%q) = true, want false", r) } } }