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	"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	}
 57}
 58
 59// EditWrite writes a rune at the cursor position.
 60func (v *View) EditWrite(ch rune) {
 61	v.writeRune(v.cx, v.cy, ch)
 62	v.MoveCursor(runewidth.RuneWidth(ch), 0, true)
 63}
 64
 65// EditDelete deletes a rune at the cursor position. back determines the
 66// direction.
 67func (v *View) EditDelete(back bool) {
 68	x, y := v.ox+v.cx, v.oy+v.cy
 69	if y < 0 {
 70		return
 71	} else if y >= len(v.viewLines) {
 72		v.MoveCursor(-1, 0, true)
 73		return
 74	}
 75
 76	maxX, _ := v.Size()
 77	if back {
 78		if x == 0 { // start of the line
 79			if y < 1 {
 80				return
 81			}
 82
 83			var maxPrevWidth int
 84			if v.Wrap {
 85				maxPrevWidth = maxX
 86			} else {
 87				maxPrevWidth = maxInt
 88			}
 89
 90			if v.viewLines[y].linesX == 0 { // regular line
 91				v.mergeLines(v.cy - 1)
 92				if len(v.viewLines[y-1].line) < maxPrevWidth {
 93					v.MoveCursor(-1, 0, true)
 94				}
 95			} else { // wrapped line
 96				ch, _ := v.deleteRune(len(v.viewLines[y-1].line)-1, v.cy-1)
 97				v.MoveCursor(0-runewidth.RuneWidth(ch), 0, true)
 98			}
 99		} else { // middle/end of the line
100			ch, _ := v.deleteRune(v.cx-1, v.cy)
101			v.MoveCursor(0-runewidth.RuneWidth(ch), 0, true)
102		}
103	} else {
104		if x == len(v.viewLines[y].line) { // end of the line
105			v.mergeLines(v.cy)
106		} else { // start/middle of the line
107			v.deleteRune(v.cx, v.cy)
108		}
109	}
110}
111
112// EditNewLine inserts a new line under the cursor.
113func (v *View) EditNewLine() {
114	v.breakLine(v.cx, v.cy)
115	v.ox = 0
116	v.cx = 0
117	v.MoveCursor(0, 1, true)
118}
119
120// MoveCursor moves the cursor taking into account the width of the line/view,
121// displacing the origin if necessary.
122func (v *View) MoveCursor(dx, dy int, writeMode bool) {
123	maxX, maxY := v.Size()
124	cx, cy := v.cx+dx, v.cy+dy
125	x, y := v.ox+cx, v.oy+cy
126
127	var curLineWidth, prevLineWidth int
128	// get the width of the current line
129	if writeMode {
130		if v.Wrap {
131			curLineWidth = maxX - 1
132		} else {
133			curLineWidth = maxInt
134		}
135	} else {
136		if y >= 0 && y < len(v.viewLines) {
137			curLineWidth = len(v.viewLines[y].line)
138			if v.Wrap && curLineWidth >= maxX {
139				curLineWidth = maxX - 1
140			}
141		} else {
142			curLineWidth = 0
143		}
144	}
145	// get the width of the previous line
146	if y-1 >= 0 && y-1 < len(v.viewLines) {
147		prevLineWidth = len(v.viewLines[y-1].line)
148	} else {
149		prevLineWidth = 0
150	}
151
152	// adjust cursor's x position and view's x origin
153	if x > curLineWidth { // move to next line
154		if dx > 0 { // horizontal movement
155			cy++
156			if writeMode || v.oy+cy < len(v.viewLines) {
157				if !v.Wrap {
158					v.ox = 0
159				}
160				v.cx = 0
161			}
162		} else { // vertical movement
163			if curLineWidth > 0 { // move cursor to the EOL
164				if v.Wrap {
165					v.cx = curLineWidth
166				} else {
167					ncx := curLineWidth - v.ox
168					if ncx < 0 {
169						v.ox += ncx
170						if v.ox < 0 {
171							v.ox = 0
172						}
173						v.cx = 0
174					} else {
175						v.cx = ncx
176					}
177				}
178			} else {
179				if writeMode || v.oy+cy < len(v.viewLines) {
180					if !v.Wrap {
181						v.ox = 0
182					}
183					v.cx = 0
184				}
185			}
186		}
187	} else if cx < 0 {
188		if !v.Wrap && v.ox > 0 { // move origin to the left
189			v.ox += cx
190			v.cx = 0
191		} else { // move to previous line
192			cy--
193			if prevLineWidth > 0 {
194				if !v.Wrap { // set origin so the EOL is visible
195					nox := prevLineWidth - maxX + 1
196					if nox < 0 {
197						v.ox = 0
198					} else {
199						v.ox = nox
200					}
201				}
202				v.cx = prevLineWidth
203			} else {
204				if !v.Wrap {
205					v.ox = 0
206				}
207				v.cx = 0
208			}
209		}
210	} else { // stay on the same line
211		if v.Wrap {
212			v.cx = cx
213		} else {
214			if cx >= maxX {
215				v.ox += cx - maxX + 1
216				v.cx = maxX
217			} else {
218				v.cx = cx
219			}
220		}
221	}
222
223	// adjust cursor's y position and view's y origin
224	if cy < 0 {
225		if v.oy > 0 {
226			v.oy--
227		}
228	} else if writeMode || v.oy+cy < len(v.viewLines) {
229		if cy >= maxY {
230			v.oy++
231		} else {
232			v.cy = cy
233		}
234	}
235}
236
237// writeRune writes a rune into the view's internal buffer, at the
238// position corresponding to the point (x, y). The length of the internal
239// buffer is increased if the point is out of bounds. Overwrite mode is
240// governed by the value of View.overwrite.
241func (v *View) writeRune(x, y int, ch rune) error {
242	v.tainted = true
243
244	x, y, err := v.realPosition(x, y)
245	if err != nil {
246		return err
247	}
248
249	if x < 0 || y < 0 {
250		return errors.New("invalid point")
251	}
252
253	if y >= len(v.lines) {
254		s := make([][]cell, y-len(v.lines)+1)
255		v.lines = append(v.lines, s...)
256	}
257
258	olen := len(v.lines[y])
259
260	var s []cell
261	if x >= len(v.lines[y]) {
262		s = make([]cell, x-len(v.lines[y])+1)
263	} else if !v.Overwrite {
264		s = make([]cell, 1)
265	}
266	v.lines[y] = append(v.lines[y], s...)
267
268	if !v.Overwrite || (v.Overwrite && x >= olen-1) {
269		copy(v.lines[y][x+1:], v.lines[y][x:])
270	}
271	v.lines[y][x] = cell{
272		fgColor: v.FgColor,
273		bgColor: v.BgColor,
274		chr:     ch,
275	}
276
277	return nil
278}
279
280// deleteRune removes a rune from the view's internal buffer, at the
281// position corresponding to the point (x, y).
282func (v *View) deleteRune(x, y int) (ch rune, err error) {
283	v.tainted = true
284
285	x, y, err = v.realPosition(x, y)
286	if err != nil {
287		return 0, err
288	}
289
290	if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
291		return 0, errors.New("invalid point")
292	}
293	chx := v.lines[y][x]
294	v.lines[y] = append(v.lines[y][:x], v.lines[y][x+1:]...)
295	return chx.chr, nil
296}
297
298// mergeLines merges the lines "y" and "y+1" if possible.
299func (v *View) mergeLines(y int) error {
300	v.tainted = true
301
302	_, y, err := v.realPosition(0, y)
303	if err != nil {
304		return err
305	}
306
307	if y < 0 || y >= len(v.lines) {
308		return errors.New("invalid point")
309	}
310
311	if y < len(v.lines)-1 { // otherwise we don't need to merge anything
312		v.lines[y] = append(v.lines[y], v.lines[y+1]...)
313		v.lines = append(v.lines[:y+1], v.lines[y+2:]...)
314	}
315	return nil
316}
317
318// breakLine breaks a line of the internal buffer at the position corresponding
319// to the point (x, y).
320func (v *View) breakLine(x, y int) error {
321	v.tainted = true
322
323	x, y, err := v.realPosition(x, y)
324	if err != nil {
325		return err
326	}
327
328	if y < 0 || y >= len(v.lines) {
329		return errors.New("invalid point")
330	}
331
332	var left, right []cell
333	if x < len(v.lines[y]) { // break line
334		left = make([]cell, len(v.lines[y][:x]))
335		copy(left, v.lines[y][:x])
336		right = make([]cell, len(v.lines[y][x:]))
337		copy(right, v.lines[y][x:])
338	} else { // new empty line
339		left = v.lines[y]
340	}
341
342	lines := make([][]cell, len(v.lines)+1)
343	lines[y] = left
344	lines[y+1] = right
345	copy(lines, v.lines[:y])
346	copy(lines[y+2:], v.lines[y+1:])
347	v.lines = lines
348	return nil
349}