Indentation with <> and move lines around

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-20 00:40:36 +0200
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-20 00:56:59 +0200
Commit 8aa7bd44fe8f67b5e0b89e9afcf68ddce1df7f76 (patch)
-rw-r--r-- editor.go 255
-rw-r--r-- kevent.go 300
-rw-r--r-- replace.go 2
3 files changed, 513 insertions, 44 deletions
diff --git a/editor.go b/editor.go
...
1080
	return indent
1080
	return indent
1081
}
1081
}
1082
  
1082
  
  
1083
func (e *Editor) indentLine(y int) {
  
1084
	b := e.activeBuffer()
  
1085
	if b == nil || y < 0 || y >= len(b.buffer) {
  
1086
		return
  
1087
	}
  
1088
  
  
1089
	tabWidth := Config.DefaultTabWidth
  
1090
	if b.fileType != nil {
  
1091
		tabWidth = b.fileType.TabWidth
  
1092
	}
  
1093
  
  
1094
	var indentRunes []rune
  
1095
	if e.useTabs() {
  
1096
		indentRunes = []rune{'\t'}
  
1097
	} else {
  
1098
		indentRunes = []rune(strings.Repeat(" ", tabWidth))
  
1099
	}
  
1100
  
  
1101
	line := b.buffer[y]
  
1102
	newLine := append(indentRunes, line...)
  
1103
	b.buffer[y] = newLine
  
1104
  
  
1105
	// Shift cursors on this line
  
1106
	for i := range b.cursors {
  
1107
		if b.cursors[i].Y == y {
  
1108
			b.cursors[i].X += len(indentRunes)
  
1109
			b.cursors[i].PreferredCol = b.cursors[i].X
  
1110
		}
  
1111
	}
  
1112
  
  
1113
	// Shift visual anchor if it's on this line
  
1114
	if (e.mode == ModeVisual || e.mode == ModeVisualLine || e.mode == ModeVisualBlock) && e.visualStartY == y {
  
1115
		e.visualStartX += len(indentRunes)
  
1116
	}
  
1117
}
  
1118
  
  
1119
func (e *Editor) unindentLine(y int) {
  
1120
	b := e.activeBuffer()
  
1121
	if b == nil || y < 0 || y >= len(b.buffer) {
  
1122
		return
  
1123
	}
  
1124
  
  
1125
	line := b.buffer[y]
  
1126
	if len(line) == 0 {
  
1127
		return
  
1128
	}
  
1129
  
  
1130
	tabWidth := Config.DefaultTabWidth
  
1131
	if b.fileType != nil {
  
1132
		tabWidth = b.fileType.TabWidth
  
1133
	}
  
1134
  
  
1135
	removedCount := 0
  
1136
	if line[0] == '\t' {
  
1137
		removedCount = 1
  
1138
	} else if line[0] == ' ' {
  
1139
		removedCount = 0
  
1140
		for removedCount < tabWidth && removedCount < len(line) && line[removedCount] == ' ' {
  
1141
			removedCount++
  
1142
		}
  
1143
	}
  
1144
  
  
1145
	if removedCount > 0 {
  
1146
		b.buffer[y] = line[removedCount:]
  
1147
		// Shift cursors on this line
  
1148
		for i := range b.cursors {
  
1149
			if b.cursors[i].Y == y {
  
1150
				b.cursors[i].X -= removedCount
  
1151
				if b.cursors[i].X < 0 {
  
1152
					b.cursors[i].X = 0
  
1153
				}
  
1154
				b.cursors[i].PreferredCol = b.cursors[i].X
  
1155
			}
  
1156
		}
  
1157
  
  
1158
		// Shift visual anchor if it's on this line
  
1159
		if (e.mode == ModeVisual || e.mode == ModeVisualLine || e.mode == ModeVisualBlock) && e.visualStartY == y {
  
1160
			e.visualStartX -= removedCount
  
1161
			if e.visualStartX < 0 {
  
1162
				e.visualStartX = 0
  
1163
			}
  
1164
		}
  
1165
	}
  
1166
}
  
1167
  
  
1168
func (e *Editor) Indent() {
  
1169
	e.IndentSelection(false)
  
1170
}
  
