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.

events.go · 244 lines · 6.6 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1package editor
2
3import (
4 "github.com/gdamore/tcell/v2"
5
📦 Turbo Core f3ade8d k33g 10h ago6 "rickub.com/turbo-editors/turbo-core/buffer"
🛟 Updated. 28d5985 k33g 18h ago7)
8
9// HandleKey turns a key press into a movement, a selection or an edit.
10//
11// The bindings follow Turbo C where it still makes sense — Ctrl-Insert to
12// copy, Shift-Insert to paste — and modern habits where it does not, since
13// Ctrl-Z for undo is what fingers now expect.
14func (v *View) HandleKey(ev *tcell.EventKey) bool {
15 if v.handleMovement(ev) || v.handleClipboard(ev) || v.handleEditing(ev) {
16 return true
17 }
18 return v.handleTyping(ev)
19}
20
21// handleMovement deals with the keys that only move the cursor, extending the
22// selection when Shift is held.
23func (v *View) handleMovement(ev *tcell.EventKey) bool {
24 move := v.movementFor(ev)
25 if move == nil {
26 return false
27 }
28
29 if ev.Modifiers()&tcell.ModShift != 0 {
30 v.buf.Extend(move)
31 } else {
32 move()
33 v.buf.ClearSelection()
34 }
35
36 v.EnsureCursorVisible()
37 v.notifyCursor()
38 return true
39}
40
41// movementFor returns the cursor movement a key asks for, or nil when the key
42// is not a movement at all.
43//
44// Ctrl widens three of them from a character to a word or to the whole file,
45// which is why the table is built here rather than being a package variable.
46func (v *View) movementFor(ev *tcell.EventKey) func() {
47 ctrl := ev.Modifiers()&tcell.ModCtrl != 0
48
49 var move func()
50 switch ev.Key() {
51 case tcell.KeyLeft:
52 move = pick(ctrl, v.buf.MoveWordLeft, v.buf.MoveLeft)
53 case tcell.KeyRight:
54 move = pick(ctrl, v.buf.MoveWordRight, v.buf.MoveRight)
55 case tcell.KeyUp:
56 move = v.buf.MoveUp
57 case tcell.KeyDown:
58 move = v.buf.MoveDown
59 case tcell.KeyHome:
60 move = pick(ctrl, v.buf.MoveBufferStart, v.buf.MoveLineStart)
61 case tcell.KeyEnd:
62 move = pick(ctrl, v.buf.MoveBufferEnd, v.buf.MoveLineEnd)
63 case tcell.KeyPgUp:
64 move = func() { v.movePage(-1) }
65 case tcell.KeyPgDn:
66 move = func() { v.movePage(+1) }
67 }
68 return move
69}
70
71// pick returns whichever of two movements the modifier calls for.
72func pick(modified bool, withModifier, plain func()) func() {
73 if modified {
74 return withModifier
75 }
76 return plain
77}
78
79// movePage moves the cursor and the view by one screenful.
80func (v *View) movePage(direction int) {
81 page := max(v.VisibleLines(), 1)
82 cursor := v.buf.Cursor()
83
84 v.buf.SetCursor(buffer.Position{Line: cursor.Line + direction*page, Col: cursor.Col})
85 v.ScrollBy(direction * page)
86}
87
88// handleClipboard deals with cut, copy, paste and select-all, in both their
89// Turbo C and their modern spellings.
90func (v *View) handleClipboard(ev *tcell.EventKey) bool {
91 shift := ev.Modifiers()&tcell.ModShift != 0
92 ctrl := ev.Modifiers()&tcell.ModCtrl != 0
93
94 switch {
95 case ev.Key() == tcell.KeyCtrlC, ev.Key() == tcell.KeyInsert && ctrl:
96 v.Copy()
97 case ev.Key() == tcell.KeyCtrlX, ev.Key() == tcell.KeyDelete && shift:
98 v.Cut()
99 case ev.Key() == tcell.KeyCtrlV, ev.Key() == tcell.KeyInsert && shift:
100 v.Paste()
101 case ev.Key() == tcell.KeyCtrlA:
102 v.SelectAll()
103 default:
104 return false
105 }
106
107 v.EnsureCursorVisible()
108 return true
109}
110
111// handleEditing deals with the keys that change the text without inserting a
112// printable character.
113func (v *View) handleEditing(ev *tcell.EventKey) bool {
114 if v.handleCommand(ev) {
115 return true
116 }
117
118 switch ev.Key() {
119 case tcell.KeyEnter:
120 v.buf.InsertNewlineAndIndent()
121 case tcell.KeyBackspace, tcell.KeyBackspace2:
122 v.buf.Backspace()
123 case tcell.KeyDelete:
124 v.buf.Delete()
125 default:
126 return false
127 }
128
129 v.EnsureCursorVisible()
130 v.notifyChange()
131 return true
132}
133
134// handleCommand deals with the keys that ask the view to do something rather
135// than to change the text under the cursor. Each of them reports its own
136// change, so none of them falls through to the notification below.
137func (v *View) handleCommand(ev *tcell.EventKey) bool {
138 switch ev.Key() {
139 case tcell.KeyTab:
140 v.Indent()
141 case tcell.KeyBacktab:
142 v.Unindent()
143 case tcell.KeyCtrlZ:
144 v.Undo()
145 case tcell.KeyCtrlR:
146 v.Redo()
147 // Ctrl-Y deletes a line and Ctrl-N inserts one, as they did in Turbo C and
148 // Turbo Pascal. Ctrl-Y is why redo moved to Ctrl-R: it was the redo key
149 // here for a while, and this is the one place the two claims collide.
150 // Ctrl-Shift-Z, the obvious alternative for redo, is not a key a terminal
151 // can deliver — it arrives as plain Ctrl-Z.
152 case tcell.KeyCtrlY:
153 v.DeleteLine()
154 case tcell.KeyCtrlN:
155 v.InsertLine()
156 case tcell.KeyCtrlSpace:
157 v.requestCompletion()
158 default:
159 return false
160 }
161
162 v.EnsureCursorVisible()
163 return true
164}
165
166// handleTyping inserts a printable character.
167func (v *View) handleTyping(ev *tcell.EventKey) bool {
168 if ev.Key() != tcell.KeyRune || ev.Modifiers()&(tcell.ModAlt|tcell.ModCtrl) != 0 {
169 return false
170 }
171
172 v.buf.InsertRune(ev.Rune())
173 v.EnsureCursorVisible()
174 v.notifyChange()
175
176 // A dot is where a completion list is most useful, so asking for one is
177 // automatic there and manual everywhere else.
178 if ev.Rune() == '.' {
179 v.requestCompletion()
180 }
181 return true
182}
183
184// requestCompletion asks the owner to offer a completion list.
185func (v *View) requestCompletion() {
186 if v.OnCompletionRequest != nil {
187 v.OnCompletionRequest()
188 }
189}
190
191// HandleMouse places the cursor, extends the selection by dragging, and
192// scrolls with the wheel.
193func (v *View) HandleMouse(ev *tcell.EventMouse) bool {
194 x, y := ev.Position()
195
196 switch ev.Buttons() {
197 case tcell.WheelUp:
198 v.ScrollBy(-wheelStep)
199 case tcell.WheelDown:
200 v.ScrollBy(+wheelStep)
201 case tcell.Button1:
202 v.pressOrDrag(x, y)
203 case tcell.ButtonNone:
204 v.selecting = false
205 return false
206 default:
207 return false
208 }
209 return true
210}
211
212// wheelStep is how many lines one notch of the mouse wheel scrolls.
213const wheelStep = 3
214
215// pressOrDrag puts the cursor where the mouse is, starting a selection on the
216// first event of a drag and extending it on the ones that follow.
217//
218// A press is only a *press* when nothing is being dragged: tcell sends Button1
219// for every event of a drag too, and counting those as clicks would turn a
220// slow drag into a double click.
221func (v *View) pressOrDrag(x, y int) {
222 position := v.positionAt(x, y)
223
224 switch {
225 case v.selecting:
226 v.buf.SetCursorKeepingSelection(position)
227 case v.countClick(position) >= 2:
228 // A second press on the same cell selects the word under it, and the
229 // drag carries on from there: holding and moving after a double click
230 // extends from the **start of the word**, by character. That is not
231 // the word-by-word drag a large editor does — it is the cheap half of
232 // it, and it beats the alternative, which is the word collapsing back
233 // to a single character the moment the pointer twitches.
234 v.buf.SelectWord(position)
235 v.selecting = true
236 default:
237 v.buf.SetCursor(position)
238 v.buf.StartSelection()
239 v.selecting = true
240 }
241
242 v.EnsureCursorVisible()
243 v.notifyCursor()
244}