turbo-editors/turbo-corepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

🛟 Updated. 28d5985 · on main · k33g · 4h ago
line_test.go · 248 lines · 6.8 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
package buffer

import "testing"

// atLine returns a buffer holding text with the cursor on a line.
func atLine(text string, line, col int) *Buffer {
	b := NewFromString(text)
	b.SetCursor(Position{Line: line, Col: col})
	return b
}

func TestInsertLineAbovePushesTheCurrentLineDown(t *testing.T) {
	b := atLine("one\ntwo\nthree\n", 1, 2)

	b.InsertLineAbove()

	if got := b.Text(); got != "one\n\ntwo\nthree\n" {
		t.Errorf("Text() = %q, want a blank line before \"two\"", got)
	}
}

func TestInsertLineAboveLeavesTheCursorOnItsOwnText(t *testing.T) {
	// The point of inserting *above*: you keep looking at what you were
	// looking at, with room made over it.
	b := atLine("one\ntwo\n", 1, 2)

	b.InsertLineAbove()

	if got := b.Cursor(); got.Line != 2 || got.Col != 2 {
		t.Errorf("Cursor() = %+v, want line 2 column 2 — the same text, one line lower", got)
	}
	if got := b.Line(b.Cursor().Line); got != "two" {
		t.Errorf("the cursor is on %q, want the line it started on", got)
	}
}

func TestInsertLineAboveWorksOnTheFirstLine(t *testing.T) {
	b := atLine("one\n", 0, 1)

	b.InsertLineAbove()

	if got := b.Text(); got != "\none\n" {
		t.Errorf("Text() = %q", got)
	}
	if got := b.Cursor().Line; got != 1 {
		t.Errorf("Cursor().Line = %d, want 1", got)
	}
}

func TestTheInsertedLineIsBlank(t *testing.T) {
	// Not indented like its neighbour: an indent nobody asked for becomes
	// trailing whitespace the moment they change their mind.
	b := atLine("\t\tdeep\n", 0, 3)

	b.InsertLineAbove()

	if got := b.Line(0); got != "" {
		t.Errorf("the new line is %q, want it empty", got)
	}
}

func TestDeleteLineClosesTheGap(t *testing.T) {
	b := atLine("one\ntwo\nthree\n", 1, 0)

	b.DeleteLine()

	if got := b.Text(); got != "one\nthree\n" {
		t.Errorf("Text() = %q", got)
	}
}

func TestDeleteLineLeavesTheCursorWhereTheNextLineNowIs(t *testing.T) {
	// So that holding the key deletes a run, which is the whole point.
	b := atLine("one\ntwo\nthree\nfour\n", 1, 0)

	b.DeleteLine()
	b.DeleteLine()

	if got := b.Text(); got != "one\nfour\n" {
		t.Errorf("Text() = %q, want two lines gone", got)
	}
	if got := b.Cursor().Line; got != 1 {
		t.Errorf("Cursor().Line = %d, want 1", got)
	}
}

func TestDeletingTheLastLineTakesTheNewlineBeforeIt(t *testing.T) {
	// There is no newline after the last line to take, so its own leading one
	// goes instead. Taking nothing would leave a blank line behind.
	b := NewFromString("one\ntwo")
	b.SetCursor(Position{Line: 1, Col: 0})

	b.DeleteLine()

	if got := b.Text(); got != "one" {
		t.Errorf("Text() = %q, want the line and the newline before it gone", got)
	}
	if got := b.LineCount(); got != 1 {
		t.Errorf("LineCount() = %d, want 1", got)
	}
}

func TestDeletingTheOnlyLineEmptiesItRatherThanRemovingIt(t *testing.T) {
	// Every other operation here assumes there is always a line to be on.
	b := atLine("alone", 0, 3)

	b.DeleteLine()

	if got := b.LineCount(); got != 1 {
		t.Fatalf("LineCount() = %d, want 1 — a buffer always has a line", got)
	}
	if got := b.Text(); got != "" {
		t.Errorf("Text() = %q, want it empty", got)
	}
}

func TestDeleteLineOnTheLastOfSeveralLeavesACursorInTheBuffer(t *testing.T) {
	b := NewFromString("one\ntwo")
	b.SetCursor(Position{Line: 1, Col: 3})

	b.DeleteLine()

	if got := b.Cursor().Line; got >= b.LineCount() {
		t.Errorf("Cursor().Line = %d with %d lines — the cursor is off the end", got, b.LineCount())
	}
}