1171
  
  
1172
func (e *Editor) IndentSelection(stayInMode bool) {
  
1173
	b := e.activeBuffer()
  
1174
	if b == nil {
  
1175
		return
  
1176
	}
  
1177
	if b.readOnly {
  
1178
		e.message = "File is read-only"
  
1179
		return
  
1180
	}
  
1181
  
  
1182
	e.saveState()
  
1183
  
  
1184
	if e.mode == ModeVisual || e.mode == ModeVisualLine || e.mode == ModeVisualBlock {
  
1185
		y1, _, y2, _ := e.getSelectionBounds()
  
1186
		for y := y1; y <= y2; y++ {
  
1187
			e.indentLine(y)
  
1188
		}
  
1189
		if !stayInMode {
  
1190
			e.mode = ModeNormal
  
1191
		}
  
1192
	} else {
  
1193
		// Indent lines with cursors
  
1194
		lines := make(map[int]bool)
  
1195
		for _, c := range b.cursors {
  
1196
			lines[c.Y] = true
  
1197
		}
  
1198
		for y := range lines {
  
1199
			e.indentLine(y)
  
1200
		}
  
1201
	}
  
1202
  
  
1203
	if b.syntax != nil {
  
1204
		b.syntax.Reparse([]byte(b.toString()))
  
1205
	}
  
1206
	e.markModified()
  
1207
}
  
1208
  
  
1209
func (e *Editor) Unindent() {
  
1210
	e.UnindentSelection(false)
  
1211
}
  
1212
  
  
1213
func (e *Editor) UnindentSelection(stayInMode bool) {
  
1214
	b := e.activeBuffer()
  
1215
	if b == nil {
  
1216
		return
  
1217
	}
  
1218
	if b.readOnly {
  
1219
		e.message = "File is read-only"
  
1220
		return
  
1221
	}
  
1222
  
  
1223
	e.saveState()
  
1224
  
  
1225
	if e.mode == ModeVisual || e.mode == ModeVisualLine || e.mode == ModeVisualBlock {
  
1226
		y1, _, y2, _ := e.getSelectionBounds()
  
1227
		for y := y1; y <= y2; y++ {
  
1228
			e.unindentLine(y)
  
1229
		}
  
1230
		if !stayInMode {
  
1231
			e.mode = ModeNormal
  
1232
		}
  
1233
	} else {
  
1234
		// Unindent lines with cursors
  
1235
		lines := make(map[int]bool)
  
1236
		for _, c := range b.cursors {
  
1237
			lines[c.Y] = true
  
1238
		}
  
1239
		for y := range lines {
  
1240
			e.unindentLine(y)
  
1241
		}
  
1242
	}
  
1243
  
  
1244
	if b.syntax != nil {
  
1245
		b.syntax.Reparse([]byte(b.toString()))
  
1246
	}
  
1247
	e.markModified()
  
1248
}
  
1249
  
  
1250
func (e *Editor) MoveLinesUp() {
  
1251
	e.moveLines(-1)
  
1252
}
  
1253
  
  
1254
func (e *Editor) MoveLinesDown() {
  
1255
	e.moveLines(1)
  
1256
}
  
