edit.go

  1// Copyright 2014 The gocui Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5package gocui
  6
  7import (
  8	"github.com/go-errors/errors"
  9
 10	"github.com/mattn/go-runewidth"
 11)
 12
 13const maxInt = int(^uint(0) >> 1)
 14
 15// Editor interface must be satisfied by gocui editors.
 16type Editor interface {
 17	Edit(v *View, key Key, ch rune, mod Modifier)
 18}
 19
 20// The EditorFunc type is an adapter to allow the use of ordinary functions as
 21// Editors. If f is a function with the appropriate signature, EditorFunc(f)
 22// is an Editor object that calls f.
 23type EditorFunc func(v *View, key Key, ch rune, mod Modifier)
 24
 25// Edit calls f(v, key, ch, mod)
 26func (f EditorFunc) Edit(v *View, key Key, ch rune, mod Modifier) {
 27	f(v, key, ch, mod)
 28}
 29
 30// DefaultEditor is the default editor.
 31var DefaultEditor Editor = EditorFunc(simpleEditor)
 32
 33// simpleEditor is used as the default gocui editor.
 34func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
 35	switch {
 36	case ch != 0 && mod == 0:
 37		v.EditWrite(ch)
 38	case key == KeySpace:
 39		v.EditWrite(' ')
 40	case key == KeyBackspace || key == KeyBackspace2:
 41		v.EditDelete(true)
 42	case key == KeyDelete:
 43		v.EditDelete(false)
 44	case key == KeyInsert:
 45		v.Overwrite = !v.Overwrite
 46	case key == KeyEnter:
 47		v.EditNewLine()
 48	case key == KeyArrowDown:
 49		v.MoveCursor(0, 1, false)
 50	case key == KeyArrowUp:
 51		v.MoveCursor(0, -1, false)
 52	case key == KeyArrowLeft:
 53		v.MoveCursor(-1, 0, false)
 54	case key == KeyArrowRight:
 55		v.MoveCursor(1, 0, false)
 56	case key == KeyTab:
 57		v.EditWrite('\t')
 58	case key == KeySpace:
 59		v.EditWrite(' ')
 60	case key == KeyInsert:
 61		v.Overwrite = !v.Overwrite
 62	default:
 63		v.EditWrite(ch)
 64	}
 65}
 66
 67// EditWrite writes a rune at the cursor position.
 68func (v *View) EditWrite(ch rune) {
 69	w := runewidth.RuneWidth(ch)
 70	v.writeRune(v.cx, v.cy, ch)
 71	v.moveCursor(w, 0, true)
 72}
 73
 74// EditDeleteToStartOfLine is the equivalent of pressing ctrl+U in your terminal, it deletes to the start of the line. Or if you are already at the start of the line, it deletes the newline character
 75func (v *View) EditDeleteToStartOfLine() {
 76	x, _ := v.Cursor()
 77	if x == 0 {
 78		v.EditDelete(true)
 79	} else {
 80		// delete characters until we are the start of the line
 81		for x > 0 {
 82			v.EditDelete(true)
 83			x, _ = v.Cursor()
 84		}
 85	}
 86}
 87
 88// EditGotoToStartOfLine takes you to the start of the current line
 89func (v *View) EditGotoToStartOfLine() {
 90	x, _ := v.Cursor()
 91	for x > 0 {
 92		v.MoveCursor(-1, 0, false)
 93		x, _ = v.Cursor()
 94	}
 95}
 96
 97// EditGotoToEndOfLine takes you to the end of the line
 98func (v *View) EditGotoToEndOfLine() {
 99	_, y := v.Cursor()
100	_ = v.SetCursor(0, y+1)
101	x, newY := v.Cursor()
102	if newY == y {
103		// we must be on the last line, so lets move to the very end
104		prevX := -1
105		for prevX != x {
106			prevX = x
107			v.MoveCursor(1, 0, false)
108			x, _ = v.Cursor()
109		}
110	} else {
111		// most left so now we're at the end of the original line
112		v.MoveCursor(-1, 0, false)
113	}
114}
115
116// EditDelete deletes a rune at the cursor position. back determines the
117// direction.
118func (v *View) EditDelete(back bool) {
119	x, y := v.ox+v.cx, v.oy+v.cy
120	if y < 0 {
121		return
122	} else if y >= len(v.viewLines) {
123		v.MoveCursor(-1, 0, true)
124		return
125	}
126
127	maxX, _ := v.Size()
128	if back {
129		if x == 0 { // start of the line
130			if y < 1 {
131				return
132			}
133
134			var maxPrevWidth int
135			if v.Wrap {
136				maxPrevWidth = maxX
137			} else {
138				maxPrevWidth = maxInt
139			}
140
141			if v.viewLines[y].linesX == 0 { // regular line
142				v.mergeLines(v.cy - 1)
143				if len(v.viewLines[y-1].line) < maxPrevWidth {
144					v.MoveCursor(-1, 0, true)
145				}
146			} else { // wrapped line
147				n, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
148				v.MoveCursor(-n, 0, true)
149			}
150		} else { // middle/end of the line
151			n, _ := v.deleteRune(v.cx-1, v.cy)
152			v.MoveCursor(-n, 0, true)
153		}
154	} else {
155		if x == len(v.viewLines[y].line) { // end of the line
156			v.mergeLines(v.cy)
157		} else { // start/middle of the line
158			v.deleteRune(v.cx, v.cy)
159		}
160	}
161}
162
163// EditNewLine inserts a new line under the cursor.
164func (v *View) EditNewLine() {
165	v.breakLine(v.cx, v.cy)
166	v.ox = 0
167	v.cy = v.cy + 1
168	v.cx = 0
169}
170
171// MoveCursor moves the cursor taking into account the width of the line/view,
172// displacing the origin if necessary.
173func (v *View) MoveCursor(dx, dy int, writeMode bool) {
174	ox, oy := v.cx+v.ox, v.cy+v.oy
175	x, y := ox+dx, oy+dy
176
177	if y < 0 || y >= len(v.viewLines) {
178		v.moveCursor(dx, dy, writeMode)
179		return
180	}
181
182	// Removing newline.
183	if x < 0 {
184		var prevLen int
185		if y-1 >= 0 && y-1 < len(v.viewLines) {
186			prevLen = lineWidth(v.viewLines[y-1].line)
187		}
188
189		v.MoveCursor(prevLen, -1, writeMode)
190		return
191	}
192
193	line := v.viewLines[y].line
194	var col int
195	var prevCol int
196	for i := range line {
197		prevCol = col
198		col += runewidth.RuneWidth(line[i].chr)
199		if dx > 0 {
200			if x <= col {
201				x = col
202				break
203			}
204			continue
205		}
206
207		if x < col {
208			x = prevCol
209			break
210		}
211	}
212
213	v.moveCursor(x-ox, y-oy, writeMode)
214}
215
216func (v *View) moveCursor(dx, dy int, writeMode bool) {
217	maxX, maxY := v.Size()
218	cx, cy := v.cx+dx, v.cy+dy
219	x, y := v.ox+cx, v.oy+cy
220
221	var curLineWidth, prevLineWidth int
222	// get the width of the current line
223	curLineWidth = maxInt
224	if v.Wrap {
225		curLineWidth = maxX - 1
226	}
227
228	if !writeMode {
229		curLineWidth = 0
230		if y >= 0 && y < len(v.viewLines) {
231			curLineWidth = lineWidth(v.viewLines[y].line)
232			if v.Wrap && curLineWidth >= maxX {
233				curLineWidth = maxX - 1
234			}
235		}
236	}
237	// get the width of the previous line
238	prevLineWidth = 0
239	if y-1 >= 0 && y-1 < len(v.viewLines) {
240		prevLineWidth = lineWidth(v.viewLines[y-1].line)
241	}
242	// adjust cursor's x position and view's x origin
243	if x > curLineWidth { // move to next line
244		if dx > 0 { // horizontal movement
245			cy++
246			if writeMode || v.oy+cy < len(v.viewLines) {
247				if !v.Wrap {
248					v.ox = 0
249				}
250				v.cx = 0
251			}
252		} else { // vertical movement
253			if curLineWidth > 0 { // move cursor to the EOL
254				if v.Wrap {
255					v.cx = curLineWidth
256				} else {
257					ncx := curLineWidth - v.ox
258					if ncx < 0 {
259						v.ox += ncx
260						if v.ox < 0 {
261							v.ox = 0
262						}
263						v.cx = 0
264					} else {
265						v.cx = ncx
266					}
267				}
268			} else {
269				if writeMode || v.oy+cy < len(v.viewLines) {
270					if !v.Wrap {
271						v.ox = 0
272					}
273					v.cx = 0
274				}
275			}
276		}
277	} else if cx < 0 {
278		if !v.Wrap && v.ox > 0 { // move origin to the left
279			v.ox += cx
280			v.cx = 0
281		} else { // move to previous line
282			cy--
283			if prevLineWidth > 0 {
284				if !v.Wrap { // set origin so the EOL is visible
285					nox := prevLineWidth - maxX + 1
286					if nox < 0 {
287						nox = 0
288					}
289					v.ox = nox
290				}
291				v.cx = prevLineWidth
292			} else {
293				if !v.Wrap {
294					v.ox = 0
295				}
296				v.cx = 0
297			}
298		}
299	} else { // stay on the same line
300		if v.Wrap {
301			v.cx = cx
302		} else {
303			if cx >= maxX {
304				v.ox += cx - maxX + 1
305				v.cx = maxX
306			} else {
307				v.cx = cx
308			}
309		}
310	}
311
312	// adjust cursor's y position and view's y origin
313	if cy < 0 {
314		if v.oy > 0 {
315			v.oy--
316		}
317	} else if writeMode || v.oy+cy < len(v.viewLines) {
318		if cy >= maxY {
319			v.oy++
320		} else {
321			v.cy = cy
322		}
323	}
324}
325
326// writeRune writes a rune into the view's internal buffer, at the
327// position corresponding to the point (x, y). The length of the internal
328// buffer is increased if the point is out of bounds. Overwrite mode is
329// governed by the value of View.overwrite.
330func (v *View) writeRune(x, y int, ch rune) error {
331	v.tainted = true
332
333	x, y, err := v.realPosition(x, y)
334	if err != nil {
335		return err
336	}
337
338	if x < 0 || y < 0 {
339		return errors.New("invalid point")
340	}
341
342	if y >= len(v.lines) {
343		s := make([][]cell, y-len(v.lines)+1)
344		v.lines = append(v.lines, s...)
345	}
346
347	olen := len(v.lines[y])
348
349	var s []cell
350	if x >= len(v.lines[y]) {
351		s = make([]cell, x-len(v.lines[y])+1)
352	} else if !v.Overwrite {
353		s = make([]cell, 1)
354	}
355	v.lines[y] = append(v.lines[y], s...)
356
357	if !v.Overwrite || (v.Overwrite && x >= olen-1) {
358		copy(v.lines[y][x+1:], v.lines[y][x:])
359	}
360	v.lines[y][x] = cell{
361		fgColor: v.FgColor,
362		bgColor: v.BgColor,
363		chr:     ch,
364	}
365
366	return nil
367}
368
369// deleteRune removes a rune from the view's internal buffer, at the
370// position corresponding to the point (x, y).
371// returns the amount of columns that where removed.
372func (v *View) deleteRune(x, y int) (int, error) {
373	v.tainted = true
374
375	x, y, err := v.realPosition(x, y)
376	if err != nil {
377		return 0, err
378	}
379
380	if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
381		return 0, errors.New("invalid point")
382	}
383
384	var tw int
385	for i := range v.lines[y] {
386		w := runewidth.RuneWidth(v.lines[y][i].chr)
387		tw += w
388		if tw > x {
389			v.lines[y] = append(v.lines[y][:i], v.lines[y][i+1:]...)
390			return w, nil
391		}
392
393	}
394
395	return 0, nil
396}
397
398// mergeLines merges the lines "y" and "y+1" if possible.
399func (v *View) mergeLines(y int) error {
400	v.tainted = true
401
402	_, y, err := v.realPosition(0, y)
403	if err != nil {
404		return err
405	}
406
407	if y < 0 || y >= len(v.lines) {
408		return errors.New("invalid point")
409	}
410
411	if y < len(v.lines)-1 { // otherwise we don't need to merge anything
412		v.lines[y] = append(v.lines[y], v.lines[y+1]...)
413		v.lines = append(v.lines[:y+1], v.lines[y+2:]...)
414	}
415	return nil
416}
417
418// breakLine breaks a line of the internal buffer at the position corresponding
419// to the point (x, y).
420func (v *View) breakLine(x, y int) error {
421	v.tainted = true
422
423	x, y, err := v.realPosition(x, y)
424	if err != nil {
425		return err
426	}
427
428	if y < 0 || y >= len(v.lines) {
429		return errors.New("invalid point")
430	}
431
432	var left, right []cell
433	if x < len(v.lines[y]) { // break line
434		left = make([]cell, len(v.lines[y][:x]))
435		copy(left, v.lines[y][:x])
436		right = make([]cell, len(v.lines[y][x:]))
437		copy(right, v.lines[y][x:])
438	} else { // new empty line
439		left = v.lines[y]
440	}
441
442	lines := make([][]cell, len(v.lines)+1)
443	lines[y] = left
444	lines[y+1] = right
445	copy(lines, v.lines[:y])
446	copy(lines[y+2:], v.lines[y+1:])
447	v.lines = lines
448	return nil
449}