turbo-editors/turbo-corepublic Fork 0
v1.0.1
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 v1.0.1 · k33g · 17h ago
selection.go · 126 lines · 3.6 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
package buffer

// StartSelection drops the selection anchor where the cursor is. Moving the
// cursor afterwards extends the selection from that anchor, which is what
// holding Shift does.
//
//	b := buffer.NewFromString("hello")
//	b.StartSelection()
//	b.MoveWordRight()
//	fmt.Println(b.SelectedText()) // hello
func (b *Buffer) StartSelection() {
	anchor := b.cursor
	b.anchor = &anchor
}

// ClearSelection forgets the anchor, leaving nothing selected.
func (b *Buffer) ClearSelection() {
	b.anchor = nil
}

// SelectAll selects the whole buffer and puts the cursor at its end.
func (b *Buffer) SelectAll() {
	b.MoveBufferStart()
	b.Extend(b.MoveBufferEnd)
}

// Extend runs a cursor movement while keeping the selection anchor, which is
// exactly what holding Shift does. If nothing was selected yet, the anchor is
// dropped where the cursor stands before the move.
//
// Every movement method works here, so there is no Shift-flavoured twin of
// each of them:
//
//	b.Extend(b.MoveWordRight)
//	b.Extend(b.MoveDown)
func (b *Buffer) Extend(move func()) {
	if b.anchor == nil {
		b.StartSelection()
	}
	anchor := b.anchor
	move()
	b.anchor = anchor
}

// SetCursorKeepingSelection moves the cursor to p while keeping the selection
// anchor. It is Extend for an absolute position, which is what a mouse drag
// needs; SetCursor is the plain, selection-clearing move.
func (b *Buffer) SetCursorKeepingSelection(p Position) {
	b.Extend(func() { b.SetCursor(p) })
}

// Selection returns the selected span and whether there is one. A selection
// that has been reduced to nothing by moving the cursor back onto the anchor
// counts as absent.
func (b *Buffer) Selection() (Range, bool) {
	if b.anchor == nil {
		return Range{}, false
	}
	r := NewRange(*b.anchor, b.cursor)
	if r.IsEmpty() {
		return Range{}, false
	}
	return r, true
}

// SelectedText returns the selected text, or the empty string when nothing is
// selected. Lines are joined with line feeds regardless of the file's own line
// endings, because this is what goes on the clipboard.
func (b *Buffer) SelectedText() string {
	r, ok := b.Selection()
	if !ok {
		return ""
	}
	return joinRunes(b.textIn(r))
}

// DeleteSelection removes the selected text and reports whether there was any.
// It is the first thing every editing operation does, so that typing over a
// selection replaces it.
func (b *Buffer) DeleteSelection() bool {
	r, ok := b.Selection()
	if !ok {
		b.ClearSelection()
		return false
	}
	b.ClearSelection()
	b.DeleteRange(r)
	return true
}

// SelectWord selects the word at a position and puts the cursor at its end.
//
// The word is a run of the runes IsWordRune accepts — the same notion
// Ctrl-Left and Ctrl-Right move by, and the same one the completion popup uses
// to work out what is being typed. One editor, one idea of a word.
//
//	b := buffer.NewFromString("func main() {}")
//	b.SelectWord(buffer.Position{Line: 0, Col: 6})
//	r, _ := b.Selection() // covers "main"
//
// A position that is not on a word rune — a space, a bracket, past the end of
// a line — selects nothing and only moves the cursor. Editors disagree about
// what a run of punctuation means, and answering nothing is at least an answer
// that can be predicted.
func (b *Buffer) SelectWord(p Position) {
	p = b.clamp(p)
	line := b.lines[p.Line]

	if p.Col >= len(line) || !IsWordRune(line[p.Col]) {
		b.ClearSelection()
		b.SetCursor(p)
		return
	}

	start, end := p.Col, p.Col
	for start > 0 && IsWordRune(line[start-1]) {
		start--
	}
	for end < len(line) && IsWordRune(line[end]) {
		end++
	}

	b.SetCursor(Position{Line: p.Line, Col: start})
	b.StartSelection()
	b.SetCursorKeepingSelection(Position{Line: p.Line, Col: end})
}