1257
  
  
1258
func (e *Editor) moveLines(dy int) {
  
1259
	b := e.activeBuffer()
  
1260
	if b == nil || b.readOnly || len(b.buffer) == 0 {
  
1261
		return
  
1262
	}
  
1263
  
  
1264
	e.saveState()
  
1265
  
  
1266
	var y1, y2 int
  
1267
	if e.mode == ModeVisual || e.mode == ModeVisualLine || e.mode == ModeVisualBlock {
  
1268
		y1, _, y2, _ = e.getSelectionBounds()
  
1269
	} else {
  
1270
		y1 = b.PrimaryCursor().Y
  
1271
		y2 = b.PrimaryCursor().Y
  
1272
		for _, c := range b.cursors {
  
1273
			if c.Y < y1 {
  
1274
				y1 = c.Y
  
1275
			}
  
1276
			if c.Y > y2 {
  
1277
				y2 = c.Y
  
1278
			}
  
1279
		}
  
1280
	}
  
1281
  
  
1282
	if dy == -1 && y1 > 0 {
  
1283
		// Move block up: swap y1-1 with the block [y1, y2]
  
1284
		lineAbove := b.buffer[y1-1]
  
1285
		for y := y1; y <= y2; y++ {
  
1286
			b.buffer[y-1] = b.buffer[y]
  
1287
		}
  
1288
		b.buffer[y2] = lineAbove
  
1289
  
  
1290
		// Update cursors
  
1291
		for i := range b.cursors {
  
1292
			if b.cursors[i].Y >= y1 && b.cursors[i].Y <= y2 {
  
1293
				b.cursors[i].Y--
  
1294
			} else if b.cursors[i].Y == y1-1 {
  
1295
				b.cursors[i].Y += (y2 - y1 + 1)
  
1296
			}
  
1297
		}
  
1298
		// Update visual anchor
  
1299
		if e.mode == ModeVisual || e.mode == ModeVisualLine || e.mode == ModeVisualBlock {
  
1300
			if e.visualStartY >= y1 && e.visualStartY <= y2 {
  
1301
				e.visualStartY--
  
1302
			} else if e.visualStartY == y1-1 {
  
1303
				e.visualStartY += (y2 - y1 + 1)
  
1304
			}
  
1305
		}
  
1306
	} else if dy == 1 && y2 < len(b.buffer)-1 {
  
1307
		// Move block down: swap y2+1 with the block [y1, y2]
  
1308
		lineBelow := b.buffer[y2+1]
  
1309
		for y := y2; y >= y1; y-- {
  
1310
			b.buffer[y+1] = b.buffer[y]
  
1311
		}
  
1312
		b.buffer[y1] = lineBelow
  
1313
  
  
1314
		// Update cursors
  
1315
		for i := range b.cursors {
  
1316
			if b.cursors[i].Y >= y1 && b.cursors[i].Y <= y2 {
  
1317
				b.cursors[i].Y++
  
1318
			} else if b.cursors[i].Y == y2+1 {
  
1319
				b.cursors[i].Y -= (y2 - y1 + 1)
  
1320
			}
  
1321
		}
  
1322
		// Update visual anchor
  
1323
		if e.mode == ModeVisual || e.mode == ModeVisualLine || e.mode == ModeVisualBlock {
  
1324
			if e.visualStartY >= y1 && e.visualStartY <= y2 {
  
1325
				e.visualStartY++
  
1326
			} else if e.visualStartY == y2+1 {
  
1327
				e.visualStartY -= (y2 - y1 + 1)
  
1328
			}
  
1329
		}
  
1330
	}
  
1331
  
  
1332
	if b.syntax != nil {
  
1333
		b.syntax.Reparse([]byte(b.toString()))
  
1334
	}
  
1335
	e.markModified()
  
1336
}
  
1337
  