func TestBothLineEditsAreUndoneInOneStep(t *testing.T) {
	// They go through ReplaceRange, the single mutation path, so the undo
	// history gets one entry each rather than a splice nobody recorded.
	for name, edit := range map[string]func(*Buffer){
		"insert": (*Buffer).InsertLineAbove,
		"delete": (*Buffer).DeleteLine,
	} {
		t.Run(name, func(t *testing.T) {
			b := atLine("one\ntwo\nthree\n", 1, 1)
			before := b.Text()

			edit(b)
			if b.Text() == before {
				t.Fatal("the edit changed nothing")
			}
			if !b.Undo() {
				t.Fatal("Undo() reported nothing to undo")
			}

			if got := b.Text(); got != before {
				t.Errorf("after one undo Text() = %q, want %q", got, before)
			}
		})
	}
}

func TestALineEditClearsTheSelection(t *testing.T) {
	// ReplaceRange clears it, and a selection left behind after the text under
	// it moved would highlight the wrong characters.
	b := atLine("one\ntwo\nthree\n", 1, 0)
	b.StartSelection()
	b.SetCursorKeepingSelection(Position{Line: 1, Col: 3})

	b.DeleteLine()

	if _, has := b.Selection(); has {
		t.Error("a selection survived the line being deleted")
	}
}

// selected returns the text a buffer's selection covers, and whether there is
// one at all.
func selected(b *Buffer) (string, bool) {
	if _, has := b.Selection(); !has {
		return "", false
	}
	return b.SelectedText(), true
}

func TestSelectWordTakesTheWholeWordFromAnywhereInIt(t *testing.T) {
	const line = "func mainLoop() {}"

	for _, col := range []int{5, 9, 12} {
		b := NewFromString(line)
		b.SelectWord(Position{Line: 0, Col: col})

		got, has := selected(b)
		if !has || got != "mainLoop" {
			t.Errorf("from column %d the selection is %q (any: %v), want %q", col, got, has, "mainLoop")
		}
	}
}

func TestSelectWordCountsUnderscoresAndDigits(t *testing.T) {
	// The same rule as Ctrl-Left and the completion popup: one editor, one
	// idea of a word.
	b := NewFromString("var max_retries2 = 3")
	b.SelectWord(Position{Line: 0, Col: 6})

	if got, _ := selected(b); got != "max_retries2" {
		t.Errorf("the selection is %q", got)
	}
}

func TestSelectWordStopsAtPunctuation(t *testing.T) {
	b := NewFromString("fmt.Println(x)")
	b.SelectWord(Position{Line: 0, Col: 5})

	if got, _ := selected(b); got != "Println" {
		t.Errorf("the selection is %q, want the dot and bracket left out", got)
	}
}

func TestSelectWordOffAWordSelectsNothing(t *testing.T) {
	// Editors disagree about what a run of punctuation means; nothing is at
	// least predictable.
	// "var x = fmt.Println" — a space at 3, a dot at 11, and nothing at 40.
	for name, col := range map[string]int{"a space": 3, "a dot": 11, "past the end": 40} {
		t.Run(name, func(t *testing.T) {
			b := NewFromString("var x = fmt.Println")
			b.SelectWord(Position{Line: 0, Col: col})

			if got, has := selected(b); has {
				t.Errorf("selected %q, want nothing", got)
			}
		})
	}
}

func TestSelectWordOnAnEmptyLineSelectsNothing(t *testing.T) {
	b := NewFromString("one\n\nthree\n")
	b.SelectWord(Position{Line: 1, Col: 0})

	if _, has := selected(b); has {
		t.Error("an empty line has a word on it")
	}
	if got := b.Cursor(); got.Line != 1 {
		t.Errorf("Cursor() = %+v, want the cursor moved to the click", got)
	}
}

func TestSelectWordLeavesTheCursorAtTheEndOfTheWord(t *testing.T) {
	// So that typing replaces the word, and Shift-Right extends from its end,
	// which is what a selection made by dragging would do.
	b := NewFromString("alpha beta")
	b.SelectWord(Position{Line: 0, Col: 7})

	if got := b.Cursor().Col; got != 10 {
		t.Errorf("Cursor().Col = %d, want 10 — the end of \"beta\"", got)
	}
}