1083
// insertNewline breaks the line at cursor and handles auto-indentation.
1338
// insertNewline breaks the line at cursor and handles auto-indentation.
1084
func (e *Editor) insertNewline() {
1339
func (e *Editor) insertNewline() {
1085
	b := e.activeBuffer()
1340
	b := e.activeBuffer()
...
diff --git a/kevent.go b/kevent.go
...
5
// etc.).
5
// etc.).
6
  
6
  
7
import (
7
import (
  
8
	"time"
  
9
  
8
	"github.com/nsf/termbox-go"
10
	"github.com/nsf/termbox-go"
9
)
11
)
10
  
12
  
  
13
const (
  
14
	seqAltArrowUp    = "[1;3A"
  
15
	seqAltArrowDown  = "[1;3B"
  
16
	seqAltArrowRight = "[1;3C"
  
17
	seqAltArrowLeft  = "[1;3D"
  
18
)
  
19
  
11
// HandleEvents is the central loop that waits for and processes all user input.
20
// HandleEvents is the central loop that waits for and processes all user input.
12
func (e *Editor) HandleEvents() {
21
func (e *Editor) HandleEvents() {
  
22
	eventChan := make(chan termbox.Event)
  
23
	go func() {
  
24
		for {
  
25
			eventChan <- termbox.PollEvent()
  
26
		}
  
27
	}()
  
28
  
13
	for {
29
	for {
14
		// Redraw the screen before waiting for the next event.
30
		// Redraw the screen before waiting for the next event.
15
		e.draw()
31
		e.draw()
16
		ev := termbox.PollEvent()
32
		var ev termbox.Event
  
33
		select {
  
34
		case ev = <-eventChan:
  
35
		}
17
  
36
  
18
		// Handle interrupt events (triggered by diagnostic updates).
37
		// Handle interrupt events (triggered by diagnostic updates).
19
		// Fetch latest diagnostics from LSP client.
38
		// Fetch latest diagnostics from LSP client.
...
39
				return
58
				return
40
			}
59
			}
41
  
60
  
42
			// Dispatch the key event to the handler for the current editor mode.
61
			// Special handling for ESC sequences (Alt+Arrows) in InputEsc mode.
43
			switch e.mode {
62
			if ev.Key == termbox.KeyEsc {
44
			case ModeNormal:
63
				seq := ""
45
				e.handleNormalMode(ev)
64
				timer := time.NewTimer(30 * time.Millisecond)
46
			case ModeInsert:
65
				matched := false
47
				e.handleInsertMode(ev)
66
				processed := false
48
			case ModeCommand:
67
			seqLoop:
49
				e.handleCommandMode(ev)
68
				for {
50
			case ModeFuzzy:
69
					select {
51
				e.handleFuzzyMode(ev)
70
					case nextEv := <-eventChan:
52
			case ModeFind:
71
						if nextEv.Type == termbox.EventKey {
53
				e.handleFindMode(ev)
72
							if nextEv.Key != 0 {
54
			case ModeVisual:
73
								// Some functional key followed ESC
55
				e.handleVisualMode(ev)
74
								if nextEv.Key == termbox.KeyArrowLeft {
56
			case ModeVisualLine:
75
									ev = termbox.Event{Type: termbox.EventKey, Key: termbox.KeyArrowLeft, Mod: termbox.ModAlt}
57
				e.handleVisualLineMode(ev)
76
									matched = true
58
			case ModeVisualBlock:
77
								} else if nextEv.Key == termbox.KeyArrowRight {
59
				e.handleVisualBlockMode(ev)
78
									ev = termbox.Event{Type: termbox.EventKey, Key: termbox.KeyArrowRight, Mod: termbox.ModAlt}
60
			case ModeReplace:
79
									matched = true
61
				e.handleReplaceMode(ev)
80
								} else if nextEv.Key == termbox.KeyArrowUp {
62
			case ModeConfirm:
81
									ev = termbox.Event{Type: termbox.EventKey, Key: termbox.KeyArrowUp, Mod: termbox.ModAlt}
63
				e.handleConfirmMode(ev)
82
									matched = true
  
83
								} else if nextEv.Key == termbox.KeyArrowDown {
  
84
									ev = termbox.Event{Type: termbox.EventKey, Key: termbox.KeyArrowDown, Mod: termbox.ModAlt}
  
85
									matched = true
  
86
								} else {
  
87
									// Not a known Alt+Arrow, process ESC then this key
  
88
									e.dispatchEvent(ev)
  
89
									ev = nextEv
  
90
								}
  
91
								break seqLoop
  
92
							} else {
  
93
								seq += string(nextEv.Ch)
  
94
								if seq == seqAltArrowLeft {
  
95
									ev = termbox.Event{Type: termbox.EventKey, Key: termbox.KeyArrowLeft, Mod: termbox.ModAlt}
  
96
									matched = true
  
97
									break seqLoop
  
98
								}
  
99
								if seq == seqAltArrowRight {
  
100
									ev = termbox.Event{Type: termbox.EventKey, Key: termbox.KeyArrowRight, Mod: termbox.ModAlt}
  
101
									matched = true
  
102
									break seqLoop
  
103
								}
  
104
								if seq == seqAltArrowUp {
  
105
									ev = termbox.Event{Type: termbox.EventKey, Key: termbox.KeyArrowUp, Mod: termbox.ModAlt}
  
106
									matched = true
  
107
									break seqLoop
  
108
								}
  
109
								if seq == seqAltArrowDown {
  
110
									ev = termbox.Event{Type: termbox.EventKey, Key: termbox.KeyArrowDown, Mod: termbox.ModAlt}
  
111
									matched = true
  
112
									break seqLoop
  
113
								}
  
114
								if len(seq) > 5 {
  
115
									// Sequence too long, process as individual keys
  
116
									e.dispatchEvent(ev)
  
117
									for _, r := range seq {
  
118
										e.dispatchEvent(termbox.Event{Type: termbox.EventKey, Ch: r})
  
119
									}
  
120
									processed = true
  
121
									break seqLoop
  
122
								}
  
123
							}
  
124
						} else {
  
125
							// Not a key event, process ESC then this event
  
126
							e.dispatchEvent(ev)
  
127
							ev = nextEv
  
128
							break seqLoop
  
129
						}
  
130
					case <-timer.C:
  
131
						break seqLoop
  
132
					}
  
133
				}
  
134
				if processed {
  
135
					continue
  
136
				}
  
137
				if !matched && seq != "" {
  
138
					e.dispatchEvent(ev)
  
139
					for _, r := range seq {
  
140
						e.dispatchEvent(termbox.Event{Type: termbox.EventKey, Ch: r})
  
141
					}
  
142
					continue
  
143
				}
64
			}
144
			}
  
145
  
  
146
			e.dispatchEvent(ev)
65
		} else if ev.Type == termbox.EventMouse {
147
		} else if ev.Type == termbox.EventMouse {
66
			e.handleMouseEvent(ev)
148
			e.handleMouseEvent(ev)
67
		}
149
		}
68
	}
150
	}
69
}
151
}
70
  
152
  
  
153
func (e *Editor) dispatchEvent(ev termbox.Event) {
  
154
	// Dispatch the key event to the handler for the current editor mode.
  
155
	switch e.mode {
  
156
	case ModeNormal:
  
157
		e.handleNormalMode(ev)
  
158
	case ModeInsert:
  
159
		e.handleInsertMode(ev)
  
160
	case ModeCommand:
  
161
		e.handleCommandMode(ev)
  
162
	case ModeFuzzy:
  
163
		e.handleFuzzyMode(ev)
  
164
	case ModeFind:
  
165
		e.handleFindMode(ev)
  
166
	case ModeVisual:
  
167
		e.handleVisualMode(ev)
  
168
	case ModeVisualLine:
  
169
		e.handleVisualLineMode(ev)
  
170
	case ModeVisualBlock:
  
171
		e.handleVisualBlockMode(ev)
  
172
	case ModeReplace:
  
173
		e.handleReplaceMode(ev)
  
174
	case ModeConfirm:
  
175
		e.handleConfirmMode(ev)
  
176
	}
  
177
}
  
178
  
71
// handleNormalMode processes keyboard input when the editor is in Normal mode.
179
// handleNormalMode processes keyboard input when the editor is in Normal mode.
72
func (e *Editor) handleNormalMode(ev termbox.Event) {
180
func (e *Editor) handleNormalMode(ev termbox.Event) {
73
	// Escape clears any pending multi-key commands or secondary cursors.
181
	// Escape clears any pending multi-key commands or secondary cursors.
...
85
  
193
  
86
	switch ev.Key {
194
	switch ev.Key {
87
	case termbox.KeyArrowLeft:
195
	case termbox.KeyArrowLeft:
88
		e.moveCursor(-1, 0)
196
		if ev.Mod&termbox.ModAlt != 0 {
  
197
			e.UnindentSelection(true)
  
198
		} else {
  
199
			e.moveCursor(-1, 0)
  
200
		}
89
	case termbox.KeyArrowRight:
201
	case termbox.KeyArrowRight:
90
		e.moveCursor(1, 0)
202
		if ev.Mod&termbox.ModAlt != 0 {
  
203
			e.IndentSelection(true)
  
204
		} else {
  
205
			e.moveCursor(1, 0)
  
206
		}
91
	case termbox.KeyArrowUp:
207
	case termbox.KeyArrowUp:
92
		if ev.Mod != 0 {
208
		if ev.Mod&termbox.ModAlt != 0 {
  
209
			e.MoveLinesUp()
  
210
		} else if ev.Mod != 0 {
93
			e.addCursorAbove()
211
			e.addCursorAbove()
94
		} else {
212
		} else {
95
			e.moveCursor(0, -1)
213
			e.moveCursor(0, -1)
96
		}
214
		}
97
	case termbox.KeyArrowDown:
215
	case termbox.KeyArrowDown:
98
		if ev.Mod != 0 {
216
		if ev.Mod&termbox.ModAlt != 0 {
  
217
			e.MoveLinesDown()
  
218
		} else if ev.Mod != 0 {
99
			e.addCursorBelow()
219
			e.addCursorBelow()
100
		} else {
220
		} else {
101
			e.moveCursor(0, 1)
221
			e.moveCursor(0, 1)
...
123
  
243
  
124
	// Prevent key event fallthrough.
244
	// Prevent key event fallthrough.
125
	if ev.Key != 0 {
245
	if ev.Key != 0 {
  
246
		return
  
247
	}
  
248
  
  
249
	if ev.Mod&termbox.ModAlt != 0 {
126
		return
250
		return
127
	}
251
	}
128
  
252
  
...
249
		} else {
373
		} else {
250
			e.pendingKey = 'd'
374
			e.pendingKey = 'd'
251
		}
375
		}
  
376
	case '>':
  
377
		if e.pendingKey == '>' {
  
378
			e.Indent()
  
379
			e.pendingKey = 0
  
380
		} else {
  
381
			e.pendingKey = '>'
  
382
		}
  
383
	case '<':
  
384
		if e.pendingKey == '<' {
  
385
			e.Unindent()
  
386
			e.pendingKey = 0
  
387
		} else {
  
388
			e.pendingKey = '<'
  
389
		}
252
	case 'y':
390
	case 'y':
253
		e.yankLine()
391
		e.yankLine()
254
		e.message = "Line yanked"
392
		e.message = "Line yanked"
...
493
		e.triggerAutocomplete()
631
		e.triggerAutocomplete()
494
	default:
632
	default:
495
		// If a character key was pressed, insert the character.
633
		// If a character key was pressed, insert the character.
496
		if ev.Ch != 0 {
634
		if ev.Ch != 0 && ev.Mod&termbox.ModAlt == 0 {
497
			e.insertRune(ev.Ch)
635
			e.insertRune(ev.Ch)
498
			// Close autocomplete if user keeps typing.
636
			// Close autocomplete if user keeps typing.
499
			if e.showAutocomplete {
637
			if e.showAutocomplete {
...
553
		// Navigate to next command in history
691
		// Navigate to next command in history
554
		e.commands.NavigateHistoryDown()
692
		e.commands.NavigateHistoryDown()
555
	default:
693
	default:
556
		if ev.Ch != 0 {
694
		if ev.Ch != 0 && ev.Mod&termbox.ModAlt == 0 {
557
			// Insert character at cursor position
695
			// Insert character at cursor position
558
			e.commandBuffer = append(e.commandBuffer[:e.commandCursorX], append([]rune{ev.Ch}, e.commandBuffer[e.commandCursorX:]...)...)
696
			e.commandBuffer = append(e.commandBuffer[:e.commandCursorX], append([]rune{ev.Ch}, e.commandBuffer[e.commandCursorX:]...)...)
559
			e.commandCursorX++
697
			e.commandCursorX++
...
584
		e.updateFuzzyResults()
722
		e.updateFuzzyResults()
585
	default:
723
	default:
586
		// Update filter as user types.
724
		// Update filter as user types.
587
		if ev.Ch != 0 {
725
		if ev.Ch != 0 && ev.Mod&termbox.ModAlt == 0 {
588
			e.fuzzyBuffer = append(e.fuzzyBuffer, ev.Ch)
726
			e.fuzzyBuffer = append(e.fuzzyBuffer, ev.Ch)
589
			e.updateFuzzyResults()
727
			e.updateFuzzyResults()
590
		}
728
		}
...
619
		e.lastSearch = string(e.findBuffer)
757
		e.lastSearch = string(e.findBuffer)
620
	default:
758
	default:
621
		// Incremental search: update e.lastSearch as the user types.
759
		// Incremental search: update e.lastSearch as the user types.
622
		if ev.Ch != 0 {
760
		if ev.Ch != 0 && ev.Mod&termbox.ModAlt == 0 {
623
			e.findBuffer = append(e.findBuffer, ev.Ch)
761
			e.findBuffer = append(e.findBuffer, ev.Ch)
624
			e.lastSearch = string(e.findBuffer)
762
			e.lastSearch = string(e.findBuffer)
625
		}
763
		}
...
636
  
774
  
637
	switch ev.Key {
775
	switch ev.Key {
638
	case termbox.KeyArrowLeft:
776
	case termbox.KeyArrowLeft:
639
		e.moveCursor(-1, 0)
777
		if ev.Mod&termbox.ModAlt != 0 {
  
778
			e.UnindentSelection(true)
  
779
		} else {
  
780
			e.moveCursor(-1, 0)
  
781
		}
640
	case termbox.KeyArrowRight:
782
	case termbox.KeyArrowRight:
641
		e.moveCursor(1, 0)
783
		if ev.Mod&termbox.ModAlt != 0 {
  
784
			e.IndentSelection(true)
  
785
		} else {
  
786
			e.moveCursor(1, 0)
  
787
		}
642
	case termbox.KeyArrowUp:
788
	case termbox.KeyArrowUp:
643
		e.moveCursor(0, -1)
789
		if ev.Mod&termbox.ModAlt != 0 {
  
790
			e.MoveLinesUp()
  
791
		} else {
  
792
			e.moveCursor(0, -1)
  
793
		}
644
	case termbox.KeyArrowDown:
794
	case termbox.KeyArrowDown:
645
		e.moveCursor(0, 1)
795
		if ev.Mod&termbox.ModAlt != 0 {
  
796
			e.MoveLinesDown()
  
797
		} else {
  
798
			e.moveCursor(0, 1)
  
799
		}
646
	}
800
	}
647
  
801
  
648
	// Prevent key event fallthrough.
802
	// Prevent key event fallthrough.
...
650
		return
804
		return
651
	}
805
	}
652
  
806
  
  
807
	if ev.Mod&termbox.ModAlt != 0 {
  
808
		return
  
809
	}
  
810
  
653
	switch ev.Ch {
811
	switch ev.Ch {
654
	case 'J':
812
	case 'J':
655
		e.saveState()
813
		e.saveState()
...
675
		e.deleteVisualSelection()
833
		e.deleteVisualSelection()
676
		e.checkDiagnostics()
834
		e.checkDiagnostics()
677
		e.message = "Selection deleted"
835
		e.message = "Selection deleted"
  
836
	case '>':
  
837
		e.Indent()
  
838
	case '<':
  
839
		e.Unindent()
678
	case 'x':
840
	case 'x':
679
		if e.pendingKey == 'z' {
841
		if e.pendingKey == 'z' {
680
			e.saveState()
842
			e.saveState()
...
745
  
907
  
746
	switch ev.Key {
908
	switch ev.Key {
747
	case termbox.KeyArrowLeft:
909
	case termbox.KeyArrowLeft:
748
		e.moveCursor(-1, 0)
910
		if ev.Mod&termbox.ModAlt != 0 {
  
911
			e.UnindentSelection(true)
  
912
		} else {
  
913
			e.moveCursor(-1, 0)
  
914
		}
749
	case termbox.KeyArrowRight:
915
	case termbox.KeyArrowRight:
750
		e.moveCursor(1, 0)
916
		if ev.Mod&termbox.ModAlt != 0 {
  
917
			e.IndentSelection(true)
  
918
		} else {
  
919
			e.moveCursor(1, 0)
  
920
		}
751
	case termbox.KeyArrowUp:
921
	case termbox.KeyArrowUp:
752
		e.moveCursor(0, -1)
922
		if ev.Mod&termbox.ModAlt != 0 {
  
923
			e.MoveLinesUp()
  
924
		} else {
  
925
			e.moveCursor(0, -1)
  
926
		}
753
	case termbox.KeyArrowDown:
927
	case termbox.KeyArrowDown:
754
		e.moveCursor(0, 1)
928
		if ev.Mod&termbox.ModAlt != 0 {
  
929
			e.MoveLinesDown()
  
930
		} else {
  
931
			e.moveCursor(0, 1)
  
932
		}
755
	}
933
	}
756
  
934
  
757
	// Prevent key event fallthrough.
935
	// Prevent key event fallthrough.
758
	if ev.Key != 0 {
936
	if ev.Key != 0 {
  
937
		return
  
938
	}
  
939
  
  
940
	if ev.Mod&termbox.ModAlt != 0 {
759
		return
941
		return
760
	}
942
	}
761
  
943
  
...
784
		e.deleteVisualSelection()
966
		e.deleteVisualSelection()
785
		e.checkDiagnostics()
967
		e.checkDiagnostics()
786
		e.message = "Selection deleted"
968
		e.message = "Selection deleted"
  
969
	case '>':
  
970
		e.Indent()
  
971
	case '<':
  
972
		e.Unindent()
787
	case 'x':
973
	case 'x':
788
		if e.pendingKey == 'z' {
974
		if e.pendingKey == 'z' {
789
			e.saveState()
975
			e.saveState()
...
853
  
1039
  
854
	switch ev.Key {
1040
	switch ev.Key {
855
	case termbox.KeyArrowLeft:
1041
	case termbox.KeyArrowLeft:
856
		e.moveCursor(-1, 0)
1042
		if ev.Mod&termbox.ModAlt != 0 {
  
1043
			e.UnindentSelection(true)
  
1044
		} else {
  
1045
			e.moveCursor(-1, 0)
  
1046
		}
857
	case termbox.KeyArrowRight:
1047
	case termbox.KeyArrowRight:
858
		e.moveCursor(1, 0)
1048
		if ev.Mod&termbox.ModAlt != 0 {
  
1049
			e.IndentSelection(true)
  
1050
		} else {
  
1051
			e.moveCursor(1, 0)
  
1052
		}
859
	case termbox.KeyArrowUp:
1053
	case termbox.KeyArrowUp:
860
		e.moveCursor(0, -1)
1054
		if ev.Mod&termbox.ModAlt != 0 {
  
1055
			e.MoveLinesUp()
  
1056
		} else {
  
1057
			e.moveCursor(0, -1)
  
1058
		}
861
	case termbox.KeyArrowDown:
1059
	case termbox.KeyArrowDown:
862
		e.moveCursor(0, 1)
1060
		if ev.Mod&termbox.ModAlt != 0 {
  
1061
			e.MoveLinesDown()
  
1062
		} else {
  
1063
			e.moveCursor(0, 1)
  
1064
		}
863
	}
1065
	}
864
  
1066
  
865
	// Prevent key event fallthrough.
1067
	// Prevent key event fallthrough.
866
	if ev.Key != 0 {
1068
	if ev.Key != 0 {
  
1069
		return
  
1070
	}
  
1071
  
  
1072
	if ev.Mod&termbox.ModAlt != 0 {
867
		return
1073
		return
868
	}
1074
	}
869
  
1075
  
...
892
		e.deleteVisualSelection()
1098
		e.deleteVisualSelection()
893
		e.checkDiagnostics()
1099
		e.checkDiagnostics()
894
		e.message = "Selection deleted"
1100
		e.message = "Selection deleted"
  
1101
	case '>':
  
1102
		e.Indent()
  
1103
	case '<':
  
1104
		e.Unindent()
895
	case 'x':
1105
	case 'x':
896
		if e.pendingKey == 'z' {
1106
		if e.pendingKey == 'z' {
897
			e.saveState()
1107
			e.saveState()
...
981
  
1191
  
982
	// Prevent key event fallthrough.
1192
	// Prevent key event fallthrough.
983
	if ev.Key != 0 {
1193
	if ev.Key != 0 {
  
1194
		return
  
1195
	}
  
1196
  
  
1197
	if ev.Mod&termbox.ModAlt != 0 {
984
		return
1198
		return
985
	}
1199
	}
986
  
1200
  
...
diff --git a/replace.go b/replace.go
...
75
		e.replaceInput = append(e.replaceInput, ' ')
75
		e.replaceInput = append(e.replaceInput, ' ')
76
		e.updateReplacePreview()
76
		e.updateReplacePreview()
77
	default:
77
	default:
78
		if ev.Ch != 0 {
78
		if ev.Ch != 0 && ev.Mod&termbox.ModAlt == 0 {
79
			e.replaceInput = append(e.replaceInput, ev.Ch)
79
			e.replaceInput = append(e.replaceInput, ev.Ch)
80
			e.updateReplacePreview() // Live preview of matches as user types.
80
			e.updateReplacePreview() // Live preview of matches as user types.
81
		}
81
		}
...